Showing preview only (4,221K chars total). Download the full file or copy to clipboard to get everything.
Repository: vincjo/datatables
Branch: main
Commit: 755b8179c99d
Files: 640
Total size: 3.9 MB
Directory structure:
gitextract_z5jwp21r/
├── .gitignore
├── .npmrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── datatables.code-workspace
├── ecosystem.config.cjs
├── mdsvex.config.js
├── package.json
├── src/
│ ├── app.d.ts
│ ├── app.html
│ ├── hooks.server.ts
│ ├── lib/
│ │ ├── index.ts
│ │ ├── legacy/
│ │ │ ├── index.ts
│ │ │ ├── local/
│ │ │ │ ├── Comparator.ts
│ │ │ │ ├── Context.ts
│ │ │ │ ├── DataHandler.ts
│ │ │ │ ├── Datatable.svelte
│ │ │ │ ├── Pagination.svelte
│ │ │ │ ├── RowCount.svelte
│ │ │ │ ├── RowsPerPage.svelte
│ │ │ │ ├── Search.svelte
│ │ │ │ ├── Th.svelte
│ │ │ │ ├── ThFilter.svelte
│ │ │ │ ├── handlers/
│ │ │ │ │ ├── EventHandler.ts
│ │ │ │ │ ├── FilterHandler.ts
│ │ │ │ │ ├── PageHandler.ts
│ │ │ │ │ ├── SearchHandler.ts
│ │ │ │ │ ├── SelectHandler.ts
│ │ │ │ │ └── SortHandler.ts
│ │ │ │ ├── helpers/
│ │ │ │ │ ├── AdvancedFilterHelper.ts
│ │ │ │ │ ├── CalculationHelper.ts
│ │ │ │ │ └── FilterHelper.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils.ts
│ │ │ └── remote/
│ │ │ ├── Context.ts
│ │ │ ├── DataHandler.ts
│ │ │ ├── Datatable.svelte
│ │ │ ├── Pagination.svelte
│ │ │ ├── README.md
│ │ │ ├── RowCount.svelte
│ │ │ ├── RowsPerPage.svelte
│ │ │ ├── Search.svelte
│ │ │ ├── SelectedCount.svelte
│ │ │ ├── Th.svelte
│ │ │ ├── ThFilter.svelte
│ │ │ ├── handlers/
│ │ │ │ ├── EventHandler.ts
│ │ │ │ ├── FilterHandler.ts
│ │ │ │ ├── PageHandler.ts
│ │ │ │ ├── SearchHandler.ts
│ │ │ │ ├── SelectHandler.ts
│ │ │ │ ├── SortHandler.ts
│ │ │ │ └── TriggerHandler.ts
│ │ │ └── index.ts
│ │ ├── server/
│ │ │ └── index.ts
│ │ ├── src/
│ │ │ ├── client/
│ │ │ │ ├── AbstractTableHandler.svelte.ts
│ │ │ │ ├── TableHandler.svelte.ts
│ │ │ │ ├── builders/
│ │ │ │ │ ├── AdvancedFilterBuilder.svelte.ts
│ │ │ │ │ ├── CSVBuilder.svelte.ts
│ │ │ │ │ ├── CalculationBuilder.svelte.ts
│ │ │ │ │ ├── FilterBuilder.svelte.ts
│ │ │ │ │ ├── QueryBuilder.svelte.ts
│ │ │ │ │ ├── RecordFilterBuilder.svelte.ts
│ │ │ │ │ ├── SearchBuilder.svelte.ts
│ │ │ │ │ └── SortBuilder.svelte.ts
│ │ │ │ ├── core/
│ │ │ │ │ ├── check.ts
│ │ │ │ │ ├── entry.ts
│ │ │ │ │ ├── field.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── rows.ts
│ │ │ │ │ └── value.ts
│ │ │ │ ├── handlers/
│ │ │ │ │ ├── FilterHandler.svelte.ts
│ │ │ │ │ ├── PageHandler.svelte.ts
│ │ │ │ │ ├── QueryHandler.svelte.ts
│ │ │ │ │ ├── SearchHandler.svelte.ts
│ │ │ │ │ ├── SelectHandler.svelte.ts
│ │ │ │ │ └── SortHandler.svelte.ts
│ │ │ │ └── index.ts
│ │ │ ├── server/
│ │ │ │ ├── AbstractTableHandler.svelte.ts
│ │ │ │ ├── TableHandler.svelte.ts
│ │ │ │ ├── builders/
│ │ │ │ │ ├── FilterBuilder.svelte.ts
│ │ │ │ │ ├── SearchBuilder.svelte.ts
│ │ │ │ │ └── SortBuilder.svelte.ts
│ │ │ │ ├── handlers/
│ │ │ │ │ ├── FetchHandler.svelte.ts
│ │ │ │ │ ├── FilterHandler.svelte.ts
│ │ │ │ │ ├── PageHandler.svelte.ts
│ │ │ │ │ ├── SearchHandler.svelte.ts
│ │ │ │ │ ├── SelectHandler.svelte.ts
│ │ │ │ │ └── SortHandler.svelte.ts
│ │ │ │ └── index.ts
│ │ │ └── shared/
│ │ │ ├── Datatable.svelte
│ │ │ ├── EventDispatcher.ts
│ │ │ ├── Pagination.svelte
│ │ │ ├── RowCount.svelte
│ │ │ ├── RowsPerPage.svelte
│ │ │ ├── Search.svelte
│ │ │ ├── Th.svelte
│ │ │ ├── ThFilter.svelte
│ │ │ ├── ThSort.svelte
│ │ │ ├── builders/
│ │ │ │ ├── HighlightBuilder.svelte.ts
│ │ │ │ └── ViewBuilder.svelte.ts
│ │ │ ├── clsx/
│ │ │ │ ├── Datatable.svelte
│ │ │ │ ├── Pagination.svelte
│ │ │ │ └── ThSort.svelte
│ │ │ └── index.ts
│ │ └── style.css
│ ├── routes/
│ │ ├── +layout.svelte
│ │ ├── +page.svelte
│ │ ├── Description.svelte
│ │ ├── Header.svelte
│ │ ├── Header_Github.svelte
│ │ ├── Header_MobileNav.svelte
│ │ ├── Header_Mode.svelte
│ │ ├── Header_Theme.svelte
│ │ ├── Header_Version.svelte
│ │ ├── about/
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ ├── Nav.svelte
│ │ │ ├── Nav_Mobile.svelte
│ │ │ └── [slug]/
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ │ ├── api/
│ │ │ └── [mode]/
│ │ │ ├── +layout.server.ts
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ ├── Nav.svelte
│ │ │ ├── Nav_Key.svelte
│ │ │ ├── Nav_Mobile.svelte
│ │ │ ├── [slug]/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Content.svelte
│ │ │ │ └── Content_Ext.svelte
│ │ │ ├── content.svx
│ │ │ ├── gen/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ └── Board.svelte
│ │ │ └── md/
│ │ │ ├── +layout.svelte
│ │ │ ├── +layout.ts
│ │ │ ├── +page.svelte
│ │ │ ├── Nav.svelte
│ │ │ ├── Nav_Key.svelte
│ │ │ ├── [slug]/
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── +page.ts
│ │ │ │ ├── AST.svelte
│ │ │ │ ├── Content.svelte
│ │ │ │ └── Content_Ext.svelte
│ │ │ └── content.svx
│ │ ├── components/
│ │ │ ├── +layout.svelte
│ │ │ └── +page.svelte
│ │ ├── docs/
│ │ │ ├── client/
│ │ │ │ ├── +layout.svelte
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── add-on/
│ │ │ │ │ ├── csv-export/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── record-filter/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── content.svx
│ │ │ │ │ └── data_cars.ts
│ │ │ │ ├── calculation/
│ │ │ │ │ ├── avg/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Advanced.svelte
│ │ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ │ └── code.svx
│ │ │ │ │ ├── bounds/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ │ └── code.svx
│ │ │ │ │ ├── data_cars.ts
│ │ │ │ │ ├── data_grocery.ts
│ │ │ │ │ ├── data_parcel.ts
│ │ │ │ │ ├── distinct/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ │ └── code.svx
│ │ │ │ │ ├── median/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Advanced.svelte
│ │ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ │ └── code.svx
│ │ │ │ │ └── sum/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Advanced.svelte
│ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ └── code.svx
│ │ │ │ ├── filters/
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── check/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Comparators.svelte
│ │ │ │ │ │ ├── Comparators_Check.svelte
│ │ │ │ │ │ ├── content.svx
│ │ │ │ │ │ ├── content2.svx
│ │ │ │ │ │ └── data.ts
│ │ │ │ │ ├── criteria/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ ├── content.svx
│ │ │ │ │ │ └── data.ts
│ │ │ │ │ ├── highlight/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── input/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── nested/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Nested.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── getting-started/
│ │ │ │ │ ├── hello-world/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Client.svx
│ │ │ │ │ │ └── Main.svelte
│ │ │ │ │ ├── i18n/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ └── doc.svx
│ │ │ │ │ ├── intro/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── migration/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── overview/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── pagination/
│ │ │ │ │ ├── navigation/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Navigation.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── pages/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── CurrentPage.svelte
│ │ │ │ │ │ ├── Pages.svelte
│ │ │ │ │ │ ├── PagesWithEllipsis.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── row-count/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── rows-per-page/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── search/
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── highlight/
│ │ │ │ │ │ ├── +page.server.ts
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Example.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── input/
│ │ │ │ │ │ ├── +page.server.ts
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Example.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── recursive/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ ├── Main_Search.svelte
│ │ │ │ │ │ ├── Main_Tree.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── regex/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── scope/
│ │ │ │ │ ├── +page.server.ts
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Example.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── select/
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── all/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── row/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── scope/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── sort/
│ │ │ │ │ ├── button/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Main.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ ├── dates/
│ │ │ │ │ │ ├── +page.svelte
│ │ │ │ │ │ ├── Example.svelte
│ │ │ │ │ │ └── content.svx
│ │ │ │ │ └── nested/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Nested.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ └── view/
│ │ │ │ ├── freeze/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── ordering/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ └── visible/
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ └── content.svx
│ │ │ └── server/
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.server.ts
│ │ │ ├── data/
│ │ │ │ ├── invalidate/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── is-loading/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── content.svx
│ │ │ │ └── load/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── content.svx
│ │ │ ├── filters/
│ │ │ │ └── input/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── content.svx
│ │ │ ├── getting-started/
│ │ │ │ ├── hello-world/
│ │ │ │ │ ├── +page.server.ts
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── content.svx
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ └── doc.svx
│ │ │ │ ├── intro/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── migration/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ └── overview/
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ └── content.svx
│ │ │ ├── pagination/
│ │ │ │ ├── navigation/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Navigation.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── pages/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── CurrentPage.svelte
│ │ │ │ │ ├── Pages.svelte
│ │ │ │ │ ├── PagesWithEllipsis.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── row-count/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ └── rows-per-page/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── content.svx
│ │ │ ├── search/
│ │ │ │ └── input/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── content.svx
│ │ │ ├── select/
│ │ │ │ ├── +layout.server.ts
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── Main_OLD.svelte
│ │ │ │ ├── all/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── content.svx
│ │ │ │ ├── api.ts
│ │ │ │ └── row/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── content.svx
│ │ │ ├── sort/
│ │ │ │ └── button/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── content.svx
│ │ │ ├── tips/
│ │ │ │ └── sticky-header/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── doc.svx
│ │ │ └── view/
│ │ │ ├── +layout.server.ts
│ │ │ ├── api.ts
│ │ │ ├── freeze/
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ └── content.svx
│ │ │ ├── ordering/
│ │ │ │ ├── +page.svelte
│ │ │ │ └── content.svx
│ │ │ └── visible/
│ │ │ ├── +page.svelte
│ │ │ ├── Main.svelte
│ │ │ └── content.svx
│ │ ├── examples/
│ │ │ ├── client/
│ │ │ │ ├── +layout.svelte
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Test.svelte
│ │ │ │ ├── column-ordering/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Client.svx
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ └── SortButton.svelte
│ │ │ │ ├── crud/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Datatable.svelte
│ │ │ │ │ ├── Modal_Create.svelte
│ │ │ │ │ ├── Modal_Destroy.svelte
│ │ │ │ │ ├── Modal_Update.svelte
│ │ │ │ │ ├── api.svelte.ts
│ │ │ │ │ ├── doc.svx
│ │ │ │ │ └── intro.svx
│ │ │ │ ├── distinct/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Basic.svelte
│ │ │ │ │ ├── RowCount.svelte
│ │ │ │ │ ├── Search.svelte
│ │ │ │ │ ├── code.svx
│ │ │ │ │ └── data.ts
│ │ │ │ ├── hello-world/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Client.svx
│ │ │ │ │ ├── Main.svelte
│ │ │ │ │ └── page.server.ts
│ │ │ │ ├── nested-array/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Client.svx
│ │ │ │ │ └── Main.svelte
│ │ │ │ ├── pokedex/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── MCF_Main.svelte
│ │ │ │ │ ├── MCF_PokemonStats.svelte
│ │ │ │ │ ├── MCF_Table.svelte
│ │ │ │ │ ├── MCF_TableFilter.svelte
│ │ │ │ │ └── data.ts
│ │ │ │ ├── shadcn/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Table.svelte
│ │ │ │ │ ├── Table_Footer.svelte
│ │ │ │ │ ├── Table_Footer_Pagination.svelte
│ │ │ │ │ ├── Table_Footer_RowSelection.svelte
│ │ │ │ │ ├── Table_Footer_RowsPerPage.svelte
│ │ │ │ │ ├── Table_Header.svelte
│ │ │ │ │ ├── Table_Header_ColumnVisibility.svelte
│ │ │ │ │ ├── Table_Header_Filter.svelte
│ │ │ │ │ ├── Table_Th.svelte
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── test/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ ├── Table.svelte
│ │ │ │ │ └── data2.ts
│ │ │ │ ├── test-calculation/
│ │ │ │ │ ├── +page.svelte
│ │ │ │ │ └── data.ts
│ │ │ │ └── tree/
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Client.svx
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── Main_Search.svelte
│ │ │ │ └── Main_Tree.svelte
│ │ │ └── server/
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.server.ts
│ │ │ ├── Features.svelte
│ │ │ ├── beer-api/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── example.server.ts
│ │ │ ├── hello-world/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── example.server.ts
│ │ │ ├── pokedex-api/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── PokemonStats.svelte
│ │ │ │ ├── PokemonTypes.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── example.server.ts
│ │ │ ├── ssr-user/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ └── params.svelte.ts
│ │ │ ├── todo-api/
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── Main.svelte
│ │ │ │ ├── api.ts
│ │ │ │ └── example.server.ts
│ │ │ └── user-api/
│ │ │ ├── +page.server.ts
│ │ │ ├── +page.svelte
│ │ │ ├── Main.svelte
│ │ │ ├── api.ts
│ │ │ └── example.server.ts
│ │ └── export/
│ │ └── [mode]/
│ │ ├── gen/
│ │ │ ├── +page.server.ts
│ │ │ ├── +page.svelte
│ │ │ └── Board.svelte
│ │ └── md/
│ │ ├── +layout.svelte
│ │ ├── +layout.ts
│ │ ├── +page.svelte
│ │ ├── Nav.svelte
│ │ ├── Nav_Key.svelte
│ │ ├── [slug]/
│ │ │ ├── +page.svelte
│ │ │ ├── +page.ts
│ │ │ ├── AST.svelte
│ │ │ ├── Content.svelte
│ │ │ └── Content_Ext.svelte
│ │ └── content.svx
│ └── site/
│ ├── Banner.svelte
│ ├── Logo.svelte
│ ├── Site.svelte.ts
│ ├── components/
│ │ ├── Demo.svelte
│ │ ├── Demo_Code.svelte
│ │ ├── Demo_Code_Icon.svelte
│ │ ├── Demo_Code_Nav.svelte
│ │ ├── Demo_CopyButton.svelte
│ │ ├── Demo_Data.svelte
│ │ ├── Highlight.svelte
│ │ ├── Install.svelte
│ │ ├── Nav_Search.svelte
│ │ ├── docs/
│ │ │ ├── Layout.svelte
│ │ │ ├── Nav.svelte
│ │ │ └── Nav_Mobile.svelte
│ │ └── examples/
│ │ ├── Layout.svelte
│ │ ├── Nav.svelte
│ │ └── Nav_Mobile.svelte
│ ├── data/
│ │ ├── cars.ts
│ │ ├── data.11000.ts
│ │ ├── data.75.ts
│ │ ├── data.ts
│ │ ├── data_with_null.ts
│ │ ├── int-bool-string.ts
│ │ ├── pokedex.ts
│ │ └── tree.ts
│ ├── index.ts
│ └── utils/
│ └── viewport.ts
├── static/
│ ├── documents/
│ │ ├── client/
│ │ │ ├── methods.clearFilters.json
│ │ │ ├── methods.clearSearch.json
│ │ │ ├── methods.clearSelection.json
│ │ │ ├── methods.clearSort.json
│ │ │ ├── methods.createAdvancedFilter.json
│ │ │ ├── methods.createCSV.json
│ │ │ ├── methods.createCalculation.json
│ │ │ ├── methods.createFilter.json
│ │ │ ├── methods.createQuery.json
│ │ │ ├── methods.createRecordFilter.json
│ │ │ ├── methods.createSearch.json
│ │ │ ├── methods.createSort.json
│ │ │ ├── methods.createView.json
│ │ │ ├── methods.filter.json
│ │ │ ├── methods.getSelectedRows.json
│ │ │ ├── methods.getView.json
│ │ │ ├── methods.on.json
│ │ │ ├── methods.select.json
│ │ │ ├── methods.selectAll.json
│ │ │ ├── methods.setPage.json
│ │ │ ├── methods.setRows.json
│ │ │ ├── methods.setRowsPerPage.json
│ │ │ ├── nav.json
│ │ │ ├── properties.allRows.json
│ │ │ ├── properties.clientWidth.json
│ │ │ ├── properties.currentPage.json
│ │ │ ├── properties.element.json
│ │ │ ├── properties.filterCount.json
│ │ │ ├── properties.filters.json
│ │ │ ├── properties.i18n.json
│ │ │ ├── properties.isAllSelected.json
│ │ │ ├── properties.pageCount.json
│ │ │ ├── properties.pages.json
│ │ │ ├── properties.pagesWithEllipsis.json
│ │ │ ├── properties.queries.json
│ │ │ ├── properties.rowCount.json
│ │ │ ├── properties.rows.json
│ │ │ ├── properties.rowsPerPage.json
│ │ │ ├── properties.selected.json
│ │ │ ├── types.Check.json
│ │ │ ├── types.ColumnView.json
│ │ │ ├── types.Criterion.json
│ │ │ ├── types.Field.json
│ │ │ ├── types.Filter.json
│ │ │ ├── types.Internationalization.json
│ │ │ ├── types.Row.json
│ │ │ ├── types.SearchType.json
│ │ │ ├── types.Sort.json
│ │ │ ├── types.SortParams.json
│ │ │ ├── types.TableHandlerParams.json
│ │ │ ├── types.TableParams.json
│ │ │ └── types.ViewColumn.json
│ │ ├── markdown/
│ │ │ ├── client/
│ │ │ │ ├── methods.clearSort.md
│ │ │ │ ├── methods.createAdvancedFilter.md
│ │ │ │ ├── methods.createCSV.md
│ │ │ │ ├── methods.createCalculation.md
│ │ │ │ ├── methods.createRecordFilter.md
│ │ │ │ ├── methods.getSelectedRows.md
│ │ │ │ ├── methods.setRows.md
│ │ │ │ ├── methods.setRowsPerPage.md
│ │ │ │ ├── properties.allRows.md
│ │ │ │ ├── properties.filters.md
│ │ │ │ ├── types.Check.md
│ │ │ │ ├── types.Comparator.md
│ │ │ │ ├── types.Criterion.md
│ │ │ │ ├── types.Filter.md
│ │ │ │ ├── types.Internationalization.md
│ │ │ │ ├── types.Sort.md
│ │ │ │ ├── types.SortParams.md
│ │ │ │ └── types.TableParams.md
│ │ │ ├── methods.clearFilters.md
│ │ │ ├── methods.clearSearch.md
│ │ │ ├── methods.clearSelection.md
│ │ │ ├── methods.createFilter.md
│ │ │ ├── methods.createSearch.md
│ │ │ ├── methods.createSort.md
│ │ │ ├── methods.createView.md
│ │ │ ├── methods.getView.md
│ │ │ ├── methods.on.md
│ │ │ ├── methods.select.md
│ │ │ ├── methods.selectAll.md
│ │ │ ├── methods.setPage.md
│ │ │ ├── methods.setRowsPerPage.md
│ │ │ ├── properties.clientWidth.md
│ │ │ ├── properties.currentPage.md
│ │ │ ├── properties.element.md
│ │ │ ├── properties.filterCount.md
│ │ │ ├── properties.i18n.md
│ │ │ ├── properties.isAllSelected.md
│ │ │ ├── properties.pageCount.md
│ │ │ ├── properties.pages.md
│ │ │ ├── properties.pagesWithEllipsis.md
│ │ │ ├── properties.rowCount.md
│ │ │ ├── properties.rows.md
│ │ │ ├── properties.rowsPerPage.md
│ │ │ ├── properties.selected.md
│ │ │ ├── properties.sort.md
│ │ │ ├── server/
│ │ │ │ ├── methods.getState.md
│ │ │ │ ├── methods.invalidate.md
│ │ │ │ ├── methods.load.md
│ │ │ │ ├── properties.filters.md
│ │ │ │ ├── properties.isLoading.md
│ │ │ │ ├── properties.sort.md
│ │ │ │ ├── properties.totalRows.md
│ │ │ │ ├── types.Filter.md
│ │ │ │ ├── types.Sort.md
│ │ │ │ └── types.State.md
│ │ │ ├── types.ColumnView.md
│ │ │ ├── types.Field.md
│ │ │ ├── types.Internationalization.md
│ │ │ └── types.Row.md
│ │ └── server/
│ │ ├── methods.clearFilters.json
│ │ ├── methods.clearSearch.json
│ │ ├── methods.clearSelection.json
│ │ ├── methods.createFilter.json
│ │ ├── methods.createSearch.json
│ │ ├── methods.createSort.json
│ │ ├── methods.createView.json
│ │ ├── methods.filter.json
│ │ ├── methods.getState.json
│ │ ├── methods.getView.json
│ │ ├── methods.invalidate.json
│ │ ├── methods.load.json
│ │ ├── methods.on.json
│ │ ├── methods.select.json
│ │ ├── methods.selectAll.json
│ │ ├── methods.setPage.json
│ │ ├── methods.setRowsPerPage.json
│ │ ├── methods.setTotalRows.json
│ │ ├── nav.json
│ │ ├── properties.clientWidth.json
│ │ ├── properties.currentPage.json
│ │ ├── properties.debounce.json
│ │ ├── properties.element.json
│ │ ├── properties.events.json
│ │ ├── properties.filterCount.json
│ │ ├── properties.filters.json
│ │ ├── properties.i18n.json
│ │ ├── properties.isAllSelected.json
│ │ ├── properties.isLoading.json
│ │ ├── properties.pageCount.json
│ │ ├── properties.pages.json
│ │ ├── properties.pagesWithEllipsis.json
│ │ ├── properties.rowCount.json
│ │ ├── properties.rows.json
│ │ ├── properties.rowsPerPage.json
│ │ ├── properties.search.json
│ │ ├── properties.selectBy.json
│ │ ├── properties.selected.json
│ │ ├── properties.sort.json
│ │ ├── properties.totalRows.json
│ │ ├── types.ColumnView.json
│ │ ├── types.Field.json
│ │ ├── types.Filter.json
│ │ ├── types.Internationalization.json
│ │ ├── types.Row.json
│ │ ├── types.Sort.json
│ │ ├── types.State.json
│ │ ├── types.TableParams.json
│ │ └── types.ViewColumn.json
│ ├── fonts/
│ │ ├── Inter/
│ │ │ ├── OFL.txt
│ │ │ └── README.txt
│ │ └── Roboto/
│ │ └── LICENSE.txt
│ ├── global.css
│ ├── gros-theme.css
│ ├── gros.css
│ ├── prism-dark.css
│ └── prism-light.css
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/.vscode
/package
/build
.env
.env.*
/dist
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
================================================
FILE: .npmrc
================================================
engine-strict=true
================================================
FILE: CHANGELOG.md
================================================
# 2.8.0 - 2025-12-17
### Added
[client] Disable filter recursion [#187](https://github.com/vincjo/datatables/issues/187)
```ts
const filter = table.createFilter(field).isNotRecursive()
const advancedFilter = table.createAdvancedFilter(field).isNotRecursive()
```
# 2.7.0 - 2025-11-25
### Fixed
[server] optimize (reduce) the number of reloads by removing irrelevant triggering events
### Changed
Add an optional debounce parameter to the TableHandler. Default = 0
This adds delays before an invalidation is triggered.
# 2.6.6 - 2025-10-08
### Fixed
fix: `RecordFilter` search capabilities.
# 2.6.5 - 2025-10-08
### Fixed
fix: Server-Side pagination - Changing filters resets to page 1 [#179](https://github.com/vincjo/datatables/issues/179)
# 2.6.4 - 2025-09-17
### Fixed
fix: remove console.log in `Calculation.round()`
# 2.6.3 - 2025-09-07
### Fixed
fix: multiple criteria filter (AdvancedFilter) handles nullish values
# 2.6.2 - 2025-07-16
### Fixed
fix: Broken package with $lib import
https://github.com/vincjo/datatables/issues/169
# 2.6.1 - 2025-07-14
### Fixed
fix: make sure `init()` arg can be undefined in any case.
### Changed
- upgrade Svelte to the latest 5.36.0
# 2.6.0 - 2025-07-14
### Added
feat: add `init()` method to initialize a value in "search", "filter" and "sort".
# 2.5.0 - 2025-02-06
### Added
feat: add the median calculation.
```ts
const median = table.createCalculation('field').median()
```
# 2.4.0 - 2025-01-25
### Added
feat: (experimental) add `queries` to handle filtering inside nested array of objects.
```ts
table.createQuery('login_count')
.from(['groups', 'users'])
.where(check.isGreaterThan)
.set(1000)
// will check if "user.login_count" is greater than 1000 in users in groups
```
### Fixed
- `selectAll`: remove duplicate keys [#157](https://github.com/vincjo/datatables/issues/157)
# 2.3.1 - 2025-01-21
### Changed
- refactor: core functions has been organized into modules `/client/core/{value, entry, rows, check, field}`
### Fixed
- fix: add "index.js" to make type exports compatible with ES Module
# 2.3.0 - 2025-01-15
### Added
feat: add `search.recursive()` to handle search in tree data structures ([DOC](https://vincjo.fr/datatables/docs/client/search/recursive)).
### Changed
- breaking: search is not recursive by default anymore [#152](https://github.com/vincjo/datatables/issues/152)
- upgrade Svelte to the latest 5.18.0
# 2.2.0 - 2024-12-05
### Added
feat: add `table.clearSort()` method [#150](https://github.com/vincjo/datatables/issues/150)
# 2.1.0 - 2024-11-26
### Fixed
- export type AdvancedFilterBuilder [#145](https://github.com/vincjo/datatables/issues/145)
- use random string instead of `crypto.randomUUID()` [PR #147](https://github.com/vincjo/datatables/pull/147)
# 2.0.5 - 2024-10-24
### Fixed
- fix: `state_unsafe_mutation` error. currentPage is mutated in setters instead: filter.set() / search.set(). [#128](https://github.com/vincjo/datatables/issues/128) [#138](https://github.com/vincjo/datatables/issues/138)
# 2.0.4 - 2024-10-22
### Fixed
- fix: (legacy) remove self closing tags
# 2.0.3 - 2024-10-22
### Changed
- docs: stay on the same page after switch between client-side and server-side navigation
### Fixed
- fix: improve perf in the data filtering function
- fix: prefer unknown type instead of enumerating primitives
# 2.0.2 - 2024-10-21
### Fixed
- fix: remove runes tag from npm publication
# 2.0.1 - 2024-10-21
### Fixed
- fix: `table.select(value: unknown)` select arg type is *unknown* instead of `T[keyof T]`
# 2.0.0 - 2024-10-21
- published major release
# 2.0.0-runes.46 - 2024-10-21
### Fixed
- fix: `EventDispatcher`
# 2.0.0-runes.45 - 2024-10-16
### Fixed
- fix: `clearSearch` maximum call stack exceeded
# 2.0.0-runes.44 - 2024-10-14
### Added
- export type SearchBuilder, CSVBuilder, FilterBuilder, CalculationBuilder...
- added RecordFilter class
### Changed
- feat: handle scrollTop status in setRows method
- fix: replace structureCloned with $state.snapshot()
# 2.0.0-runes.{40,41,42,43} - 2024-10-06
### Fixed
- back to `:global()` style: css import is not working as expected after packaging.
# 2.0.0-runes.39 - 2024-10-06
### Added
- added `TableHandlerInterface` to improve shared components type.
### Changed
- update dependencies: svelte-next.262
- breaking: `table.createCalcultaion().distinct()` parameter is now: `distinct({ sort: [field, direction] })`
### Fixed
- remove dupplicate builder: `ColumnViewBuilders`
- remove dupplicate type definition: `Row`, `ColumnView`, `Internationalization`, `Field`
- use generic `T` type in `AbstractTableHandler` instead of `Row` (server-side pagination).
- distinct values has a default order in addition to sort param
- fix `$$Generic<Row>` type for server-side pagination
- fix `isAllSelected` when `selectScope` = 'all'
# 2.0.0-runes.38 - 2024-10-04
### Fixed
- CSS import in `<Datatable>` component.
# 2.0.0-runes.37 - 2024-09-29
### Added
- feat: make selectBy parameter type of `Field<Row>` so it can combine multiple fields or use a nested property as identifier
- docs: added a [migration guide](https://github.com/vincjo/datatables/blob/runes/MIGRATION.md)
### Changed
- update dependencies: svelte-next.260
### Fixed
- added type="button" in `<Pagination>` and `<RowCount/>` components, including the legacy part.
# 2.0.0-runes.{33,34,35,36} - 2024-09-16
### Added
- feat: add headless option for Datatable component
- feat: use @import instead of :global() in Datatable component
### Changed
- update dependencies: svelte-next.257
# 2.0.0-runes.32 - 2024-09-16
### Changed
- update dependencies: svelte-next.246
# 2.0.0-runes.31 - 2024-07-25
### Changed
- rename type `ViewColumn` to `ColumnView`
### Fixed
- improve types
- fix criterion value type
# 2.0.0-runes.30 - 2024-07-01
### Fixed
- client-side: refactor / improve types
# 2.0.0-runes.29 - 2024-06-30
### Fixed
- client-side: untrack sort and event dispatcher when rows are updated.
# 2.0.0-runes.28 - 2024-06-30
### Changed
- client-side *(breaking)*: sort params for distinct values is now an object `{ field: 'value' | 'count', direction: 'asc' | 'desc' }` instead of an array
- client-side: use crypto UUID instead of js random string
- client-side: improve types
# 2.0.0-runes.27 - 2024-06-29
### Added
- client-side: regular expression search takes scope parameter into account
# 2.0.0-runes.26 - 2024-06-27
### Added
- server-side: added a `table.isLoading` state which is true while `invalidate()` method runs
# 2.0.0-runes.25 - 2024-06-26
### Fixed
- server-side: column filtering
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Contributions are welcome.
1. Fork the project
2. ⚠️ Create your feature branch `git checkout -b myfeature`
3. Commit your changes `git commit -m 'feat: my feature'`
4. Push to the branch `git push origin myfeature`
5. Open a Pull Request
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 vincjo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">
<img align="center" src="./static/logo.svg" alt="logo" width="172"/>
<p align="center">
<h1 align="center" style="font-size:32px;margin:0;border:none;">svelte simple datatables</h1>
<p style="color:#eee">A powerful toolkit for building datatable components.</p>
<img src="https://img.shields.io/badge/svelte-runes-v2?color=%23c2185b" alt="any text"/>
<img src="https://img.shields.io/github/license/vincjo/datatables?color=c2185b" alt="last commit"/>
</p>
</div>
# Docs
Streamline your data workflow with a robust API providing advanced features while reducing code complexity.
:globe_with_meridians: **[vincjo.fr/datatables](https://vincjo.fr/datatables)**
# Install
```apache
npm i -D @vincjo/datatables
```
# Smooth transition from v1 to v2
In order to make the migration process a little easier, v1 is embed in “legacy” namespace so you will have the opportunity to upgrade your components progressively by simply modifying imports.
```diff
- @vincjo/datatables
+ @vincjo/datatables/legacy
- @vincjo/datatables/remote
+ @vincjo/datatables/legacy/remote
```
<br>
# Sample code
```svelte
<script lang="ts">
import { TableHandler } from '@vincjo/datatables'
import { someData } from './data'
const table = new TableHandler(someData, { rowsPerPage: 50 })
</script>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<tbody>
{#each table.rows as row}
<tr>
<td>{row.first_name}</td>
<td>{row.last_name}</td>
</tr>
{/each}
</tbody>
</table>
```
================================================
FILE: datatables.code-workspace
================================================
{
"folders": [
{ "path": "./" },
{ "path": "../autodoc"}
],
"settings": {
"restoreTerminals.terminals": [
{
"profile": "Git Bash",
"splitTerminals": [{
"name": "API",
"commands": ["cd ../autodoc && npm run dev"],
}],
}, {
"profile": "Git Bash",
"splitTerminals": [{
"name": "DEV",
"commands": ["npm run dev"],
}],
}, {
"profile": "Git Bash",
"splitTerminals": [{
"name": "Git bash",
"commands": ["cd ./"],
}],
}
],
"terminal.integrated.persistentSessionReviveProcess": "never"
}
}
================================================
FILE: ecosystem.config.cjs
================================================
module.exports = {
apps: [
{
name: 'datatables',
script: './build/index.js',
watch: false,
max_restarts: 10,
env: {
NODE_ENV: 'production',
PORT: 3010
}
}
],
deploy: {
production: {
user: 'vincjo',
host: ['vincjo.fr -p 625'],
ref: 'origin/main',
repo: 'git@github.com:vincjo/datatables.git',
path: '/home/vincjo/www/datatables',
'post-deploy':
'npm install --force && npm run build && pm2 startOrRestart ecosystem.config.cjs --env production'
}
}
}
================================================
FILE: mdsvex.config.js
================================================
import { defineMDSveXConfig as defineConfig } from 'mdsvex'
const config = defineConfig({
extensions: ['.svelte.md', '.md', '.svx'],
smartypants: {
dashes: 'oldschool'
},
remarkPlugins: [],
rehypePlugins: []
})
export default config
================================================
FILE: package.json
================================================
{
"name": "@vincjo/datatables",
"version": "2.8.0",
"keywords": [
"svelte sveltejs table tables datatable datatables filter headless sort selection lazy-loading"
],
"description": "A powerful toolkit for building datatable components",
"repository": {
"type": "git",
"url": "git+https://github.com/vincjo/datatables.git"
},
"author": "vincjo",
"contributors": [
"bn-l",
"jst-r"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/vincjo/datatables/issues"
},
"homepage": "https://vincjo.fr/datatables",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"package": "svelte-kit sync && svelte-package",
"publish:runes": "svelte-kit sync && svelte-package && npm publish",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --plugin-search-dir . --write .",
"deploy": "pm2 deploy ecosystem.config.cjs production",
"deploy:setup": "pm2 deploy ecosystem.config.cjs production setup"
},
"devDependencies": {
"@rollup/plugin-replace": "^6.0.2",
"@sveltejs/adapter-node": "^5.2.13",
"@sveltejs/kit": "^2.23.0",
"@sveltejs/package": "=2.3.7",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^24.0.13",
"dotenv": "^17.2.0",
"gros": "^1.1.3",
"mdsvex": "^0.12.6",
"prism-svelte": "^0.5.0",
"prismjs": "^1.30.0",
"svelte": "5.36.0",
"svelte-check": "^4.2.2",
"svelte-dnd-action": "^0.9.63",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"vite": "^7.0.4"
},
"peerDependencies": {
"svelte": "^5.16.0"
},
"type": "module",
"files": [
"dist"
],
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"css": "./dist/*.css",
"svelte": "./dist/index.js",
"default": "./dist/index.js"
},
"./server": {
"types": "./dist/server/index.d.ts",
"svelte": "./dist/server/index.js",
"default": "./dist/server/index.js"
},
"./legacy": {
"types": "./dist/legacy/index.d.ts",
"svelte": "./dist/legacy/index.js",
"default": "./dist/legacy/index.js"
},
"./legacy/remote": {
"types": "./dist/legacy/remote/index.d.ts",
"svelte": "./dist/legacy/remote/index.js",
"default": "./dist/legacy/remote/index.js"
}
}
}
================================================
FILE: src/app.d.ts
================================================
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}
================================================
FILE: src/app.html
================================================
<!DOCTYPE html>
<html lang="en" data-theme="" data-mode="">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="stylesheet" href="%sveltekit.assets%/gros-theme.css" />
<link rel="stylesheet" href="%sveltekit.assets%/gros.css" />
<link rel="stylesheet" href="%sveltekit.assets%/global.css" />
<link rel="stylesheet" href="%sveltekit.assets%/prism-dark.css" />
<link rel="stylesheet" href="%sveltekit.assets%/prism-light.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>svelte simple datatables</title>
<!-- <meta name="viewport" content="width=1024"> -->
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>
================================================
FILE: src/hooks.server.ts
================================================
import type { RequestEvent } from '@sveltejs/kit'
export const handle = async ({ event, resolve }) => {
const theme = event.cookies.get('siteTheme') ?? 'light'
const mode = getMode(event)
return resolve(event, {
transformPageChunk: ({ html }) => {
return html.replace(`data-theme=""`, `data-theme="${theme}"`)
.replace(`data-mode=""`, `data-mode="${mode}"`)
}
})
}
const getMode = (event: RequestEvent) => {
if (event.url.pathname.includes('/server')) {
return 'server'
}
else if (event.url.pathname.includes('/client')) {
return 'client'
}
return event.cookies.get('siteMode') ?? 'client'
}
================================================
FILE: src/lib/index.ts
================================================
export {
// class:
TableHandler,
RecordFilter,
// components:
Datatable,
Search,
RowsPerPage,
Th,
ThSort,
ThFilter,
Pagination,
RowCount,
// utils:
check,
// types:
type Field,
type Check,
type Internationalization,
type Row,
type TableParams,
type SortParams,
type TableHandlerInterface,
type AdvancedFilterBuilder,
type CalculationBuilder,
type CSVBuilder,
type FilterBuilder,
type RecordFilterBuilder,
type SearchBuilder,
type SortBuilder,
} from '$lib/src/client/index.js'
================================================
FILE: src/lib/legacy/index.ts
================================================
// Reexport your entry components here
import DataHandler from './local/DataHandler'
import Datatable from './local/Datatable.svelte'
import Th from './local/Th.svelte'
import ThFilter from './local/ThFilter.svelte'
import Pagination from './local/Pagination.svelte'
import RowCount from './local/RowCount.svelte'
import RowsPerPage from './local/RowsPerPage.svelte'
import Search from './local/Search.svelte'
import { check } from './local/Comparator'
import type { Internationalization, Row, Field, Comparator, Filter, Sort, Selectable, Order, FilterBy, OrderBy } from './local'
export { DataHandler, check, Datatable, Th, ThFilter, Pagination, RowCount, RowsPerPage, Search }
export type {
Internationalization,
Row,
Field,
Comparator,
Filter,
Sort,
// deprecated
Selectable,
Order,
OrderBy,
FilterBy,
}
================================================
FILE: src/lib/legacy/local/Comparator.ts
================================================
import type { Criterion } from '$lib/legacy/local'
export const check = {
isLike: (entry: any, value: any) => {
return stringify(entry).indexOf(stringify(value)) > -1
},
isNotLike: (entry: any, value: any) => {
return stringify(entry).indexOf(stringify(value)) === -1
},
startsWith: (entry: any, value: any) => {
return stringify(entry).startsWith(stringify(value))
},
endsWith: (entry: any, value: any) => {
return stringify(entry).endsWith(stringify(value))
},
isEqualTo: (entry: any, value: any) => {
return stringify(entry) === stringify(value)
},
isNotEqualTo: (entry: any, value: any) => {
return stringify(entry) !== stringify(value)
},
isGreaterThan: (entry: number, value: number) => {
if (isNull(entry)) return false
return entry > value
},
isGreaterThanOrEqualTo: (entry: number, value: number) => {
if (isNull(entry)) return false
return entry >= value
},
isLessThan: (entry: number, value: number) => {
if (isNull(entry)) return false
return entry < value
},
isLessThanOrEqualTo: (entry: number, value: number) => {
if (isNull(entry)) return false
return entry <= value
},
isBetween: (entry: number, value: [min: number, max: number]) => {
if (isNull(entry)) return false
const [min, max] = value
return entry >= min && entry <= max
},
isStrictlyBetween: (entry: number, value: [min: number, max: number]) => {
if (isNull(entry)) return false
const [min, max] = value
return entry > min && entry < max
},
isTrue: (entry: boolean, _: any) => {
return entry === true
},
isFalse: (entry: boolean, _: any) => {
return entry === false
},
isNull: (entry: null, _: any) => {
return entry === null || entry === undefined
},
isNotNull: (entry: any, _: any) => {
return entry === null || entry === undefined ? false : true
},
whereIn: (entry: any, values: Criterion[] = []) => {
if (isNull(entry)) return false
if (values.length === 0) return false
for(const { value, comparator } of values) {
if (comparator(entry, value)) {
return true
}
}
return false
},
/**
* @deprecated use "isLike" instead
* @since 1.12.7 2023-09-27
*/
contains: (entry: any, value: any) => {
return check.isLike(entry, value)
},
}
/* utils */
function stringify(value: string | number | boolean = null) {
return String(value)
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
}
function isNull(entry: any) {
if (entry === null || entry === undefined) return true
}
================================================
FILE: src/lib/legacy/local/Context.ts
================================================
import { writable, derived, type Writable, type Readable } from 'svelte/store'
import type { Filter, Sort, Comparator, Criterion, Field } from '$lib/legacy/local'
import type { Params } from '$lib/legacy/local/DataHandler'
import { isNull, parseField } from './utils'
import { check } from './Comparator'
import EventHandler from './handlers/EventHandler'
export default class Context<Row>
{
public event : EventHandler
public rowsPerPage : Writable<number | null>
public pageNumber : Writable<number>
public search : Writable<{ value?: string, scope?: (keyof Row)[] }>
public filters : Writable<Filter<Row>[]>
public filterCount : Readable<number>
public rawRows : Writable<Row[]>
public filteredRows : Readable<Row[]>
public pagedRows : Readable<Row[]>
public rowCount : Readable<{ total: number, start: number, end: number }>
public pages : Readable<number[]>
public pagesWithEllipsis : Readable<number[]>
public pageCount : Readable<number>
public sort : Writable<(Sort<Row>)>
public selected : Writable<(Row | Row[keyof Row])[]>
public selectScope : Writable<'all' | 'currentPage'>
public isAllSelected : Readable<boolean>
constructor(data: Row[], params: Params)
{
this.event = new EventHandler()
this.rowsPerPage = writable(params.rowsPerPage)
this.pageNumber = writable(1)
this.search = writable({})
this.filters = writable([])
this.filterCount = this.createFilterCount()
this.rawRows = writable(data)
this.filteredRows = this.createFilteredRows()
this.pagedRows = this.createPagedRows()
this.rowCount = this.createRowCount()
this.pages = this.createPages()
this.pagesWithEllipsis = this.createPagesWithEllipsis()
this.pageCount = this.createPageCount()
this.sort = writable({})
this.selected = writable([])
this.selectScope = writable('all')
this.isAllSelected = this.createIsAllSelected()
}
private createFilterCount()
{
return derived(this.filters, ($filters) => $filters.length)
}
private createFilteredRows()
{
return derived(
[this.rawRows, this.search, this.filters],
([$rawRows, $search, $filters]) => {
if ($search.value) {
$rawRows = $rawRows.filter((row) => {
const fields = $search.scope ?? Object.keys(row) as Field<Row>[]
const scope = fields.map((field: Field<Row>) => {
const { callback } = parseField(field)
return callback
})
return scope.some((callback) => {
return this.match(callback(row), $search.value)
})
})
this.pageNumber.set(1)
this.selected.set([])
this.event.trigger('change')
}
if ($filters.length > 0) {
$filters.forEach((filter) => {
return ($rawRows = $rawRows.filter((row) => {
const entry = filter.callback(row)
return this.match(entry, filter.value, filter.comparator)
}))
})
this.pageNumber.set(1)
this.selected.set([])
this.event.trigger('change')
}
return $rawRows
}
)
}
private match(entry: Row[keyof Row], value: string|number|boolean|symbol|number[]|Criterion[], compare: Comparator<Row> = null)
{
if (isNull(value)) {
return true
}
if (!entry && compare) {
return compare(entry, value)
}
if (!entry) return check.isLike(entry, value)
else if (typeof entry === 'object') {
return Object.keys(entry).some((k) => {
return this.match(entry[k], value, compare)
})
}
if (!compare) return check.isLike(entry, value)
return compare(entry, value)
}
private createPagedRows()
{
return derived(
[this.filteredRows, this.rowsPerPage, this.pageNumber],
([$filteredRows, $rowsPerPage, $pageNumber]) => {
if (!$rowsPerPage) {
return $filteredRows
}
return $filteredRows.slice(
($pageNumber - 1) * $rowsPerPage,
$pageNumber * $rowsPerPage
)
}
)
}
private createRowCount()
{
return derived(
[this.filteredRows, this.pageNumber, this.rowsPerPage],
([$filteredRows, $pageNumber, $rowsPerPage]) => {
const total = $filteredRows.length
if (!$rowsPerPage) {
return { total: total, start: 1, end: total }
}
return {
total: total,
start: $pageNumber * $rowsPerPage - $rowsPerPage + 1,
end: Math.min($pageNumber * $rowsPerPage, $filteredRows.length)
}
}
)
}
private createPages()
{
return derived([this.rowsPerPage, this.filteredRows], ([$rowsPerPage, $filteredRows]) => {
if (!$rowsPerPage) {
return [1]
}
const pages = Array.from(Array(Math.ceil($filteredRows.length / $rowsPerPage)))
return pages.map((_, i) => i + 1 )
})
}
private createPagesWithEllipsis()
{
return derived([this.pages, this.pageNumber], ([$pages, $pageNumber]) => {
if ($pages.length <= 7) {
return $pages
}
const ellipse = null
const firstPage = 1
const lastPage = $pages.length
if ($pageNumber <= 4) {
return [
...$pages.slice(0, 5),
ellipse,
lastPage
]
} else if ($pageNumber < $pages.length - 3) {
return [
firstPage,
ellipse,
...$pages.slice($pageNumber - 2, $pageNumber + 1),
ellipse,
lastPage
]
} else {
return [
firstPage,
ellipse,
...$pages.slice($pages.length - 5,
$pages.length)
]
}
})
}
private createPageCount()
{
return derived(this.pages, ($pages) => {
return $pages.length
})
}
private createIsAllSelected()
{
return derived(
[this.selected, this.pagedRows, this.filteredRows, this.selectScope],
([$selected, $pagedRows, $filteredRows, $selectScope]) => {
const rowCount = $selectScope === 'currentPage' ? $pagedRows.length : $filteredRows.length
if (rowCount === $selected.length && rowCount !== 0) {
return true
}
return false
}
)
}
}
================================================
FILE: src/lib/legacy/local/DataHandler.ts
================================================
import Context from './Context'
import SortHandler from './handlers/SortHandler'
import SelectHandler from './handlers/SelectHandler'
import PageHandler from './handlers/PageHandler'
import SearchHandler from './handlers/SearchHandler'
import FilterHandler from './handlers/FilterHandler'
import FilterHelper from './helpers/FilterHelper'
import AdvancedFilterHelper from './helpers/AdvancedFilterHelper'
import CalculationHelper from './helpers/CalculationHelper'
import type { Readable, Writable } from 'svelte/store'
import type { Internationalization, Row, Field, Comparator } from '$lib/legacy/local'
export type Params = { rowsPerPage?: number, i18n?: Internationalization }
export default class DataHandler<T extends Row = any>
{
private context : Context<T>
private sortHandler : SortHandler<T>
private selectHandler : SelectHandler<T>
private pageHandler : PageHandler<T>
private searchHandler : SearchHandler<T>
private filterHandler : FilterHandler<T>
public i18n : Internationalization
constructor(data: T[] = [], params: Params = { rowsPerPage: null })
{
this.i18n = this.translate(params.i18n)
this.context = new Context(data, params)
this.sortHandler = new SortHandler(this.context)
this.selectHandler = new SelectHandler(this.context)
this.pageHandler = new PageHandler(this.context)
this.searchHandler = new SearchHandler(this.context)
this.filterHandler = new FilterHandler(this.context)
}
public setRows(data: T[])
{
this.context.rawRows.set(data)
this.context.event.trigger('change')
this.applySort()
}
public getRows(): Readable<T[]>
{
return this.context.pagedRows
}
public getAllRows(): Readable<T[]>
{
return this.context.filteredRows
}
public getRowCount(): Readable<{ total: number, start: number, end: number }>
{
return this.context.rowCount
}
public getRowsPerPage(): Writable<number | null>
{
return this.context.rowsPerPage
}
public getPages(param = { ellipsis: false }): Readable<number[]>
{
if (param.ellipsis) {
return this.context.pagesWithEllipsis
}
return this.context.pages
}
public getPageCount(): Readable<number>
{
return this.context.pageCount
}
public getPageNumber(): Readable<number>
{
return this.context.pageNumber
}
public setPage(value: number | 'previous' | 'next'): void
{
switch (value) {
case 'previous' : return this.pageHandler.previous()
case 'next' : return this.pageHandler.next()
default : return this.pageHandler.goto(value as number)
}
}
public search(value: string, scope: Field<T>[] = null)
{
this.searchHandler.set(value, scope)
}
public clearSearch()
{
this.searchHandler.clear()
}
public sort(orderBy: Field<T>, identifier?: string)
{
this.setPage(1)
this.sortHandler.set(orderBy, identifier)
}
public sortAsc(orderBy: Field<T>, identifier?: string)
{
this.setPage(1)
this.sortHandler.asc(orderBy, identifier)
}
public sortDesc(orderBy: Field<T>, identifier?: string)
{
this.setPage(1)
this.sortHandler.desc(orderBy, identifier)
}
public getSort(): Writable<{ identifier?: string, direction?: 'asc' | 'desc' }>
{
return this.context.sort
}
public applySort( params: { orderBy: Field<T>, direction?: 'asc' | 'desc' } = null )
{
this.sortHandler.apply(params)
}
public defineSort(orderBy: Field<T>, direction?: 'asc' | 'desc')
{
this.sortHandler.define(orderBy, direction)
}
public clearSort()
{
this.sortHandler.clear()
}
public filter( value: string | number | null | undefined | boolean | number[], filterBy: Field<T>, comparator: Comparator<T> = null )
{
this.filterHandler.set(value, filterBy, comparator)
}
public getFilters()
{
return this.filterHandler.get()
}
public createFilter( filterBy: Field<T>, comparator?: Comparator<T> )
{
return new FilterHelper( this.filterHandler, filterBy, comparator )
}
public createAdvancedFilter(filterBy: Field<T>)
{
return new AdvancedFilterHelper(this.filterHandler, filterBy)
}
public getFilterCount(): Readable<number>
{
return this.context.filterCount
}
public clearFilters(): void
{
this.filterHandler.clear()
}
public select(value: T | T[keyof T])
{
this.selectHandler.set(value)
}
public getSelected()
{
return this.context.selected
}
public selectAll(params: { selectBy?: keyof T; scope?: 'all' | 'currentPage' } = {}): void
{
this.context.selectScope.set(params.scope === 'currentPage' ? 'currentPage' : 'all')
this.selectHandler.all(params.selectBy ?? null)
}
public isAllSelected(): Readable<boolean>
{
return this.context.isAllSelected
}
public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: () => void)
{
this.context.event.add(event, callback)
}
public createCalculation(field: Field<T>, param: { precision: number } = null)
{
return new CalculationHelper(this.context, field, { precision: param?.precision ?? 2 })
}
public translate(i18n: Internationalization): Internationalization
{
return {
...{
search: 'Search...',
show: 'Show',
entries: 'entries',
filter: 'Filter',
rowCount: 'Showing {start} to {end} of {total} entries',
noRows: 'No entries found',
previous: 'Previous',
next: 'Next'
},
...i18n
}
}
/**
* @deprecated use setRows() instead
* @since v0.9.99 2023-01-16
*/
public update(data: any[]): void
{
console.log(
'%c%s',
'color:#e65100;background:#fff3e0;font-size:12px;border-radius:4px;padding:4px;text-align:center;',
`DataHandler.update(data) method is deprecated. Please use DataHandler.setRows(data) instead`
)
this.context.rawRows.set(data)
}
/**
* @deprecated use applySort() instead
* @since v1.11.0 2023-07-11
*/
public applySorting( params: { orderBy: Field<T>, direction?: 'asc' | 'desc' } = null )
{
this.applySort(params)
}
/**
* @deprecated use getSort() instead
* @since v1.11.0 2023-07-11
*/
public getSorted()
{
return this.getSort()
}
public getTriggerChange(): Writable<number>
{
return this.context.event.triggerChange
}
}
================================================
FILE: src/lib/legacy/local/Datatable.svelte
================================================
<script lang="ts">
import { type DataHandler, type Row, Search, RowsPerPage, RowCount, Pagination } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let search = true
export let rowsPerPage = true
export let rowCount = true
export let pagination = true
let element: HTMLElement
let clientWidth = 1000
const height = (search || rowsPerPage ? 48 : 8) + (rowCount || pagination ? 48 : 8)
handler.on('change', () => {
if (element) element.scrollTop = 0
})
</script>
<section bind:clientWidth class={$$props.class ?? ''}>
<header class:container={search || rowsPerPage}>
{#if search}
<Search {handler} />
{/if}
{#if rowsPerPage}
<RowsPerPage {handler} small={clientWidth < 600} />
{/if}
</header>
<article bind:this={element} style="height:calc(100% - {height}px)">
<slot />
</article>
<footer class:container={rowCount || pagination}>
{#if rowCount}
<RowCount {handler} small={clientWidth < 600} />
{/if}
{#if pagination}
<Pagination {handler} small={clientWidth < 600} />
{/if}
</footer>
</section>
<style>
section {
height: 100%;
}
section :global(table) {
border-collapse: separate;
border-spacing: 0;
width: 100%;
}
section :global(thead) {
position: sticky;
inset-block-start: 0;
z-index: 1;
}
header,
footer {
min-height: 8px;
padding: 0 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
header.container,
footer.container {
height: 48px;
}
footer {
border-top: 1px solid #e0e0e0;
}
article {
position: relative;
/* height:calc(100% - 96px); */
overflow: auto;
scrollbar-width: thin;
}
article::-webkit-scrollbar {
width: 6px;
height: 6px;
}
article::-webkit-scrollbar-track {
background: #f5f5f5;
}
article::-webkit-scrollbar-thumb {
background: #c2c2c2;
}
article::-webkit-scrollbar-thumb:hover {
background: #9e9e9e;
}
</style>
================================================
FILE: src/lib/legacy/local/Pagination.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
const pageNumber = handler.getPageNumber()
const pageCount = handler.getPageCount()
const pages = handler.getPages({ ellipsis: true })
</script>
<section class={$$props.class ?? ''}>
{#if small}
<button type="button"
class="small"
class:disabled={$pageNumber === 1}
on:click={() => handler.setPage(1)}
>
❬❬
</button>
<button type="button"
class:disabled={$pageNumber === 1}
on:click={() => handler.setPage('previous')}
>
❮
</button>
<button type="button"
class:disabled={$pageNumber === $pageCount}
on:click={() => handler.setPage('next')}
>
❯
</button>
<button type="button"
class="small"
class:disabled={$pageNumber === $pageCount}
on:click={() => handler.setPage($pageCount)}
>
❭❭
</button>
{:else}
<button type="button"
class:disabled={$pageNumber === 1}
on:click={() => handler.setPage('previous')}
>
{@html handler.i18n.previous}
</button>
{#each $pages as page}
<button type="button"
class:active={$pageNumber === page}
class:ellipse={page === null}
on:click={() => handler.setPage(page)}
>
{page ?? '...'}
</button>
{/each}
<button type="button"
class:disabled={$pageNumber === $pageCount}
on:click={() => handler.setPage('next')}
>
{@html handler.i18n.next}
</button>
{/if}
</section>
<style>
section {
display: flex;
}
button {
background: inherit;
height: 32px;
width: 32px;
color: #616161;
cursor: pointer;
font-size: 13px;
margin: 0;
padding: 0;
transition: all, 0.2s;
line-height: 32px;
border: 1px solid #e0e0e0;
border-right: none;
border-radius: 0;
outline: none;
}
button:first-child {
border-radius: 4px 0 0 4px;
}
button:last-child {
border-right: 1px solid #e0e0e0;
border-radius: 0 4px 4px 0;
}
button:first-child:not(.small),
button:last-child:not(.small) {
min-width: 72px;
}
button:not(.active):hover {
background: #eee;
}
button.ellipse:hover {
background: inherit;
cursor: default;
}
button.active {
background: #eee;
font-weight: bold;
cursor: default;
}
button.disabled:hover {
background: inherit;
cursor: default;
}
</style>
================================================
FILE: src/lib/legacy/local/RowCount.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
const rowCount = handler.getRowCount()
</script>
<aside class={$$props.class ?? ''}>
{#if small}
{#if $rowCount.total > 0}
<b>{$rowCount.start}</b>-
<b>{$rowCount.end}</b>/
<b>{$rowCount.total}</b>
{:else}
{handler.i18n.noRows}
{/if}
{:else if $rowCount.total > 0}
{@html handler.i18n.rowCount
.replace('{start}', `<b>${$rowCount.start}</b>`)
.replace('{end}', `<b>${$rowCount.end}</b>`)
.replace('{total}', `<b>${$rowCount.total}</b>`)}
{:else}
{handler.i18n.noRows}
{/if}
</aside>
<style>
aside {
color: #616161;
line-height: 32px;
font-size: 14px;
}
</style>
================================================
FILE: src/lib/legacy/local/RowsPerPage.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
const rowsPerPage = handler.getRowsPerPage()
const options = [5, 10, 20, 50, 100]
</script>
<aside class={$$props.class ?? ''}>
{#if !small}
<span>{handler.i18n.show}</span>
{/if}
<select bind:value={$rowsPerPage} on:change={() => handler.setPage(1)}>
{#each options as option}
<option value={option}>
{option}
</option>
{/each}
</select>
{#if !small}
<span>{handler.i18n.entries}</span>
{/if}
</aside>
<style>
aside {
display: flex;
justify-content: flex-start;
align-items: center;
height: 32px;
color: #757575;
}
select {
margin: 0 4px;
}
</style>
================================================
FILE: src/lib/legacy/local/Search.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
let value = ''
handler.on('clearSearch', () => value = '')
</script>
<input
class={$$props.class ?? ''}
bind:value
placeholder={handler.i18n.search}
spellcheck="false"
on:input={() => handler.search(value)}
/>
<style>
input {
border: 1px solid #e0e0e0;
border-radius: 4px;
outline: none;
padding: 0 8px;
line-height: 24px;
margin: 0;
height: 24px;
background: transparent;
width: 40%;
max-width: 176px;
min-width: 96px;
transition: all, 0.1s;
}
input:focus {
border: 1px solid #bdbdbd;
}
input::placeholder {
color: #9e9e9e;
line-height: 24px;
}
</style>
================================================
FILE: src/lib/legacy/local/Th.svelte
================================================
<script lang="ts">
import type { DataHandler, Field, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let orderBy: Field<T>
export let identifier = orderBy?.toString()
export let align: 'left' | 'right' | 'center' = 'left'
export let rowSpan: number = 1
const sort = handler.getSort()
</script>
<th
on:click={() => handler.sort(orderBy, identifier)}
class:sortable={orderBy}
class:active={$sort.identifier === identifier}
class={$$props.class ?? ''}
rowspan={rowSpan}
>
<div
class="flex"
style:justify-content={align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center'}
>
<strong>
<slot />
</strong>
<span class:asc={$sort.direction === 'asc'} class:desc={$sort.direction === 'desc'}></span>
</div>
</th>
<style>
th {
background: inherit;
padding: 8px 20px;
white-space: nowrap;
font-size: 13px;
user-select: none;
border-bottom: 1px solid #e0e0e0;
}
th.sortable {
cursor: pointer;
}
th strong {
white-space: pre-wrap;
font-size: 13.5px;
line-height: 16px;
}
th.sortable div.flex {
padding: 0;
display: flex;
align-items: center;
height: 100%;
}
th.sortable span {
padding-left: 8px;
}
th.sortable span:before,
th.sortable span:after {
border: 4px solid transparent;
content: '';
display: block;
height: 0;
width: 0;
}
th.sortable span:before {
border-bottom-color: #e0e0e0;
margin-top: 2px;
}
th.sortable span:after {
border-top-color: #e0e0e0;
margin-top: 2px;
}
th.active.sortable span.asc:before {
border-bottom-color: #9e9e9e;
}
th.active.sortable span.desc:after {
border-top-color: #9e9e9e;
}
th:not(.sortable) span {
visibility: hidden;
}
</style>
================================================
FILE: src/lib/legacy/local/ThFilter.svelte
================================================
<script lang="ts">
import type { DataHandler, Field, Comparator, Row } from '$lib/legacy/local'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let filterBy: Field<T>
export let align: 'left' | 'right' | 'center' = 'left'
export let comparator: Comparator<T> = null
let value: string = ''
handler.on('clearFilters', () => value = '')
</script>
<th class={$$props.class ?? ''}>
<input
style:text-align={align}
type="text"
placeholder={handler.i18n.filter}
spellcheck="false"
bind:value
on:input={() => handler.filter(value, filterBy, comparator)}
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
width: 100%;
height: 24px;
border: none;
text-align: left;
padding: 0 20px;
background: inherit;
outline: none;
border-radius: 0;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
}
input::placeholder {
color: #bdbdbd;
font-style: italic;
font-size: 13px;
}
input:focus {
outline: none;
border: none;
}
</style>
================================================
FILE: src/lib/legacy/local/handlers/EventHandler.ts
================================================
import { writable } from 'svelte/store'
export default class EventHandler
{
private events = {
change : [] as (() => void)[],
clearFilters: [] as (() => void)[],
clearSearch : [] as (() => void)[]
}
public triggerChange = writable(0) // legacy
public add(event: keyof EventHandler['events'], callback: () => void)
{
this.events[event].push(callback)
}
public trigger(event: keyof EventHandler['events'])
{
for (const callback of this.events[event]) {
callback()
}
/* legacy: support for triggerChange store */
if (event === 'change') {
this.triggerChange.update((store) => { return store + 1 })
}
}
}
================================================
FILE: src/lib/legacy/local/handlers/FilterHandler.ts
================================================
import type { Filter, Field, Comparator, EventHandler, Criterion } from '$lib/legacy/local'
import { isNotNull } from '../utils'
import type Context from '$lib/legacy/local/Context'
import { type Writable, type Readable, derived } from 'svelte/store'
import { parseField } from '$lib/legacy/local/utils'
// import { check } from '$lib/legacy/local/Comparator'
type Value = string | number | null | undefined | boolean | number[] | Criterion[]
// type Collection<Row> = {
// value: unknown
// filterBy: Field<Row>
// set: (value: unknown, comparator: Comparator<Row>) => void,
// clear: () => void
// }
export default class FilterHandler<Row>
{
protected filters: Writable<Filter<Row>[]>
protected event: EventHandler
private collection: Readable<{ value: unknown, filterBy: Field<Row>, check: string }[]>
constructor(context: Context<Row>)
{
this.filters = context.filters
this.event = context.event
}
public set(value: Value, filterBy: Field<Row>, comparator: Comparator<Row> = null, name?: string )
{
const { callback, identifier, key } = parseField(filterBy, name)
const filter = { value, identifier, callback, comparator, key }
this.filters.update((store) => {
store = store.filter((item) => item.identifier !== identifier)
if (isNotNull(value)) {
store.push(filter)
}
return store
})
}
public clear()
{
this.filters.set([])
this.event.trigger('change')
this.event.trigger('clearFilters')
}
public get()
{
if (this.collection) {
return this.collection
}
this.collection = this.createCollection()
return this.collection
}
private createCollection()
{
return derived(this.filters, ($filters) => {
return $filters.map( ({ value, callback, key, comparator }) => {
const filterBy = key as Field<Row> ?? callback
return {
value,
filterBy,
check: comparator ? comparator.name : 'isLike'
// set: (value: Value, comparator: Comparator<Row> = check.isLike) => {
// this.set(value, filterBy, comparator)
// },
// clear: () => {
// this.set(undefined, filterBy)
// }
}
})
})
}
}
================================================
FILE: src/lib/legacy/local/handlers/PageHandler.ts
================================================
import type Context from '$lib/legacy/local/Context'
import { type Writable, type Readable, get } from 'svelte/store'
import type { EventHandler } from '$lib/legacy/local'
export default class PageHandler<Row>
{
private pageNumber : Writable<number>
private rowCount : Readable<{ total: number, start: number, end: number }>
private rowsPerPage : Writable<number | null>
private event : EventHandler
constructor(context: Context<Row>)
{
this.pageNumber = context.pageNumber
this.rowCount = context.rowCount
this.rowsPerPage = context.rowsPerPage
this.event = context.event
}
public goto(number: number)
{
this.pageNumber.update((store) => {
const rowsPerPage = get(this.rowsPerPage)
if (rowsPerPage) {
const total = get(this.rowCount).total
if (number >= 1 && number <= Math.ceil(total / rowsPerPage)) {
store = number
this.event.trigger('change')
}
}
return store
})
}
public previous()
{
const number = get(this.pageNumber) - 1
this.goto(number)
}
public next()
{
const number = get(this.pageNumber) + 1
this.goto(number)
}
}
================================================
FILE: src/lib/legacy/local/handlers/SearchHandler.ts
================================================
import type Context from '$lib/legacy/local/Context'
import type { Writable } from 'svelte/store'
import type { EventHandler, Field } from '$lib/legacy/local'
export default class SearchHandler<Row>
{
private search : Writable<{ value?: string, scope?: Field<Row>[] }>
private event : EventHandler
constructor(context: Context<Row>)
{
this.search = context.search
this.event = context.event
}
public set(value: string, scope: Field<Row>[] = null)
{
this.search.update((store) => {
store = {
value: value ?? '',
scope: scope ?? null,
}
return store
})
}
public clear()
{
this.search.set({ value: null, scope: null })
this.event.trigger('change')
this.event.trigger('clearSearch')
}
}
================================================
FILE: src/lib/legacy/local/handlers/SelectHandler.ts
================================================
import type Context from '$lib/legacy/local/Context'
import { type Writable, type Readable, get } from 'svelte/store'
import type EventHandler from './EventHandler'
export default class SelectHandler<Row>
{
private filteredRows : Readable<Row[]>
private pagedRows : Readable<Row[]>
private selected : Writable<(Row | Row[keyof Row])[]>
private scope : Writable<'currentPage' | 'all'>
private isAllSelected : Readable<boolean>
private event : EventHandler
constructor(context: Context<Row>)
{
this.filteredRows = context.filteredRows
this.pagedRows = context.pagedRows
this.selected = context.selected
this.scope = context.selectScope
this.isAllSelected = context.isAllSelected
this.event = context.event
}
public set(value: Row[keyof Row] | Row)
{
const selected = get(this.selected)
if (selected.includes(value)) {
this.selected.set(selected.filter((item) => item !== value))
} else {
this.selected.set([value, ...selected])
}
}
public all(selectBy: keyof Row = null)
{
const isAllSelected = get(this.isAllSelected)
if (isAllSelected) {
return this.clear()
}
const scope = get(this.scope)
const rows = scope === 'currentPage' ? get(this.pagedRows) : get(this.filteredRows)
if (scope === 'currentPage') {
this.event.add('change', () => this.clear())
}
if (selectBy) {
this.selected.set( rows.map((row) => row[selectBy]) )
} else {
this.selected.set(rows)
}
}
public clear()
{
this.selected.set([])
}
}
================================================
FILE: src/lib/legacy/local/handlers/SortHandler.ts
================================================
import type Context from '$lib/legacy/local/Context'
import type { Sort, Field, EventHandler } from '$lib/legacy/local'
import { type Writable, get } from 'svelte/store'
import { parseField } from '$lib/legacy/local/utils'
export default class SortHandler<Row>
{
private rawRows : Writable<Row[]>
private event : EventHandler
private sort : Writable<(Sort<Row>)>
private backup : Sort<Row>[]
constructor(context: Context<Row>)
{
this.rawRows = context.rawRows
this.event = context.event
this.sort = context.sort
this.backup = []
}
public set(orderBy: Field<Row> = null, uid?: string)
{
if (!orderBy) return
const sort = get(this.sort)
const { identifier } = parseField(orderBy, uid)
if (sort.identifier !== identifier) {
this.sort.update((store) => (store.direction = null))
}
if (sort.direction === null || sort.direction === 'desc') {
this.asc(orderBy, uid)
}
else if (sort.direction === 'asc') {
this.desc(orderBy, uid)
}
}
public asc(orderBy: Field<Row>, uid?: string)
{
if (!orderBy) return
const { identifier, callback, key } = parseField(orderBy, uid)
this.sort.set({ identifier, callback, direction: 'asc', key })
this.rawRows.update((store) => {
store.sort((x, y) => {
const [a, b] = [callback(x), callback(y)]
if (a === b) return 0
if (a === null) return 1
if (b === null) return -1
if (typeof a === 'boolean') return a === false ? 1 : -1
if (typeof a === 'string') return a.localeCompare(b as string)
if (typeof a === 'number') return a - (b as number)
if (typeof a === 'object') return JSON.stringify(a).localeCompare(JSON.stringify(b))
else return String(a).localeCompare(String(b))
})
return store
})
this.log({ identifier, callback, direction: 'asc' })
this.event.trigger('change')
}
public desc(orderBy: Field<Row>, uid?: string)
{
if (!orderBy) return
const { identifier, callback, key } = parseField(orderBy, uid)
this.sort.set({ identifier, callback, direction: 'desc', key })
this.rawRows.update((store) => {
store.sort((x, y) => {
const [a, b] = [callback(x), callback(y)]
if (a === b) return 0
if (a === null) return 1
if (b === null) return -1
if (typeof b === 'boolean') return b === false ? 1 : -1
if (typeof b === 'string') return b.localeCompare(a as string)
if (typeof b === 'number') return b - (a as number)
if (typeof b === 'object') return JSON.stringify(b).localeCompare(JSON.stringify(a))
else return String(b).localeCompare(String(a))
})
return store
})
this.log({ identifier, callback, direction: 'desc' })
this.event.trigger('change')
}
public apply(params: { orderBy: Field<Row>, direction?: 'asc' | 'desc' } = null)
{
if (params) {
switch (params.direction) {
case 'asc' : return this.asc(params.orderBy)
case 'desc': return this.desc(params.orderBy)
default : return this.set(params.orderBy)
}
}
else {
this.restore()
}
}
public clear()
{
this.backup = []
this.sort.set({})
}
public define(orderBy: Field<Row>, direction: 'asc' | 'desc' = 'asc')
{
if (!orderBy) return
const { identifier, callback, key } = parseField(orderBy)
this.sort.set({ identifier, callback, direction, key })
}
private restore()
{
for (const sort of this.backup) {
const { key, callback, direction } = sort
const param = key as Field<Row> ?? callback
this[direction](param)
}
}
private log(sort: Sort<Row>)
{
this.backup = this.backup.filter(item => item.identifier !== sort.identifier )
if (this.backup.length >= 3) {
const [_, slot2, slot3] = this.backup
this.backup = [slot2, slot3, sort]
}
else {
this.backup = [...this.backup, sort]
}
}
}
================================================
FILE: src/lib/legacy/local/helpers/AdvancedFilterHelper.ts
================================================
import type { Field, Comparator, Criterion } from '$lib/legacy/local'
import type FilterHandler from '$lib/legacy/local/handlers/FilterHandler'
import { type Writable, writable } from 'svelte/store'
import { check } from '$lib/legacy/local/Comparator'
type Value = string | number | [min: number, max: number]
export default class AdvancedFilterHandler<Row>
{
private filterHandler : FilterHandler<Row>
private criteria : Criterion[]
private filterBy : Field<Row>
private selected : Writable<Value[]>
constructor(filterHandler: FilterHandler<Row>, filterBy: Field<Row>)
{
this.filterHandler = filterHandler
this.filterBy = filterBy
this.criteria = []
this.selected = writable([])
}
public set(value: Value, comparator: Comparator<any> = check.isLike)
{
if (this.criteria.find(criterion => criterion.value === value)) {
this.criteria = this.criteria.filter(criterion => criterion.value !== value)
}
else {
this.criteria = [ { value, comparator }, ...this.criteria ]
}
if (this.criteria.length === 0) {
return this.clear()
}
this.filterHandler.set(this.criteria, this.filterBy, check.whereIn)
this.selected.set(this.criteria.map(criterion => criterion.value))
}
public getSelected()
{
return this.selected
}
public clear()
{
this.criteria = []
this.selected.set([])
this.filterHandler.set(undefined, this.filterBy, check.whereIn)
}
}
================================================
FILE: src/lib/legacy/local/helpers/CalculationHelper.ts
================================================
import type { Field } from '$lib/legacy/local'
import type Context from '$lib/legacy/local/Context'
import { type Writable, type Readable, get, derived } from 'svelte/store'
import { parseField } from '$lib/legacy/local/utils'
export default class CalcultationHandler<Row>
{
private rawRows: Writable<Row[]>
private filteredRows: Readable<Row[]>
private callback: (row: Row) => Row[keyof Row]
private precision: number
constructor(context: Context<Row>, field: Field<Row>, param: { precision: number })
{
this.rawRows = context.rawRows
this.filteredRows = context.filteredRows
this.callback = parseField(field).callback
this.precision = param.precision
}
public distinct(callback: (values: any[]) => any[] = null)
{
const rawRows = get(this.rawRows)
const values = rawRows.map(row => this.callback(row))
const array = callback ? callback(values) : values
const result = array.reduce((acc, curr) => {
acc[curr] = (acc[curr] ?? 0) + 1
return acc
}, {})
return Object.entries(result).map(([value, count]) => ({ value, count }))
}
public avg(callback: (values: number[]) => any[] = null)
{
return derived(this.filteredRows, $filteredRows => {
if ($filteredRows.length === 0) return 0
const values = $filteredRows.map(row => this.callback(row)).filter(Boolean) as number[]
const array = callback ? callback(values) : values
return this.round(array.reduce((acc, curr) => acc + curr, 0) / array.length)
})
}
public sum(callback: (values: number[]) => any[] = null)
{
return derived(this.filteredRows, $filteredRows => {
const values = $filteredRows.map(row => this.callback(row)) as number[]
const array = callback ? callback(values) : values
return this.round(array.reduce((acc, curr) => acc + curr, 0))
})
}
public bounds(callback: (values: number[]) => any[] = null): [min: number, max: number]
{
const rawRows = get(this.rawRows)
const values = rawRows.map(row => this.callback(row))
const numbers = callback ? callback(values as number[]) : values
return [
Math.min(...numbers.filter(Boolean)),
Math.max(...numbers.filter(Boolean))
]
}
public setPrecision(value: number)
{
this.precision = value
}
private round(value: number)
{
if (this.precision === 0) {
return Math.round(value)
}
const denominator = Math.pow(10, this.precision)
return Math.round( (value + Number.EPSILON) * denominator ) / denominator
}
}
================================================
FILE: src/lib/legacy/local/helpers/FilterHelper.ts
================================================
import type { Field, Comparator } from '$lib/legacy/local'
import { check } from '$lib/legacy/local/Comparator'
import type FilterHandler from '../handlers/FilterHandler'
type Value = string | number | boolean
export default class FilterHelper<Row>
{
private filterHandler : FilterHandler<Row>
private filterBy : Field<Row>
private uid : string
private comparator : Comparator<Row>
private callback : () => void
constructor(filterHandler: FilterHandler<Row>, filterBy: Field<Row>, comparator?: Comparator<Row>)
{
this.filterHandler = filterHandler
this.filterBy = filterBy
this.uid = 'f_' + (Math.random()).toString(28).substring(2)
this.comparator = comparator ?? check.isLike
this.callback = () => null
}
public set(value: Value, comparator?: Comparator<any>)
{
if (comparator) {
this.comparator = comparator
}
this.filterHandler.set(value, this.filterBy, this.comparator, this.uid)
}
public clear()
{
this.callback()
this.filterHandler.set(undefined, this.filterBy)
}
public on(event: 'clear', callback: () => void)
{
this.callback = callback
}
}
================================================
FILE: src/lib/legacy/local/index.ts
================================================
// Reexport your entry components here
import DataHandler from './DataHandler'
import Datatable from './Datatable.svelte'
import Th from './Th.svelte'
import ThFilter from './ThFilter.svelte'
import Pagination from './Pagination.svelte'
import RowCount from './RowCount.svelte'
import RowsPerPage from './RowsPerPage.svelte'
import Search from './Search.svelte'
import { check } from './Comparator'
export { DataHandler, check, Datatable, Th, ThFilter, Pagination, RowCount, RowsPerPage, Search }
export type { default as EventHandler } from './handlers/EventHandler'
export type Internationalization = {
search?: string
show?: string
entries?: string
filter?: string
rowCount?: string
noRows?: string
previous?: string
next?: string
}
export type Row = { [key: string]: any }
export type Field<Row> = keyof Row | ((row: Row) => Row[keyof Row])
export type Comparator<Row> = (entry: Row[keyof Row], value: any) => boolean
export type Criterion = { value: string | number | [min: number, max: number], comparator: Comparator<Row> }
export type Filter<Row> = {
callback: (row: Row) => Row[keyof Row]
identifier: string
value?: string | number | boolean | symbol | Criterion[] | number[]
comparator?: Comparator<Row>
key?: string
}
export type Sort<Row> = {
callback?: (row: Row) => Row[keyof Row]
identifier?: string
direction?: 'asc' | 'desc'
key?: string
}
/**
* @deprecated use (Row[keyof Row] | Row) instead
*
* import type { Row } from '@vincjo/datatables'
*/
export type Selectable<Row> = Row[keyof Row] | Row
/**
* @deprecated use type Field<Row> instead
*
* import type { Field } from '@vincjo/datatables'
*/
export type FilterBy<Row> = Field<Row>
/**
* @deprecated use type Field<Row> instead
*
* import type { Field } from '@vincjo/datatables'
*/
export type OrderBy<Row> = Field<Row>
/**
* @deprecated use type Sort<Row> instead
*
* import type { Sort } from '@vincjo/datatables'
*/
export type Order<Row> = Sort<Row>
================================================
FILE: src/lib/legacy/local/utils.ts
================================================
import type { Row, Field } from '$lib/legacy/local'
export const isNull = (value: any) => {
if (value === null || value === undefined || value === '') return true
return false
}
export const isNotNull = (value: any) => { return !isNull(value) }
export const parseField = (field: Field<any>, uid?: string) => {
const identifier = uid ?? field.toString()
if (typeof field === 'string') {
return {
callback: (row: Row) => row[field],
identifier,
key: field as string
}
} else if (typeof field === 'function') {
return {
callback: field,
identifier,
key: undefined
}
}
throw new Error(`Invalid field argument: ${String(field)}`)
}
================================================
FILE: src/lib/legacy/remote/Context.ts
================================================
import { type Writable, writable, get, derived, type Readable } from 'svelte/store'
import type { State, Sort, Filter } from '$lib/legacy/remote'
import type { Params } from './DataHandler'
import EventHandler from './handlers/EventHandler'
export default class Context<Row>
{
public totalRows : Writable<number | undefined>
public rowsPerPage : Writable<number>
public pageNumber : Writable<number>
public event : EventHandler
public search : Writable<string>
public filters : Writable<Filter<Row>[]>
public rows : Writable<Row[]>
public rowCount : Readable<{ total: number, start: number, end: number }>
public pages : Readable<number[]>
public pagesWithEllipsis : Readable<number[]>
public pageCount : Readable<number>
public sort : Writable<Sort<Row>>
public selected : Writable<(Row | Row[keyof Row])[]>
public isAllSelected : Readable<boolean>
public selectedCount : Readable<{ count: number, total: number }>
public selectBy : keyof Row | undefined
constructor(data: Row[], params: Params)
{
this.totalRows = writable(params.totalRows)
this.rowsPerPage = writable(params.rowsPerPage)
this.pageNumber = writable(1)
this.event = new EventHandler()
this.search = writable('')
this.filters = writable([])
this.rows = writable(data)
this.rowCount = this.createRowCount()
this.pages = this.createPages()
this.pagesWithEllipsis = this.createPagesWithEllipsis()
this.pageCount = this.createPageCount()
this.sort = writable(undefined)
this.selected = writable([])
this.isAllSelected = this.createIsAllSelected()
this.selectedCount = this.createSelectedCount()
this.selectBy = params.selectBy as keyof Row ?? undefined
}
public getState(): State
{
const pageNumber = get(this.pageNumber)
const rowsPerPage = get(this.rowsPerPage)
const sort = get(this.sort)
const filters = get(this.filters)
return {
pageNumber,
rowsPerPage,
offset: rowsPerPage * (pageNumber - 1),
search: get(this.search),
sorted: sort ?? undefined as any, // deprecated
sort: sort ?? undefined as any,
filters: filters.length > 0 ? filters : undefined as any,
setTotalRows: (value: number) => this.totalRows.set(value)
}
}
private createPages()
{
return derived([this.rowsPerPage, this.totalRows], ([$rowsPerPage, $totalRows]) => {
if (!$rowsPerPage || !$totalRows) {
return undefined
}
const pages = Array.from(Array(Math.ceil($totalRows / $rowsPerPage)))
return pages.map((_, i) => {
return i + 1
})
})
}
private createPagesWithEllipsis()
{
return derived([this.pages, this.pageNumber], ([$pages, $pageNumber]) => {
if (!$pages) {
return undefined
}
if ($pages.length <= 7) {
return $pages
}
const ellipse = null
const firstPage = 1
const lastPage = $pages.length
if ($pageNumber <= 4) {
return [
...$pages.slice(0, 5),
ellipse,
lastPage
]
} else if ($pageNumber < $pages.length - 3) {
return [
firstPage,
ellipse,
...$pages.slice($pageNumber - 2, $pageNumber + 1),
ellipse,
lastPage
]
} else {
return [
firstPage,
ellipse,
...$pages.slice($pages.length - 5,
$pages.length)
]
}
})
}
private createPageCount()
{
return derived(this.pages, ($pages) => {
if (!$pages) return undefined
return $pages.length
})
}
private createRowCount()
{
return derived(
[this.totalRows, this.pageNumber, this.rowsPerPage],
([$totalRows, $pageNumber, $rowsPerPage]) => {
if (!$rowsPerPage || !$totalRows) {
return undefined
}
return {
total: $totalRows,
start: $pageNumber * $rowsPerPage - $rowsPerPage + 1,
end: Math.min($pageNumber * $rowsPerPage, $totalRows)
}
}
)
}
private createIsAllSelected()
{
return derived([this.selected, this.rows], ([$selected, $rows]) => {
if ($rows.length === 0) {
return false
}
if (this.selectBy) {
const ids = $rows.map(row => row[this.selectBy])
return ids.every(id => $selected.includes(id))
}
return $rows.every(row => $selected.includes(row as Row))
})
}
private createSelectedCount()
{
return derived(
[this.selected, this.totalRows],
([$selected, $totalRows]) => {
return {
count: $selected.length,
total: $totalRows
}
}
)
}
}
================================================
FILE: src/lib/legacy/remote/DataHandler.ts
================================================
import Context from './Context'
import TriggerHandler from './handlers/TriggerHandler'
import SortHandler from './handlers/SortHandler'
import SelectHandler from './handlers/SelectHandler'
import PageHandler from './handlers/PageHandler'
import SearchHandler from './handlers/SearchHandler'
import FilterHandler from './handlers/FilterHandler'
import type { Writable, Readable } from 'svelte/store'
import type { Internationalization, Row, State, Sort } from '$lib/legacy/remote'
export type Params = {
rowsPerPage ?: number,
totalRows ?: number,
selectBy ?: keyof Row,
i18n ?: Internationalization
}
export default class DataHandler<T extends Row = any>
{
private context : Context<T>
private triggerHandler : TriggerHandler<T>
private sortHandler : SortHandler<T>
private selectHandler : SelectHandler<T>
private pageHandler : PageHandler<T>
private searchHandler : SearchHandler<T>
private filterHandler : FilterHandler<T>
public i18n : Internationalization
constructor(data: T[] = [], params: Params = { rowsPerPage: 5 })
{
this.i18n = this.translate(params.i18n)
this.context = new Context(data, params)
this.triggerHandler = new TriggerHandler(this.context)
this.sortHandler = new SortHandler(this.context)
this.selectHandler = new SelectHandler(this.context)
this.pageHandler = new PageHandler(this.context)
this.searchHandler = new SearchHandler(this.context)
this.filterHandler = new FilterHandler(this.context)
}
public onChange(callback: (state: State) => Promise<T[]>)
{
this.triggerHandler.set(callback)
}
public invalidate()
{
this.triggerHandler.invalidate()
}
public setRows(data: T[])
{
this.context.rows.set(data)
}
public setTotalRows(value: number)
{
this.context.totalRows.set(value)
}
public getRows(): Writable<T[]>
{
return this.context.rows
}
public select(value: T[keyof T] | T)
{
this.selectHandler.set(value)
}
public getSelected()
{
return this.context.selected
}
public selectAll(): void
{
this.selectHandler.all()
}
public isAllSelected(): Readable<boolean>
{
return this.context.isAllSelected
}
public getSelectedCount(): Readable<{ count: number, total: number }>
{
return this.context.selectedCount
}
public clearSelection()
{
this.selectHandler.clear()
}
public getRowsPerPage(): Writable<number | null>
{
return this.context.rowsPerPage
}
public sort(orderBy: keyof T)
{
this.setPage(1)
this.sortHandler.set(orderBy)
}
public applySort( params: { orderBy: keyof T, direction?: 'asc' | 'desc' } = null )
{
this.sortHandler.apply(params)
}
public sortAsc(orderBy: keyof T)
{
this.setPage(1)
this.sortHandler.asc(orderBy)
}
public sortDesc(orderBy: keyof T)
{
this.setPage(1)
this.sortHandler.desc(orderBy)
}
public getSort(): Writable<Sort<T>>
{
return this.context.sort
}
public search(value: string): void
{
this.setPage(1)
this.context.search.set(value)
}
public clearSearch()
{
this.searchHandler.remove()
}
public filter(value: string | number, filterBy: keyof T)
{
this.setPage(1)
return this.filterHandler.set(value, filterBy)
}
public clearFilters(): void
{
this.filterHandler.remove()
}
public getPages(params = { ellipsis: false }): Readable<number[]>
{
if (params.ellipsis) {
return this.context.pagesWithEllipsis
}
return this.context.pages
}
public getPageCount(): Readable<number>
{
return this.context.pageCount
}
public getPageNumber(): Writable<number>
{
return this.context.pageNumber
}
public setPage(value: number | 'previous' | 'next'): void
{
switch (value) {
case 'previous' : return this.pageHandler.previous()
case 'next' : return this.pageHandler.next()
default : return this.pageHandler.goto(value as number)
}
}
public getRowCount(): Readable<{ total: number, start: number, end: number }>
{
return this.context.rowCount
}
public on(event: 'change', callback: () => void)
{
this.context.event.add(event, callback)
}
public translate(i18n: Internationalization): Internationalization
{
return {
...{
search: 'Search...',
show: 'Show',
entries: 'entries',
filter: 'Filter',
rowCount: 'Showing {start} to {end} of {total} entries',
noRows: 'No entries found',
previous: 'Previous',
next: 'Next',
selectedCount: '{count} of {total} row(s).'
},
...i18n
}
}
/**
*
* @depracted use on('change', callback) instead
*/
public getTriggerChange(): Writable<number>
{
return this.context.event.triggerChange
}
/**
*
* @deprecated use applySort() instead
*/
public applySorting( params: { orderBy: keyof T, direction?: 'asc' | 'desc' } = null )
{
this.applySort(params)
}
/**
*
* @deprecated use getSort() instead
*/
public getSorted()
{
return this.getSort()
}
}
================================================
FILE: src/lib/legacy/remote/Datatable.svelte
================================================
<script lang="ts">
import { type DataHandler, type Row, Search, RowsPerPage, RowCount, SelectedCount, Pagination } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let search = true
export let rowsPerPage = true
export let rowCount = true
export let selectedCount = false
export let pagination = true
let element: HTMLElement
let clientWidth = 1000
const height = (search || rowsPerPage ? 48 : 8) + (rowCount || selectedCount || pagination ? 48 : 8)
handler.on('change', () => {
if (element) element.scrollTop = 0
})
</script>
<section bind:clientWidth class={$$props.class ?? ''}>
<header class:container={search || rowsPerPage}>
{#if search}
<Search {handler} />
{:else}
<div></div>
{/if}
{#if rowsPerPage}
<RowsPerPage {handler} small={clientWidth < 600} />
{/if}
</header>
<article bind:this={element} style="height:calc(100% - {height}px)">
<slot />
</article>
<footer class:container={rowCount || pagination}>
{#if selectedCount}
<SelectedCount {handler}/>
{:else if rowCount}
<RowCount {handler} small={clientWidth < 600} />
{/if}
{#if pagination}
<Pagination {handler} small={clientWidth < 600} />
{/if}
</footer>
</section>
<style>
section {
height: 100%;
}
section :global(table) {
border-collapse: separate;
border-spacing: 0;
width: 100%;
}
section :global(thead) {
position: sticky;
inset-block-start: 0;
z-index: 1;
}
header,
footer {
min-height: 8px;
padding: 0 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
header.container,
footer.container {
height: 48px;
}
footer {
border-top: 1px solid #e0e0e0;
}
article {
position: relative;
/* height:calc(100% - 96px); */
overflow: auto;
scrollbar-width: thin;
}
article::-webkit-scrollbar {
width: 6px;
height: 6px;
}
article::-webkit-scrollbar-track {
background: #f5f5f5;
}
article::-webkit-scrollbar-thumb {
background: #c2c2c2;
}
article::-webkit-scrollbar-thumb:hover {
background: #9e9e9e;
}
</style>
================================================
FILE: src/lib/legacy/remote/Pagination.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
const pageNumber = handler.getPageNumber()
const pageCount = handler.getPageCount()
const pages = handler.getPages({ ellipsis: true })
const setPage = (value: 'previous' | 'next' | number) => {
handler.setPage(value)
handler.invalidate()
}
</script>
<section class={$$props.class ?? ''}>
{#if $pages === undefined}
<button type="button"
class="small"
class:disabled={$pageNumber === 1}
on:click={() => setPage('previous')}
>
❮
</button>
<button type="button" class="page">page <b>{$pageNumber}</b></button>
<button type="button"
class="small"
on:click={() => setPage('next')}
>
❯
</button>
{:else}
{#if small}
<button type="button"
class="small"
class:disabled={$pageNumber === 1}
on:click={() => setPage(1)}
>
❬❬
</button>
<button type="button"
class:disabled={$pageNumber === 1}
on:click={() => setPage('previous')}
>
❮
</button>
<button type="button"
class:disabled={$pageNumber === $pageCount}
on:click={() => setPage('next')}
>
❯
</button>
<button type="button"
class="small"
class:disabled={$pageNumber === $pageCount}
on:click={() => setPage($pageCount)}
>
❭❭
</button>
{:else}
<button type="button"
class:disabled={$pageNumber === 1}
on:click={() => setPage('previous')}
>
{@html handler.i18n.previous}
</button>
{#each $pages as page}
<button type="button"
class:active={$pageNumber === page}
class:ellipse={page === null}
on:click={() => setPage(page)}
>
{page ?? '...'}
</button>
{/each}
<button type="button"
class:disabled={$pageNumber === $pageCount}
on:click={() => setPage('next')}
>
{@html handler.i18n.next}
</button>
{/if}
{/if}
</section>
<style>
section {
display: flex;
}
button {
background: inherit;
height: 32px;
width: 32px;
color: #616161;
cursor: pointer;
font-size: 13px;
margin: 0;
padding: 0;
transition: all, 0.2s;
line-height: 32px;
border: 1px solid #e0e0e0;
border-right: none;
border-radius: 0;
outline: none;
}
button:first-child {
border-radius: 4px 0 0 4px;
}
button:last-child {
border-right: 1px solid #e0e0e0;
border-radius: 0 4px 4px 0;
}
button:first-child:not(.small),
button:last-child:not(.small) {
min-width: 72px;
}
button:not(.active):hover {
background: #eee;
}
button.disabled:hover {
background: inherit;
cursor: default;
}
button.page {
width: 72px;
background: #fafafa;
}
button.page:hover {
background: #fafafa;
cursor: default;
}
button.ellipse:hover {
background: inherit;
cursor: default;
}
button.active {
background: #eee;
font-weight: bold;
cursor: default;
}
button.disabled {
color: #bdbdbd;
}
button.disabled:hover {
background: inherit;
cursor: default;
}
</style>
================================================
FILE: src/lib/legacy/remote/README.md
================================================
<div align="center">
<img align="center" src="../../../static/logo-remote.svg" alt="logo" width="172"/>
<p align="center">
<h1 align="center" style="font-size:32px;margin:0;border:none;">svelte simple datatables</h1>
<p style="color:#eee">For server-side pagination</p>
<img src="https://img.shields.io/npm/v/@vincjo/datatables?color=%23006990" alt="npm version"/>
<img src="https://img.shields.io/github/license/vincjo/datatables?color=006990" alt="last commit"/>
</p>
</div>
**→ [Home](https://vincjo.fr/datatables/remote/home)**
**→ [Examples](https://vincjo.fr/datatables/remote/examples)**
================================================
FILE: src/lib/legacy/remote/RowCount.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
const rowCount = handler.getRowCount()
</script>
{#if $rowCount === undefined}
<div></div>
{:else}
<aside class={$$props.class ?? ''}>
{#if small}
{#if $rowCount.total > 0}
<b>{$rowCount.start}</b>-
<b>{$rowCount.end}</b>/
<b>{$rowCount.total}</b>
{:else}
{handler.i18n.noRows}
{/if}
{:else if $rowCount.total > 0}
{@html handler.i18n.rowCount
.replace('{start}', `<b>${$rowCount.start}</b>`)
.replace('{end}', `<b>${$rowCount.end}</b>`)
.replace('{total}', `<b>${$rowCount.total}</b>`)}
{:else}
{handler.i18n.noRows}
{/if}
</aside>
{/if}
<style>
aside {
color: #616161;
line-height: 32px;
font-size: 14px;
}
</style>
================================================
FILE: src/lib/legacy/remote/RowsPerPage.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let small = false
export let options = [5, 10, 20]
const rowsPerPage = handler.getRowsPerPage()
const setRowsPerPage = () => {
handler.setPage(1)
handler.invalidate()
}
</script>
<aside class={$$props.class ?? ''}>
{#if !small}
<span>{handler.i18n.show}</span>
{/if}
<select bind:value={$rowsPerPage} on:change={setRowsPerPage}>
{#each options as option}
<option value={option}>
{option}
</option>
{/each}
</select>
{#if !small}
<span>{handler.i18n.entries}</span>
{/if}
</aside>
<style>
aside {
display: flex;
justify-content: flex-start;
align-items: center;
height: 32px;
color: #757575;
}
select {
margin: 0 4px;
}
</style>
================================================
FILE: src/lib/legacy/remote/Search.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
let value = ''
let timeout: any
const search = () => {
handler.search(value)
clearTimeout(timeout)
timeout = setTimeout( () => {
handler.invalidate()
}, 400)
}
</script>
<input
class={$$props.class ?? ''}
bind:value
placeholder={handler.i18n.search}
spellcheck="false"
on:input={search}
/>
<style>
input {
border: 1px solid #e0e0e0;
border-radius: 4px;
outline: none;
padding: 0 8px;
line-height: 24px;
margin: 0;
height: 24px;
background: transparent;
width: 40%;
max-width: 176px;
min-width: 96px;
transition: all, 0.1s;
}
input:focus {
border: 1px solid #bdbdbd;
}
input::placeholder {
color: #9e9e9e;
line-height: 24px;
}
</style>
================================================
FILE: src/lib/legacy/remote/SelectedCount.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
const selectedCount = handler.getSelectedCount()
</script>
<aside class={$$props.class ?? ''}>
<b>{$selectedCount.count}</b>
{#if $selectedCount.total}
of <b>{$selectedCount.total}</b>
{/if}
row(s) selected.
{#if $selectedCount.count > 0}
<button type="button" on:click={() => handler.clearSelection()}>❌ Clear</button>
{/if}
</aside>
<style>
aside {
color: #616161;
line-height: 32px;
font-size: 14px;
}
button {
background: #fafafa;
padding: 2px 4px;
border-radius: 2px;
cursor: pointer;
border: 1px solid #e0e0e0;
}
</style>
================================================
FILE: src/lib/legacy/remote/Th.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let orderBy: keyof T
export let align: 'left' | 'right' | 'center' = 'left'
export let rowSpan: number = 1
const sort = handler.getSort()
const update = () => {
handler.sort(orderBy)
handler.invalidate()
}
</script>
<th
on:click={update}
class:sortable={orderBy}
class:active={$sort?.orderBy === orderBy}
class={$$props.class ?? ''}
rowspan={rowSpan}
>
<div
class="flex"
style:justify-content={align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center'}
>
<strong>
<slot />
</strong>
<span class:asc={$sort?.direction === 'asc'} class:desc={$sort?.direction === 'desc'}></span>
</div>
</th>
<style>
th {
background: inherit;
padding: 8px 20px;
white-space: nowrap;
font-size: 13px;
user-select: none;
border-bottom: 1px solid #e0e0e0;
}
th.sortable {
cursor: pointer;
}
th strong {
white-space: pre-wrap;
font-size: 13.5px;
line-height: 16px;
}
th.sortable div.flex {
padding: 0;
display: flex;
align-items: center;
height: 100%;
}
th.sortable span {
padding-left: 8px;
}
th.sortable span:before,
th.sortable span:after {
border: 4px solid transparent;
content: '';
display: block;
height: 0;
width: 0;
}
th.sortable span:before {
border-bottom-color: #e0e0e0;
margin-top: 2px;
}
th.sortable span:after {
border-top-color: #e0e0e0;
margin-top: 2px;
}
th.active.sortable span.asc:before {
border-bottom-color: #9e9e9e;
}
th.active.sortable span.desc:after {
border-top-color: #9e9e9e;
}
th:not(.sortable) span {
visibility: hidden;
}
</style>
================================================
FILE: src/lib/legacy/remote/ThFilter.svelte
================================================
<script lang="ts">
import type { DataHandler, Row } from '$lib/legacy/remote'
type T = $$Generic<Row>
export let handler: DataHandler<T>
export let filterBy: keyof T
export let align: 'left' | 'right' | 'center' = 'left'
let value: string = ''
let timeout: any
const filter = () => {
handler.filter(value, filterBy)
clearTimeout(timeout)
timeout = setTimeout( () => {
handler.invalidate()
}, 400)
}
</script>
<th class={$$props.class ?? ''}>
<input
style:text-align={align}
type="text"
placeholder={handler.i18n.filter}
spellcheck="false"
bind:value
on:input={filter}
/>
</th>
<style>
th {
border-bottom: 1px solid #e0e0e0;
}
input {
width: 100%;
height: 24px;
border: none;
text-align: left;
padding: 0 20px;
background: inherit;
outline: none;
border-radius: 0;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
}
input::placeholder {
color: #bdbdbd;
font-style: italic;
font-size: 13px;
}
input:focus {
outline: none;
border: none;
}
</style>
================================================
FILE: src/lib/legacy/remote/handlers/EventHandler.ts
================================================
import { writable } from 'svelte/store'
export default class EventHandler
{
private events = {
change : [] as (() => void)[],
clearFilters: [] as (() => void)[],
clearSearch : [] as (() => void)[]
}
public triggerChange = writable(0) // legacy
public add(event: keyof EventHandler['events'], callback: () => void)
{
this.events[event].push(callback)
}
public trigger(event: keyof EventHandler['events'])
{
for (const callback of this.events[event]) {
callback()
}
/* legacy: support for triggerChange store */
if (event === 'change') {
this.triggerChange.update((store) => { return store + 1 })
}
}
}
================================================
FILE: src/lib/legacy/remote/handlers/FilterHandler.ts
================================================
import type { Filter } from '$lib/legacy/remote'
import type Context from '$lib/legacy/remote/Context'
import type { Writable } from 'svelte/store'
export default class FilterHandler<Row>
{
public filters: Writable<Filter<Row>[]>
constructor(context: Context<Row>)
{
this.filters = context.filters
}
public set(value: string | number, filterBy: keyof Row )
{
const filter = { filterBy, value }
this.filters.update((store) => {
store = store.filter((item) => {
return (item.filterBy !== filterBy) && item.value
})
if (value) {
store.push(filter)
}
return store
})
}
public remove()
{
this.filters.set([])
}
}
================================================
FILE: src/lib/legacy/remote/handlers/PageHandler.ts
================================================
import type Context from '$lib/legacy/remote/Context'
import { type Writable, type Readable, get } from 'svelte/store'
import type EventHandler from './EventHandler'
export default class PageHandler<Row>
{
public totalRows : Writable<number>
public pageNumber : Writable<number>
public rowCount : Readable<{ total: number, start: number, end: number }>
public rowsPerPage : Writable<number | null>
public event : EventHandler
public pages : Readable<number[]>
public selected : Writable<(Row | Row[keyof Row])[]>
constructor(context: Context<Row>)
{
this.totalRows = context.totalRows
this.pageNumber = context.pageNumber
this.rowCount = context.rowCount
this.rowsPerPage = context.rowsPerPage
this.event = context.event
this.pages = context.pages
this.selected = context.selected
}
public get()
{
return this.pages
}
public goto(number: number)
{
const rowsPerPage = get(this.rowsPerPage)
const totalRows = get(this.totalRows)
this.pageNumber.update((store) => {
if (rowsPerPage && totalRows) {
if (number >= 1 && number <= Math.ceil(totalRows / rowsPerPage)) {
store = number
this.event.trigger('change')
}
return store
}
else {
if (number >= 1) {
store = number
this.event.trigger('change')
}
return store
}
})
}
public previous()
{
const number = get(this.pageNumber) - 1
this.goto(number)
}
public next()
{
const number = get(this.pageNumber) + 1
this.goto(number)
}
}
================================================
FILE: src/lib/legacy/remote/handlers/SearchHandler.ts
================================================
import type Context from '$lib/legacy/remote/Context'
import type { Writable } from 'svelte/store'
export default class SearchHandler<Row>
{
private search: Writable<string>
constructor(context: Context<Row>) {
this.search = context.search
}
public set(value: string)
{
this.search.set(value ?? null)
}
public remove()
{
this.search.set(null)
}
}
================================================
FILE: src/lib/legacy/remote/handlers/SelectHandler.ts
================================================
import type Context from '$lib/legacy/remote/Context'
import { type Writable, type Readable, get } from 'svelte/store'
export default class SelectHandler<Row>
{
private rows : Readable<Row[]>
private selected : Writable<(Row | Row[keyof Row])[]>
private isAllSelected : Readable<boolean>
private selectBy : keyof Row | undefined
constructor(context: Context<Row>)
{
this.rows = context.rows
this.selected = context.selected
this.isAllSelected = context.isAllSelected
this.selectBy = context.selectBy
}
public set(value: Row | Row[keyof Row])
{
const selected = get(this.selected)
if (selected.includes(value)) {
this.selected.set(selected.filter((item) => item !== value))
}
else {
this.selected.set([value, ...selected])
}
}
public all()
{
const rows = get(this.rows)
const isAllSelected = get(this.isAllSelected)
this.selected.update( store => {
if (this.selectBy) {
return store = store.filter(item => !rows.map((row) => row[this.selectBy]).includes(item as Row[keyof Row]))
}
return store = store.filter(item => !rows.includes(item as Row))
})
if (!isAllSelected) {
this.selected.update( store => {
if (this.selectBy) {
store = [...rows.map((row) => row[this.selectBy]), ...store]
}
else {
store = [...rows, ...store]
}
return store
})
}
}
public clear()
{
this.selected.set([])
}
}
================================================
FILE: src/lib/legacy/remote/handlers/SortHandler.ts
================================================
import type Context from '$lib/legacy/remote/Context'
import type { Order } from '$lib/legacy/remote'
import { type Writable, get } from 'svelte/store'
import type EventHandler from './EventHandler'
export default class SortHandler<Row>
{
private event : EventHandler
private hasMultipleSort : boolean
private sort : Writable<Order<Row>>
constructor(context: Context<Row>)
{
this.event = context.event
this.hasMultipleSort = false
this.sort = context.sort
}
public set(orderBy: keyof Row = null)
{
if (!orderBy) return
const sort = get(this.sort)
if(!sort || sort.orderBy !== orderBy) {
this.asc(orderBy)
}
else if (sort.direction === 'asc') {
this.desc(sort.orderBy)
}
else if (sort.direction === 'desc') {
this.asc(orderBy)
}
}
public asc(orderBy: keyof Row)
{
if (!orderBy) return
this.sort.set({ orderBy, direction: 'asc' })
this.event.trigger('change')
}
public desc(orderBy: keyof Row)
{
if (!orderBy) return
this.sort.set({ orderBy, direction: 'desc' })
this.event.trigger('change')
}
public apply(params: { orderBy: keyof Row, direction?: 'asc' | 'desc' } = null)
{
if (params) {
switch (params.direction) {
case 'asc' : return this.asc(params.orderBy)
case 'desc': return this.desc(params.orderBy)
default : return this.set(params.orderBy)
}
}
const sort = get(this.sort)
if (sort) {
return this.apply({ orderBy: sort.orderBy, direction: sort.direction })
}
return
}
// public set(orderBy: keyof Row, direction: 'asc' | 'desc')
// {
// const sort = { orderBy, direction }
// if (this.hasMultipleSort === false) {
// this.sort.set([ sort ])
// return
// }
// this.sort.update((store) => {
// store = store.filter((item) => {
// return (item.orderBy !== orderBy) && item.direction
// })
// if (orderBy) {
// store.push(sort)
// }
// return store
// })
// }
// public sort(orderBy: keyof Row = null)
// {
// if (!orderBy) return
// const sort = get(this.sort)
// const exists = sort.find(sort => sort.orderBy === orderBy)
// if(!exists) {
// this.sortAsc(orderBy)
// }
// else if (exists.direction === 'asc') {
// this.sortDesc(exists.orderBy)
// }
// else if (exists.direction === 'desc') {
// this.sortAsc(orderBy)
// }
// }
// public sortAsc(orderBy: keyof Row)
// {
// if (!orderBy) return
// this.set( orderBy, 'asc' )
// this.triggerChange.update((store) => { return store + 1 })
// }
// public sortDesc(orderBy: keyof Row)
// {
// if (!orderBy) return
// this.set( orderBy, 'desc' )
// this.triggerChange.update((store) => { return store + 1 })
// }
// public applySorting(params: { orderBy: keyof Row, direction?: 'asc' | 'desc' } = null)
// {
// if (params) {
// switch (params.direction) {
// case 'asc' : return this.sortAsc(params.orderBy)
// case 'desc': return this.sortDesc(params.orderBy)
// default : return this.sort(params.orderBy)
// }
// }
// const sort = get(this.sort)
// if (sort.length > 0) {
// for (const order of sort) {
// return this.applySorting({ orderBy: order.orderBy, direction: order.direction })
// }
// }
// return
// }
}
================================================
FILE: src/lib/legacy/remote/handlers/TriggerHandler.ts
================================================
import type { State } from '$lib/legacy/remote'
import type Context from '$lib/legacy/remote/Context'
export default class TriggerHandler<Row>
{
private context: Context<Row>
private reload: (state: State) => Promise<Row[]>
constructor(context: Context<Row>)
{
this.context = context
}
public set(callback: (state: State) => Promise<Row[]>)
{
this.reload = callback
}
public async invalidate()
{
if (!this.reload) return
const state = this.context.getState()
const data = await this.reload(state)
if (data) {
this.context.rows.set(data)
}
}
}
================================================
FILE: src/lib/legacy/remote/index.ts
================================================
// Reexport your entry components here
import DataHandler from './DataHandler'
import Datatable from './Datatable.svelte'
import Search from './Search.svelte'
import RowsPerPage from './RowsPerPage.svelte'
import Th from './Th.svelte'
import ThFilter from './ThFilter.svelte'
import RowCount from './RowCount.svelte'
import SelectedCount from './SelectedCount.svelte'
import Pagination from './Pagination.svelte'
export { DataHandler, Datatable, Search, RowsPerPage, Th, ThFilter, RowCount, SelectedCount, Pagination }
export type Internationalization = {
search ?: string
show ?: string
entries ?: string
filter ?: string
rowCount ?: string
noRows ?: string
previous ?: string
next ?: string
selectedCount ?: string
}
export type Row = { [key: string]: unknown }
export type Filter<Row> = {
filterBy: keyof Row
value?: string | number | boolean
}
export type Sort<Row> = {
orderBy?: keyof Row
direction?: 'asc' | 'desc'
}
export type State = {
pageNumber: number,
rowsPerPage: number,
offset: number,
search: string | undefined,
sort: Sort<Row> | undefined
filters: Filter<Row>[] | undefined
setTotalRows: (value: number) => void
/**
* @deprecated use 'sort' instead
*/
sorted: Sort<Row> | undefined
}
/**
* @deprecated use (Row[keyof Row] | Row) instead
* @since v1.13.0 2023-11-14
*
* import type { Row } from '@vincjo/datatables'
*/
export type Selectable<Row> = Row[keyof Row] | Row
/**
* @deprecated use type Sort<Row> instead
* @since v1.13.0 2023-11-14
*
* import type { Sort } from '@vincjo/datatables'
*/
export type Order<Row> = Sort<Row>
================================================
FILE: src/lib/server/index.ts
================================================
export {
// class:
TableHandler,
// components:
Datatable,
Search,
RowsPerPage,
Th,
ThSort,
ThFilter,
Pagination,
RowCount,
// types:
type Row,
type State,
type Filter,
type Sort,
type Internationalization,
type TableHandlerInterface
} from '$lib/src/server/index.js'
================================================
FILE: src/lib/src/client/AbstractTableHandler.svelte.ts
================================================
import type { Field, TableParams } from '$lib/src/client'
import { EventDispatcher } from '$lib/src/shared'
import { type Search, type Filter, type Query, type Sort, data, parse } from './core'
export default abstract class AbstractTableHandler<Row>
{
protected selectBy ?: Field<Row>
protected selectScope = $state<'all' | 'currentPage'>('currentPage')
protected highlight : boolean
protected event = new EventDispatcher()
protected rawRows = $state.raw<Row[]>([])
protected search = $state<(Search<Row>)>({ value: null })
protected sort = $state<(Sort<Row>)>({})
public filters = $state<(Filter<Row>)[]>([])
public queries = $state<(Query<Row>)[]>([])
public rowsPerPage = $state<number>(10)
public currentPage = $state<number>(1)
public element = $state<HTMLElement>(undefined)
public clientWidth = $state<number>(1000)
public filterCount = $derived<number>(this.filters.length)
public allRows = $derived<readonly $state.Snapshot<Row>[]>(this.createAllRows())
public rows = $derived<readonly $state.Snapshot<Row>[]>(this.createRows())
public rowCount = $derived<{total: number, start: number, end: number, selected: number}>(this.createRowCount())
public pages = $derived<readonly number[]>(this.createPages())
public pageCount = $derived<number>(this.pages.length)
public pagesWithEllipsis = $derived<readonly number[]>(this.createPagesWithEllipsis())
public selected = $state<unknown[]>([])
public isAllSelected = $derived<boolean>(this.createIsAllSelected())
constructor(data: Row[], params: TableParams<Row>)
{
this.rawRows = data
this.rowsPerPage = params.rowsPerPage ?? null
this.highlight = params.highlight ?? false
this.selectBy = params.selectBy
}
private createAllRows()
{
let allRows = $state.snapshot(this.rawRows)
if (this.search.value) {
allRows = data.search(allRows, this.search, this.highlight)
this.event.dispatch('change')
}
else if (this.filters.length > 0) {
for (const filter of this.filters) {
allRows = data.filter(allRows, filter, this.highlight)
}
this.event.dispatch('change')
}
else if (this.queries.length > 0) {
for (const query of this.queries) {
allRows = data.query(allRows, query)
}
this.event.dispatch('change')
}
return allRows
}
private createRows()
{
if (!this.rowsPerPage) return this.allRows
return this.allRows.slice(
(this.currentPage - 1) * this.rowsPerPage,
this.currentPage * this.rowsPerPage
)
}
private createRowCount()
{
const total = this.allRows.length
if (!this.rowsPerPage) {
return { total: total, start: 1, end: total, selected: this.selected.length }
}
return {
total: total,
start: this.currentPage * this.rowsPerPage - this.rowsPerPage + 1,
end: Math.min(this.currentPage * this.rowsPerPage, total),
selected: this.selected.length
}
}
private createPages()
{
if (!this.rowsPerPage) {
return [1]
}
const pages = Array.from(Array(Math.ceil(this.allRows.length / this.rowsPerPage)))
return pages.map((_, i) => i + 1 )
}
private createPagesWithEllipsis()
{
if (this.pageCount <= 7) {
return this.pages
}
const ellipse = null
const firstPage = 1
const lastPage = this.pageCount
if (this.currentPage <= 4) {
return [
...this.pages.slice(0, 5),
ellipse,
lastPage
]
} else if (this.currentPage < lastPage - 3) {
return [
firstPage,
ellipse,
...this.pages.slice(this.currentPage - 2, this.currentPage + 1),
ellipse,
lastPage
]
} else {
return [
firstPage,
ellipse,
...this.pages.slice(lastPage - 5, lastPage)
]
}
}
private createIsAllSelected()
{
if (this.rowCount.total === 0 || !this.selectBy) {
return false
}
const { callback } = parse(this.selectBy)
if (this.selectScope === 'all') {
const identifiers = this.allRows.map(callback)
return identifiers.every(id => this.selected.includes(id))
}
const identifiers = this.rows.map(callback)
return identifiers.every(id => this.selected.includes(id))
}
}
================================================
FILE: src/lib/src/client/TableHandler.svelte.ts
================================================
import { untrack } from 'svelte'
import AbstractTableHandler from './AbstractTableHandler.svelte'
import SortHandler from './handlers/SortHandler.svelte'
import FilterHandler from './handlers/FilterHandler.svelte'
import QueryHandler from './handlers/QueryHandler.svelte'
import SelectHandler from './handlers/SelectHandler.svelte'
import PageHandler from './handlers/PageHandler.svelte'
import SearchHandler from './handlers/SearchHandler.svelte'
import type { Internationalization, Row, Field, Check, TableParams, ColumnView, TableHandlerInterface } from '$lib/src/client'
import ViewBuilder from '../shared/builders/ViewBuilder.svelte'
import SearchBuilder from './builders/SearchBuilder.svelte'
import FilterBuilder from './builders/FilterBuilder.svelte'
import QueryBuilder from './builders/QueryBuilder.svelte'
import AdvancedFilterBuilder from './builders/AdvancedFilterBuilder.svelte'
import CalculationBuilder from './builders/CalculationBuilder.svelte'
import SortBuilder from './builders/SortBuilder.svelte'
import CSVBuilder from './builders/CSVBuilder.svelte'
import RecordFilterBuilder from './builders/RecordFilterBuilder.svelte'
export default class TableHandler<T extends Row = any> extends AbstractTableHandler<T> implements TableHandlerInterface<T>
{
public i18n : Internationalization
private view : ViewBuilder<T>
private sortHandler : SortHandler<T>
private filterHandler : FilterHandler<T>
private queryHandler : QueryHandler<T>
private selectHandler : SelectHandler<T>
private pageHandler : PageHandler<T>
private searchHandler : SearchHandler<T>
constructor(data: T[] = [], params: TableParams<T> = { rowsPerPage: null })
{
super(data, params)
this.translate(params.i18n)
this.sortHandler = new SortHandler(this)
this.filterHandler = new FilterHandler(this)
this.queryHandler = new QueryHandler(this)
this.selectHandler = new SelectHandler(this)
this.pageHandler = new PageHandler(this)
this.searchHandler = new SearchHandler(this)
}
public setRows(data: T[]): void
{
const scrollTop = this.element?.scrollTop ?? 0
this.rawRows = data
untrack(() => {
this.event.dispatch('change')
this.sortHandler.restore()
if (this.element) {
setTimeout(() => this.element.scrollTop = scrollTop, 2)
}
})
}
public setRowsPerPage(value: number): void
{
this.rowsPerPage = value
this.setPage(1)
}
public setPage(value: number | 'previous' | 'next' | 'last'): void
{
switch (value) {
case 'previous' : return this.pageHandler.previous()
case 'next' : return this.pageHandler.next()
case 'last' : return this.pageHandler.last()
default : return this.pageHandler.goto(value as number)
}
}
public createSearch(scope?: Field<T>[]): SearchBuilder<T>
{
return new SearchBuilder(this.searchHandler, scope)
}
public clearSearch(): void
{
this.searchHandler.clear()
this.event.dispatch('clearSearch')
this.setPage(1)
}
public createRecordFilter(records?: Row[]): RecordFilterBuilder
{
return new RecordFilterBuilder(records)
}
public createSort(field: Field<T>, params?: { locales: Intl.LocalesArgument, options: Intl.CollatorOptions}): SortBuilder<T>
{
return new SortBuilder(this.sortHandler, field, params)
}
public clearSort()
{
this.sortHandler.clear()
}
public clearFilters(): void
{
this.filters = []
this.event.dispatch('clearFilters')
this.setPage(1)
}
public createAdvancedFilter(field: Field<T>, check?: Check): AdvancedFilterBuilder<T>
{
return new AdvancedFilterBuilder(this.filterHandler, field, check)
}
public createFilter(field: Field<T>, check?: Check): FilterBuilder<T>
{
return new FilterBuilder(this.filterHandler, field, check)
}
public createQuery(): QueryBuilder<T>
{
return new QueryBuilder(this.queryHandler)
}
public select(value: unknown): void
{
this.selectHandler.set(value)
}
public selectAll(params: { scope?: 'all' | 'currentPage' } = {}): void
{
this.selectScope = (params.scope === 'all') ? 'all' : 'currentPage'
this.selectHandler.all(this.selectScope)
}
public getSelectedRows(): T[]
{
return this.selectHandler.getRows()
}
public clearSelection(): void
{
this.selectHandler.clear()
}
public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: () => void)
{
this.event.add(event, callback)
}
public createCalculation(field: Field<T>): CalculationBuilder<T>
{
return new CalculationBuilder(this, field)
}
public createCSV(): CSVBuilder<T>
{
return new CSVBuilder(this)
}
public createView(columns: ColumnView[]): ViewBuilder<T>
{
this.view = new ViewBuilder(this, columns)
return this.view
}
public getView(): ViewBuilder<T>
{
return this.view
}
private translate(i18n: Internationalization): void
{
this.i18n = {
...{
search: 'Search...',
show: 'Show',
entries: 'entries',
filter: 'Filter',
rowCount: 'Showing {start} to {end} of {total} entries',
noRows: 'No entries found',
previous: 'Previous',
next: 'Next'
},
...i18n
}
}
}
================================================
FILE: src/lib/src/client/builders/AdvancedFilterBuilder.svelte.ts
================================================
import type { Field, Check, Criterion } from '$lib/src/client'
import type FilterHandler from '$lib/src/client/handlers/FilterHandler.svelte'
import { check as comparator } from '$lib/src/client/core'
export default class AdvancedFilterBuilder<Row>
{
public criteria = $state<unknown[]>([])
private id = Math.random().toString(36).substring(2, 15)
private filterHandler : FilterHandler<Row>
private collection : Criterion[]
private field : Field<Row>
private check : Check
private isRecursive = true
constructor(filterHandler: FilterHandler<Row>, field: Field<Row>, check?: Check)
{
this.filterHandler = filterHandler
this.field = field
this.collection = []
this.check = check ?? comparator.isEqualTo
this.cleanup()
}
public set(value: unknown, check?: Check): void
{
if (this.collection.find(criterion => criterion.value === value)) {
this.collection = this.collection.filter(criterion => criterion.value !== value)
}
else {
this.collection = [ { value, check: check ?? this.check }, ...this.collection ]
}
if (this.collection.length === 0) {
return this.clear()
}
this.filterHandler.set(this.collection, this.field, comparator.whereIn, this.id, this.isRecursive)
this.criteria = this.collection.map(criterion => criterion.value)
}
public isNotRecursive()
{
this.isRecursive = false
return this
}
public clear(): void
{
this.collection = []
this.criteria = []
this.filterHandler.unset(this.id)
}
private cleanup()
{
this.filterHandler['table'].on('clearFilters', () => this.clear())
}
}
================================================
FILE: src/lib/src/client/builders/CSVBuilder.svelte.ts
================================================
import type { TableHandler } from '$lib/src/client'
export default class CSVBuilder<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public download(filename: string)
{
const csv = this.get()
const element = document.createElement('a')
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(csv))
element.setAttribute('download', filename)
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
}
public get(): string
{
const rows = this.getRows()
rows.unshift(this.getHeader().join(','))
return rows.join('\r\n')
}
private getRows()
{
return this.table.allRows.map(row => {
const entries = Object.entries(row).map(([_, value]) => {
if (value === null) return ''
if (typeof value === 'number') {
return value
}
return `"${value}"`
})
return entries.join(',')
})
}
private getHeader()
{
const [row] = this.table.allRows
return Object.entries(row).map(([key, _]) => {
return `"${key}"`
})
}
}
================================================
FILE: src/lib/src/client/builders/CalculationBuilder.svelte.ts
================================================
import type { Field, TableHandler } from '$lib/src/client'
import { sort, parse } from '$lib/src/client/core'
type Sort = [key: 'value' | 'count', direction: 'asc' | 'desc']
export default class CalcultationBuilder<Row>
{
private callback : (row: $state.Snapshot<Row>) => string | number
private precision : number
private table : TableHandler<Row>
constructor(table: TableHandler<Row>, field: Field<Row>)
{
this.table = table
this.callback = parse(field).callback as (row: $state.Snapshot<Row>) => string | number
}
public distinct(param?: { sort: Sort }): { value: string, count: number }[]
{
const values = this.table.allRows.map(row => this.callback(row))
const aggregate: { [key: string ]: number } = values.reduce((acc, curr) => {
if (Array.isArray(curr)) {
for (const item of curr) {
acc[item] = (acc[item] ?? 0) + 1
}
return acc
}
acc[curr] = (acc[curr] ?? 0) + 1
return acc
}, {})
const result = Object.entries(aggregate).map(([value, count]) => ({ value, count }))
if (param?.sort) {
const [field, direction] = param.sort
if (field === 'count') {
result.sort((x, y) => sort.asc(x.value, y.value))
}
result.sort((a, b) => sort[direction](a[field], b[field]))
}
return result
}
public avg(param?: { precision: number }): number
{
this.precision = param?.precision ?? 2
if (this.table.allRows.length === 0) return 0
const values = this.table.allRows.map(row => this.callback(row)).filter(Boolean) as number[]
return this.round(values.reduce((acc, curr) => acc + curr, 0) / values.length)
}
public sum(param?: { precision: number }): number
{
this.precision = param?.precision ?? 2
const values = this.table.allRows.map(row => this.callback(row)) as number[]
return this.round(values.reduce((acc, curr) => acc + curr, 0))
}
public median(param?: { precision: number }): number
{
this.precision = param?.precision ?? 2
const values = [...this.table.allRows.map(row => this.callback(row))]
.sort((a: number, b: number) => a - b)
.filter(Boolean) as number[]
if (values.length === 0) return null
const half = Math.floor(values.length / 2)
return (values.length % 2
? values[half]
: this.round((values[half - 1] + values[half]) / 2)
)
}
public bounds(): number[]
{
const values = this.table.allRows.map(row => this.callback(row))
const numbers = values.filter(Boolean) as number[]
if (numbers.length === 0) return [null, null]
return [
Math.min(...numbers),
Math.max(...numbers)
]
}
private round(value: number)
{
return Number(value.toFixed(this.precision))
}
}
================================================
FILE: src/lib/src/client/builders/FilterBuilder.svelte.ts
================================================
import type { Field, Check } from '$lib/src/client'
import type FilterHandler from '../handlers/FilterHandler.svelte'
import { check as comparator } from '$lib/src/client/core'
import type { FilterInterface } from '$lib/src/shared'
export default class FilterBuilder<Row> implements FilterInterface
{
public value = $state<unknown>('')
private id = Math.random().toString(36).substring(2, 15)
private filterHandler : FilterHandler<Row>
private field : Field<Row>
private check : Check
private isRecursive = true
constructor(filterHandler: FilterHandler<Row>, field: Field<Row>, check?: Check)
{
this.filterHandler = filterHandler
this.field = field
this.check = check ?? comparator.isLike
this.cleanup()
}
public set(check?: Check)
{
this.filterHandler.set(this.value, this.field, check ?? this.check, this.id, this.isRecursive)
}
public init(value?: unknown)
{
this.value = value
this.set()
return this
}
public isNotRecursive()
{
this.isRecursive = false
return this
}
public clear()
{
this.value = ''
this.filterHandler.unset(this.id)
}
private cleanup()
{
this.filterHandler['table'].on('clearFilters', () => this.clear())
}
}
================================================
FILE: src/lib/src/client/builders/QueryBuilder.svelte.ts
================================================
import type { Check } from '$lib/src/client'
import type QueryHandler from '../handlers/QueryHandler.svelte'
export default class QueryBuilder<Row>
{
public value = $state<unknown>('')
private id = Math.random().toString(36).substring(2, 15)
private queryHandler : QueryHandler<Row>
private path : string[] = []
private check : Check
constructor(queryHandler: QueryHandler<Row>)
{
this.queryHandler = queryHandler
this.cleanup()
}
public from(path: string[])
{
this.path = path
return this
}
public where(filter: (row: any, value?: unknown) => boolean)
{
this.check = filter
return this
}
public set(value?: unknown)
{
if (value) this.value = value
this.queryHandler.set(this.path, this.value, this.check, this.id)
}
public clear()
{
this.value = ''
this.queryHandler.unset(this.id)
}
private cleanup()
{
this.queryHandler['table'].on('clearFilters', () => this.clear())
}
}
================================================
FILE: src/lib/src/client/builders/RecordFilterBuilder.svelte.ts
================================================
import { match, check, isNotNull } from '$lib/src/client/core'
import type { Row } from '$lib/src/client'
export default class RecordFilterBuilder
{
public value = $state<string>('')
public records = $derived<readonly Row[]>(this.createRecords())
private rawRecords = $state.raw<Row[]>([])
private filter = $state<string>('')
constructor(records: Row[])
{
this.rawRecords = records
}
public set()
{
this.filter = this.value
}
private createRecords(): readonly Row[]
{
if (isNotNull(this.filter)) {
return this.rawRecords.filter(record => match(record, this.filter, { check: check.isLike }))
}
return this.rawRecords
}
}
================================================
FILE: src/lib/src/client/builders/SearchBuilder.svelte.ts
================================================
import { type Field } from '$lib/src/client'
import type SearchHandler from '../handlers/SearchHandler.svelte'
import type { SearchInterface } from '$lib/src/shared'
export default class SearchBuilder<Row> implements SearchInterface
{
public value = $state<string>('')
private scope : Field<Row>[]
private searchHandler : SearchHandler<Row>
constructor(searchHandler: SearchHandler<Row>, scope?: Field<Row>[])
{
this.searchHandler = searchHandler
this.scope = scope
this.cleanup()
}
public set()
{
this.searchHandler.set(this.value, this.scope)
}
public init(value?: string)
{
this.value = value
this.set()
return this
}
public recursive()
{
this.searchHandler.recursive(this.value, this.scope)
}
public regex()
{
this.searchHandler.regex(this.value, this.scope)
}
public clear()
{
this.value = ''
this.searchHandler.clear()
}
private cleanup()
{
this.searchHandler['table'].on('clearSearch', () => this.clear())
}
}
================================================
FILE: src/lib/src/client/builders/SortBuilder.svelte.ts
================================================
import type { Field, SortParams } from '$lib/src/client'
import type SortHandler from '../handlers/SortHandler.svelte'
import type { SortInterface } from '$lib/src/shared'
export default class SortBuilder<Row> implements SortInterface
{
public direction = $derived<'asc' | 'desc'>(this.createDirection())
public isActive = $derived<boolean>(this.createIsActive())
private id = Math.random().toString(36).substring(2, 15)
private sortHandler : SortHandler<Row>
private field : Field<Row>
private params : SortParams
constructor(sortHandler: SortHandler<Row>, field: Field<Row>, params: SortParams)
{
this.sortHandler = sortHandler
this.field = field
this.params = params ?? {}
}
public set()
{
this.sortHandler.set(this.field, this.id, this.params)
}
public init(direction?: 'asc' | 'desc')
{
if (!direction) return this
this[direction]()
return this
}
public asc()
{
this.sortHandler.asc(this.field, this.id, this.params)
}
public desc()
{
this.sortHandler.desc(this.field, this.id, this.params)
}
public clear()
{
this.sortHandler.clear()
}
private createIsActive()
{
if (this.id === this.sortHandler['table']['sort']?.id) {
return true
}
return false
}
private createDirection()
{
if (this.isActive === false) return null
return this.sortHandler['table']['sort']?.direction
}
}
================================================
FILE: src/lib/src/client/core/check.ts
================================================
import { isNull, stringify, isNotNull } from './value'
import type { Criterion, Check } from '$lib/src/client'
export const check: { [name: string]: Check } = {
isLike: (entry: unknown, value: unknown) => stringify(entry).includes(stringify(value)),
isNotLike: (entry: unknown, value: unknown) => stringify(entry).includes(stringify(value)) === false,
startsWith: (entry: unknown, value: unknown) => stringify(entry).startsWith(stringify(value)),
endsWith: (entry: unknown, value: unknown) => stringify(entry).endsWith(stringify(value)),
isEqualTo: (entry: unknown, value: unknown) => stringify(entry) === stringify(value),
isNotEqualTo: (entry: unknown, value: unknown) => stringify(entry) !== stringify(value),
isGreaterThan: (entry: number, value: number) => isNull(entry) ? false : (entry > value),
isGreaterThanOrEqualTo: (entry: number, value: number) => isNull(entry) ? false : (entry >= value),
isLessThan: (entry: number, value: number) => isNull(entry) ? false : (entry < value),
isLessThanOrEqualTo: (entry: number, value: number) => isNull(entry) ? false : (entry <= value),
isBetween: (entry: number, [min, max]: number[]) => isNull(entry) ? false : (entry >= min && entry <= max),
isStrictlyBetween: (entry: number, [min, max]: number[]) => isNull(entry) ? false : (entry > min && entry < max),
isTrue: (entry: unknown, _: unknown) => entry === true,
isFalse: (entry: unknown, _: unknown) => entry === false,
isNull: (entry: unknown, _: unknown) => isNull(entry),
isNotNull: (entry: unknown, _: unknown) => isNotNull(entry),
// multiple criteria
whereIn: (entry: unknown, criteria: Criterion[] = []) => {
if (criteria.length === 0) return false
for(const { value, check } of criteria) {
if (value?.['key']) {
return checkByKey(entry, value as any, check)
}
else if (check(entry, value)) {
return true
}
}
return false
},
// regexp
match: (entry: unknown, pattern: string) => {
const match = pattern.match(/^([\/~@;%#'])(.*?)\1([gimsuy]*)$/)
const regex = match ? new RegExp(match[2], match[3]
.split('')
.filter((char, pos, flagArr) => flagArr.indexOf(char) === pos)
.join('')
)
: new RegExp(pattern)
return stringify(entry).match(regex) ? true : false
},
}
const checkByKey = (entry: unknown, param: { key: string, value: unknown }, check: Check) => {
if (!param?.key) return false
const { key, value } = param
if (Array.isArray(entry) === false && typeof entry === 'object') {
const keys = Object.keys(entry)
if (keys.includes(key) && check(entry[key], value)) {
return true
}
}
return false
}
================================================
FILE: src/lib/src/client/core/entry.ts
================================================
import { isNull, isObject, isObjectArray } from './value'
import { check } from './check'
import type { Criterion, Check } from '$lib/src/client'
type Params = {
isRecursive?: boolean,
check?: Check,
highlight?: boolean,
}
export const match = (entry: unknown, value: unknown | Criterion[], params: Params): boolean => {
params.check = params.check ?? check.isLike
if (isNull(value)) {
return true
}
else if (isObjectArray(entry)) {
// if (isObject(entry) && entry.is_map === true) return true
return Object.keys(entry).some(k => match(entry[k], value, params))
}
return params.check(entry, value)
}
export const sift = (entry: unknown, value: unknown, params: Params) => {
if (Array.isArray(entry)) {
entry = entry.filter((item: unknown) => {
// to disable deletion while filtering, check is worth true anyway
const check = params.isRecursive === true ? match(item, value, params) : true
if (typeof item === 'object' && check === true) {
for (const k of Object.keys(item)) {
item[k] = sift(item[k], value, params)
}
}
return check
})
}
if (params.highlight && (typeof entry === 'string' || typeof entry === 'number') && typeof value === 'string' && match(entry, value, params)) {
return emphasize(entry, value)
}
return entry
}
const emphasize = (entry: string | number, value: string) => {
const search = value
.replace(/a/g, '[aàâáä]')
.replace(/e/g, '[eèêéë]')
.replace(/i/g, '[iìîíï]')
.replace(/o/g, '[oòôо́ö]')
.replace(/u/g, '[uùûúü]')
.replace(/y/g, '[yỳŷýÿ]')
const exp = new RegExp(`${search}`, 'gi')
return String(entry).replace(exp, `<u class="highlight">$&</u>`)
}
export const deepEmphasize = <Row>(entry: Row, value: string, callback: (entry: Row) => unknown) => {
const path = callback.toString()
.split('=>')[1]
.replace(/\(\)/g, '')
.replace(/\?/g, '')
.split('.').splice(1).join('.')
.trim()
if (path.indexOf(' ') > -1) return entry
return deepSet(entry, path, value)
}
const deepSet = <Row>(entry: Row, path: string, value: string) => {
const initial = entry
const keys = path.replace(/\[/g, '.[').split(".")
try {
for (let i = 0; i < keys.length; i++) {
let current = keys[i]
let next = keys[i + 1]
if (current.includes('[')) {
current = String(parseInt(current.substring(1, current.length - 1)))
}
if (next && next.includes('[')) {
next = String(parseInt(next.substring(1, next.length - 1)))
}
if (next !== undefined) {
entry[current] = entry[current] ? entry[current] : (isNaN(Number(next)) ? {} : [])
} else {
entry[current] = emphasize(entry[current] as string | number, value)
}
entry = entry[current] as Row
}
return initial
} catch(_) {
return initial
}
}
================================================
FILE: src/lib/src/client/core/field.ts
================================================
import type { Field } from '$lib/src/client'
export const parse = <Row>(field: Field<Row>, id?: string) => {
if (typeof field === 'string') {
return {
callback: (row: $state.Snapshot<Row>) => row[(field as keyof $state.Snapshot<Row>)],
id: id,
key: field,
}
} else if (typeof field === 'function') {
return {
callback: field as (row: $state.Snapshot<Row>) => unknown,
id: id,
key: undefined,
}
}
throw new Error(`Invalid field argument: ${String(field)}`)
}
================================================
FILE: src/lib/src/client/core/index.ts
================================================
import type { Field, Check } from '$lib/src/client'
export { isNull, isNotNull, stringify } from './value'
export { match, sift, deepEmphasize } from './entry'
export { check } from './check'
export { data, sort } from './rows'
export { parse } from './field'
export type Search<Row> = {
value: string
scope?: Field<Row>[]
isRecursive?: boolean
check?: Check
}
export type Filter<Row> = {
callback: (row: $state.Snapshot<Row>) => unknown
id: string
value?: unknown
isRecursive?: boolean
check?: Check
key?: string
}
export type Query<Row> = {
path: string[]
id: string
value?: unknown
check?: Check
}
export type Sort<Row> = {
callback?: (row: $state.Snapshot<Row>) => unknown
id?: string
direction?: 'asc' | 'desc'
key?: string,
}
================================================
FILE: src/lib/src/client/core/rows.ts
================================================
import type { Field } from '$lib/src/client'
import { type Search, type Filter, type Query } from '$lib/src/client/core'
import { match, sift, deepEmphasize } from './entry'
import { isNull } from './value'
import { parse } from './field'
export const data = {
search: <Row>(allRows: $state.Snapshot<Row[]>, { scope, isRecursive, value, check }: Search<Row>, highlight: boolean = false) => {
return allRows.filter(row => {
const keys = scope ?? Object.keys(row) as Field<Row>[]
const fields = keys.map(field => parse(field))
for(const { key, callback } of fields) {
if (key) {
row[key] = sift(row[key], value, {
highlight: highlight,
isRecursive: isRecursive === true
})
}
else if (highlight) {
row = deepEmphasize(row, value, callback)
}
}
return fields.some(({ callback }) => {
return match(callback(row), value, { check })
})
})
},
filter: <Row>(allRows: $state.Snapshot<Row[]>, { callback, isRecursive, value, check, key }: Filter<Row>, highlight: boolean = false) => {
return allRows.filter((row) => {
const checked = match(callback(row), value, { check })
if (key) {
row[key] = sift(row[key], value, {
highlight: highlight,
check: check,
isRecursive: isRecursive === false ? false : true
})
}
else if (highlight && checked && value && typeof value === 'string') {
row = deepEmphasize(row, value, callback)
}
return checked
})
},
query: <Row>(allRows: $state.Snapshot<Row[]>, { path, value, check }: Query<Row>) => {
return allRows.filter(row => {
if (path.length === 0) {
return check(row, value)
}
let obj = row
let i = 1
let verify = false
function recursive(i) {
}
// ['groups', 'users']
// row[groups].map(group => group.users.map(user) => {
// })
// row.groups.filter(group => group.users.filter(user => ))
for (const prop of path) {
if (i === 1 && i < path.length) {
obj = obj[prop]
for (const subobj of obj[prop]) {
}
}
if (i === path.length) {
console.log(prop, obj)
obj[prop] = data.query(obj[prop], { path: [], value, check } as Query<Row>)
if (obj[prop].length > 0) {
verify = true
}
}
obj = obj[prop]
i++
}
return verify
})
// return allRows.map(row => {
// let obj = row
// const [root, ...props] = path
// let i = 1
// for (const prop of path) {
// if (obj[prop]) {
// if (obj[root]) {
// obj[root] = data.query(obj[root], { path, value, check } as Query<Row>)
// }
// if (i < props.length) {
// obj[prop] = data.query(obj[prop], { path, value, check } as Query<Row>)
// }
// else {
// obj[prop] = obj[prop].filter((item: any) => check(item))
// }
// }
// i++
// }
// return row
// }).filter(Boolean)
}
}
export const sort = {
asc: (a: unknown, b: unknown, locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions) => {
if (a === b) return 0
else if (isNull(a)) return 1
else if (isNull(b)) return -1
else if (typeof a === 'boolean') return a === false ? 1 : -1
else if (typeof a === 'string') return a.localeCompare(b as string, locales, options)
else if (typeof a === 'number') return a - (b as number)
else if (typeof a === 'object') return JSON.stringify(a).localeCompare(JSON.stringify(b), locales, options)
return String(a).localeCompare(String(b), locales, options)
},
desc: (a: unknown, b: unknown, locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions) => {
if (a === b) return 0
else if (isNull(a)) return 1
else if (isNull(b)) return -1
else if (typeof b === 'boolean') return b === false ? 1 : -1
else if (typeof b === 'string') return b.localeCompare(a as string, locales, options)
else if (typeof b === 'number') return b - (a as number)
else if (typeof b === 'object') return JSON.stringify(b).localeCompare(JSON.stringify(a), locales, options)
else return String(b).localeCompare(String(a), locales, options)
}
}
================================================
FILE: src/lib/src/client/core/value.ts
================================================
export const isNull = (value: unknown) => {
if (value === null || value === undefined || value === '') return true
return false
}
export const isNotNull = (value: unknown) => { return !isNull(value) }
export const stringify = (value: unknown = null) => {
return String(value)
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
}
export const isObject = (value: unknown) => {
if (typeof value !== 'object') return false
else if (value === null) return false
else if (Array.isArray(value)) return false
return true
}
export const isObjectArray = (value: unknown) => {
if (typeof value !== 'object') return false
else if (value === null) return false
// test
else if (Array.isArray(value)) {
if (isNotNull(value[0]) && typeof value[0] !== 'object') return false
}
return true
}
================================================
FILE: src/lib/src/client/handlers/FilterHandler.svelte.ts
================================================
import type { Field, Check, TableHandler } from '$lib/src/client'
import { isNotNull, parse } from '$lib/src/client/core'
export default class FilterHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(value: unknown, field: Field<Row>, check: Check = null, id: string, isRecursive = true)
{
this.table.setPage(1)
const { callback, key } = parse<Row>(field, id)
const filter = { value, id, callback, check, key, isRecursive }
this.table.filters = this.table.filters.filter(filter => filter.id !== id)
if (isNotNull(value)) {
this.table.filters.push(filter)
}
}
public unset(id: string)
{
this.table.setPage(1)
this.table.filters = this.table.filters.filter(filter => filter.id !== id)
}
}
================================================
FILE: src/lib/src/client/handlers/PageHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/client'
export default class PageHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public goto(page: number)
{
if (this.table.rowsPerPage) {
if (page >= 1 && page <= this.table.pageCount) {
this.table.currentPage = page
this.table['event'].dispatch('change')
}
}
}
public previous()
{
this.goto(this.table.currentPage - 1)
}
public next()
{
this.goto(this.table.currentPage + 1)
}
public last()
{
this.goto(this.table.pageCount)
}
}
================================================
FILE: src/lib/src/client/handlers/QueryHandler.svelte.ts
================================================
import type { Field, Check, TableHandler } from '$lib/src/client'
import { isNotNull } from '$lib/src/client/core'
export default class QueryHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(path: string[], value: unknown, check: Check, id: string)
{
this.table.setPage(1)
this.table.queries = this.table.queries.filter(query => query.id !== id)
this.table.queries.push({ path, value, check, id })
}
public unset(id: string)
{
this.table.setPage(1)
this.table.queries = this.table.queries.filter(query => query.id !== id)
}
}
================================================
FILE: src/lib/src/client/handlers/SearchHandler.svelte.ts
================================================
import { type TableHandler, type Field, type Criterion, check } from '$lib/src/client'
export default class SearchHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(value: string, scope?: Field<Row>[])
{
this.table.setPage(1)
this.table['search'] = { value: value, scope: scope }
}
public recursive(value: string, scope?: Field<Row>[])
{
this.table.setPage(1)
this.table['search'] = { value: value, scope: scope, isRecursive: true }
}
public regex(pattern: string, scope?: Field<Row>[])
{
this.table.setPage(1)
this.table['search'] = { value: pattern, scope: scope, check: check.match }
}
public clear()
{
this.table.setPage(1)
this.table['search'] = { value: '' }
}
}
================================================
FILE: src/lib/src/client/handlers/SelectHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/client'
import { parse } from '$lib/src/client/core'
export default class SelectHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(value: unknown)
{
if (this.table.selected.includes(value)) {
this.table.selected = this.table.selected.filter((item) => item !== value)
} else {
this.table.selected = [value, ...this.table.selected]
}
}
public all(scope: 'all' | 'currentPage')
{
const rows = (scope === 'currentPage') ? this.table.rows : this.table.allRows
const { callback } = parse(this.table['selectBy'])
const selection = rows.map(callback)
if (scope === 'currentPage') {
if (this.table.isAllSelected) {
this.table.selected = this.table.selected.filter(item => selection.includes(item) === false)
}
else {
this.table.selected = [...new Set([...selection, ...this.table.selected])]
}
}
else {
this.table.isAllSelected ? this.clear() : this.table.selected = selection
}
}
public clear()
{
this.table.selected = []
}
public getRows()
{
const { callback } = parse(this.table['selectBy'])
return this.table['rawRows'].filter(row => {
return this.table.selected.includes(callback(row as $state.Snapshot<Row>))
})
}
}
================================================
FILE: src/lib/src/client/handlers/SortHandler.svelte.ts
================================================
import type { Field, TableHandler, SortParams } from '$lib/src/client'
import { type Sort, parse, sort } from '$lib/src/client/core'
export default class SortHandler<Row>
{
private backup : Sort<Row>[]
private table : TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
this.backup = []
}
public set(field: Field<Row>, id: string, params: SortParams = {})
{
if (this.table['sort'].id !== id) {
this.table['sort'].direction = null
}
if (this.table['sort'].direction === null || this.table['sort'].direction === 'desc') {
this.asc(field, id, params)
}
else if (this.table['sort'].direction === 'asc') {
this.desc(field, id, params)
}
}
public asc(field: Field<Row>, id: string, { locales, options }: SortParams = {})
{
if (!field) return
const { callback, key } = parse(field, id)
this.table['sort'] = { id, callback, direction: 'asc', key }
this.table['rawRows'] = [...this.table['rawRows']].sort((x, y) => {
const [a, b] = [callback(x as $state.Snapshot<Row>), callback(y as $state.Snapshot<Row>)]
return sort.asc(a, b, locales, options)
})
this.save({ id, callback, direction: 'asc' })
this.table.setPage(1)
}
public desc(field: Field<Row>, id: string, { locales, options }: SortParams = {})
{
if (!field) return
const { callback, key } = parse(field, id)
this.table['sort'] = { id, callback, direction: 'desc', key }
this.table['rawRows'] = [...this.table['rawRows']].sort((x, y) => {
const [a, b] = [callback(x as $state.Snapshot<Row>), callback(y as $state.Snapshot<Row>)]
return sort.desc(a, b, locales, options)
})
this.save({ id, callback, direction: 'desc' })
this.table.setPage(1)
}
public clear()
{
this.backup = []
this.table['sort'] = {}
}
public restore()
{
for (const { key, callback, direction, id } of this.backup) {
const field = (key ?? callback) as Field<Row>
this[direction](field, id)
}
}
private save(sort: Sort<Row>)
{
this.backup = this.backup.filter(item => item.id !== sort.id )
if (this.backup.length >= 3) {
const [_, slot2, slot3] = this.backup
this.backup = [slot2, slot3, sort]
}
else {
this.backup = [...this.backup, sort]
}
}
}
================================================
FILE: src/lib/src/client/index.ts
================================================
export { default as TableHandler } from './TableHandler.svelte'
export { check } from './core'
import type { Internationalization, Field } from '$lib/src/shared'
export type { default as AdvancedFilterBuilder } from './builders/AdvancedFilterBuilder.svelte'
export type { default as CalculationBuilder } from './builders/CalculationBuilder.svelte'
export type { default as CSVBuilder } from './builders/CSVBuilder.svelte'
export type { default as FilterBuilder } from './builders/FilterBuilder.svelte'
export type { default as RecordFilterBuilder } from './builders/RecordFilterBuilder.svelte'
export type { default as SearchBuilder } from './builders/SearchBuilder.svelte'
export type { default as SortBuilder } from './builders/SortBuilder.svelte'
export { default as RecordFilter } from './builders/RecordFilterBuilder.svelte'
export {
Datatable,
Search,
RowsPerPage,
Th,
ThSort,
ThFilter,
Pagination,
RowCount,
type Row,
type Field,
type Internationalization,
type ColumnView,
type TableHandlerInterface,
} from '$lib/src/shared'
export type SortParams = {
locales?: Intl.LocalesArgument,
options?: Intl.CollatorOptions
}
export type Check = (entry: unknown, value: unknown) => boolean
export type TableParams<Row> = {
rowsPerPage?: number,
selectBy?: Field<Row>,
highlight?: boolean,
i18n?: Internationalization,
}
export type Criterion = {
value: unknown,
check: Check
}
================================================
FILE: src/lib/src/server/AbstractTableHandler.svelte.ts
================================================
import type { State, Sort, Filter, TableParams } from '$lib/src/server'
import { EventDispatcher } from '$lib/src/shared'
export default class AbstractTableHandler<Row>
{
protected selectBy ?: keyof Row
protected event = new EventDispatcher()
protected search = $state<string>('')
protected sort = $state<(Sort<Row>)>({})
public debounce : number
public totalRows = $state<number>(undefined)
public isLoading = $state<boolean>(false)
public rowsPerPage = $state<number>(10)
public currentPage = $state<number>(1)
public filters = $state<(Filter<Row>)[]>([])
public filterCount = $derived<number>(this.filters.length)
public rows = $state<Row[]>([])
public rowCount = $derived<{total: number, start: number, end: number, selected: number}>(this.createRowCount())
public pages = $derived<number[]>(this.createPages())
public pageCount = $derived<number>(this.createPageCount())
public pagesWithEllipsis = $derived<number[]>(this.createPagesWithEllipsis())
public selected = $state<(Row[keyof Row])[]>([])
public isAllSelected = $derived<boolean>(this.createIsAllSelected())
public element = $state<HTMLElement>(undefined)
public clientWidth = $state<number>(1000)
constructor(data: Row[], params: TableParams<Row>)
{
this.rows = data
this.selectBy = params.selectBy as keyof Row ?? undefined
this.totalRows = params.totalRows
this.rowsPerPage = params.rowsPerPage ?? 10
this.debounce = params.debounce ?? 0
}
public getState(): State<Row>
{
return {
currentPage: this.currentPage,
rowsPerPage: this.rowsPerPage,
offset: this.rowsPerPage * (this.currentPage - 1),
search: this.search,
sort: this.sort.field ? this.sort : undefined,
filters: this.filters.length > 0 ? this.filters : undefined,
setTotalRows: (value: number) => this.totalRows = value
}
}
private createPages()
{
if (!this.rowsPerPage || !this.totalRows) {
return undefined
}
const pages = Array.from(Array(Math.ceil(this.totalRows / this.rowsPerPage)))
return pages.map((_, i) => i + 1)
}
private createPageCount()
{
if (!this.pages) return undefined
return this.pages.length
}
private createPagesWithEllipsis()
{
if (!this.pages) {
return undefined
}
if (this.pageCount <= 7) {
return this.pages
}
const ellipse = null
const firstPage = 1
const lastPage = this.pageCount
if (this.currentPage <= 4) {
return [
...this.pages.slice(0, 5),
ellipse,
lastPage
]
} else if (this.currentPage < this.pageCount - 3) {
return [
firstPage,
ellipse,
...this.pages.slice(this.currentPage - 2, this.currentPage + 1),
ellipse,
lastPage
]
} else {
return [
firstPage,
ellipse,
...this.pages.slice(this.pageCount - 5, this.pageCount)
]
}
}
private createRowCount()
{
if (!this.rowsPerPage || !this.totalRows) {
return {
total: undefined,
start: undefined,
end: undefined,
selected: this.selected.length
}
}
return {
total: this.totalRows,
start: this.currentPage * this.rowsPerPage - this.rowsPerPage + 1,
end: Math.min(this.currentPage * this.rowsPerPage, this.totalRows),
selected: this.selected.length
}
}
private createIsAllSelected()
{
if (this.rows.length === 0) {
return false
}
const ids = this.rows.map(row => row[this.selectBy])
return ids.every(id => this.selected.includes(id))
}
}
================================================
FILE: src/lib/src/server/TableHandler.svelte.ts
================================================
import AbstractTableHandler from './AbstractTableHandler.svelte'
import FetchHandler from './handlers/FetchHandler.svelte'
import SortHandler from './handlers/SortHandler.svelte'
import SelectHandler from './handlers/SelectHandler.svelte'
import PageHandler from './handlers/PageHandler.svelte'
import SearchHandler from './handlers/SearchHandler.svelte'
import FilterHandler from './handlers/FilterHandler.svelte'
import ViewBuilder from '../shared/builders/ViewBuilder.svelte'
import SearchBuilder from './builders/SearchBuilder.svelte'
import SortBuilder from './builders/SortBuilder.svelte'
import FilterBuilder from './builders/FilterBuilder.svelte'
import type { Internationalization, Row, Field, State, ColumnView, TableParams } from '$lib/src/server'
import type { TableHandlerInterface } from '$lib/src/shared'
export default class TableHandler<T extends Row = any> extends AbstractTableHandler<T> implements TableHandlerInterface<T>
{
private fetchHandler : FetchHandler<T>
private sortHandler : SortHandler<T>
private selectHandler : SelectHandler<T>
private pageHandler : PageHandler<T>
private searchHandler : SearchHandler<T>
private filterHandler : FilterHandler<T>
private view : ViewBuilder<T>
public i18n : Internationalization
constructor(data: T[] = [], params: TableParams<T> = { rowsPerPage: 5 })
{
super(data, params)
this.i18n = this.translate(params.i18n)
this.fetchHandler = new FetchHandler(this)
this.sortHandler = new SortHandler(this)
this.selectHandler = new SelectHandler(this)
this.pageHandler = new PageHandler(this)
this.searchHandler = new SearchHandler(this)
this.filterHandler = new FilterHandler(this)
}
public load(callback: (state: State<T>) => Promise<T[]>): void
{
this.fetchHandler.set(callback)
}
public invalidate(): void
{
this.fetchHandler.invalidate()
}
public setRowsPerPage(value: number)
{
this.rowsPerPage = value
this.setPage(1)
}
public setPage(value: number | 'previous' | 'next' | 'last'): void
{
switch (value) {
case 'previous' : return this.pageHandler.previous()
case 'next' : return this.pageHandler.next()
case 'last' : return this.pageHandler.goto(this.pageCount)
default : return this.pageHandler.goto(value as number)
}
}
public clearSearch(): void
{
this.searchHandler.clear()
}
public createSearch(): SearchBuilder<T>
{
return new SearchBuilder(this)
}
public createSort(field: Field<T>): SortBuilder<T>
{
if (typeof field === 'function') {
throw new Error(`Invalid field argument: ${String(field)}. Function type arguments are not allowed in server-side mode`)
}
return new SortBuilder(this.sortHandler, field)
}
public clearFilters(): void
{
this.filterHandler.clear()
this.invalidate()
}
public createFilter(field: Field<T>): FilterBuilder<T>
{
if (typeof field === 'function') {
throw new Error(`Invalid field argument: ${String(field)}. Function type arguments are not allowed in server-side mode`)
}
return new FilterBuilder(this.filterHandler, field)
}
public select(value: T[keyof T])
{
this.selectHandler.set(value)
}
public selectAll(): void
{
this.selectHandler.all()
}
public clearSelection(): void
{
this.selectHandler.clear()
}
public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: () => void): void
{
this.event.add(event, callback)
}
public createView(columns: ColumnView[]): ViewBuilder<T>
{
this.view = new ViewBuilder(this, columns)
return this.view
}
public getView(): ViewBuilder<T>
{
return this.view
}
private translate(i18n: Internationalization): Internationalization
{
return {
...{
search: 'Search...',
show: 'Show',
entries: 'entries',
filter: 'Filter',
rowCount: 'Showing {start} to {end} of {total} entries',
noRows: 'No entries found',
previous: 'Previous',
next: 'Next',
selectedCount: '{count} of {total} row(s).'
},
...i18n
}
}
}
================================================
FILE: src/lib/src/server/builders/FilterBuilder.svelte.ts
================================================
import type FilterHandler from '../handlers/FilterHandler.svelte'
export default class FilterBuilder<Row>
{
public value = $state<string>('')
private timeout = undefined
private filterHandler : FilterHandler<Row>
private field : keyof Row
constructor(filterHandler: FilterHandler<Row>, field: keyof Row)
{
this.filterHandler = filterHandler
this.field = field
this.cleanup()
}
public set()
{
this.filterHandler.set(this.value, this.field)
clearTimeout(this.timeout)
this.timeout = setTimeout( () => {
this.filterHandler['table'].setPage(1)
}, 400)
}
public init(value?: string)
{
if (!value) return this
this.value = value
this.filterHandler.set(this.value, this.field)
return this
}
public clear()
{
this.value = ''
this.filterHandler.unset(this.field)
}
private cleanup()
{
this.filterHandler['table'].on('clearFilters', () => this.clear())
}
}
================================================
FILE: src/lib/src/server/builders/SearchBuilder.svelte.ts
================================================
import type TableHandler from '../TableHandler.svelte'
import type { SearchInterface } from '$lib/src/shared'
export default class SearchBuilder<Row> implements SearchInterface
{
public value = $state<string>('')
private timeout = undefined
private table : TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
this.cleanup()
}
public set()
{
this.table['search'] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout( () => {
this.table.setPage(1)
}, 400)
}
public init(value?: string)
{
if (!value) return this
this.value = value
this.table['search'] = value
return this
}
public clear()
{
this.value = ''
this.table['search'] = ''
// this.table.invalidate()
this.table.setPage(1)
}
private cleanup()
{
this.table.on('clearSearch', () => this.clear())
}
}
================================================
FILE: src/lib/src/server/builders/SortBuilder.svelte.ts
================================================
import type SortHandler from '../handlers/SortHandler.svelte'
import type { SortInterface } from '$lib/src/shared'
export default class SortBuilder<Row> implements SortInterface
{
private sortHandler : SortHandler<Row>
private field : keyof Row
public isActive = $derived<boolean>(this.createIsActive())
public direction = $derived<'asc' | 'desc'>(this.createDirection())
constructor(sortHandler: SortHandler<Row>, field: keyof Row)
{
this.sortHandler = sortHandler
this.field = field
}
public set()
{
this.sortHandler.set(this.field)
}
public init(direction?: 'asc' | 'desc')
{
if (!direction) return this
this[direction]()
return this
}
public asc()
{
this.sortHandler.asc(this.field)
}
public desc()
{
this.sortHandler.desc(this.field)
}
private createIsActive()
{
if (this.field === this.sortHandler['table']['sort']?.field) {
return true
}
return false
}
private createDirection()
{
if (this.isActive === false) return null
return this.sortHandler['table']['sort']?.direction
}
}
================================================
FILE: src/lib/src/server/handlers/FetchHandler.svelte.ts
================================================
import type { State, TableHandler } from '$lib/src/server'
export default class FetchHandler<Row>
{
private table: TableHandler<Row>
private reload: (state: State) => Promise<Row[]>
private timeout: NodeJS.Timeout
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(callback: (state: State) => Promise<Row[]>)
{
this.reload = callback
}
public async invalidate()
{
if (!this.reload) return
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.trigger(), this.table.debounce)
}
private async trigger()
{
this.table.isLoading = true
const state = this.table.getState()
const data = await this.reload(state)
this.table.isLoading = false
if (data) {
this.table.rows = data
}
}
}
================================================
FILE: src/lib/src/server/handlers/FilterHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/server'
export default class FilterHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(value: string | number, field: keyof Row)
{
this.table.filters = this.table.filters.filter(filter => filter.field !== field && filter.value)
if (value) {
this.table.filters.push({ value, field })
}
}
public unset(field: keyof Row)
{
this.table.filters = this.table.filters.filter(filter => filter.field !== field)
}
public clear()
{
this.table.filters = []
this.table['event'].dispatch('clearFilters')
}
}
================================================
FILE: src/lib/src/server/handlers/PageHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/server'
export default class PageHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public goto(number: number)
{
if (this.table.rowsPerPage && this.table.totalRows) {
if (number >= 1 && number <= this.table.pageCount) {
this.table.currentPage = number
this.table['event'].dispatch('change')
this.table.invalidate()
}
}
else {
if (number >= 1) {
this.table.currentPage = number
this.table['event'].dispatch('change')
this.table.invalidate()
}
}
}
public previous()
{
this.goto(this.table.currentPage - 1)
}
public next()
{
this.goto(this.table.currentPage + 1)
}
}
================================================
FILE: src/lib/src/server/handlers/SearchHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/server'
export default class SearchHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>) {
this.table = table
}
public clear()
{
this.table['event'].dispatch('clearSearch')
}
}
================================================
FILE: src/lib/src/server/handlers/SelectHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/server'
export default class SelectHandler<Row>
{
private table: TableHandler<Row>
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(value: Row[keyof Row])
{
if (this.table.selected.includes(value)) {
this.table.selected = this.table.selected.filter((item) => item !== value)
}
else {
this.table.selected = [value, ...this.table.selected]
}
}
public all()
{
const selection = this.table.rows.map((row) => row[this.table['selectBy']])
if (this.table.isAllSelected) {
this.table.selected = this.table.selected.filter(item => selection.includes(item) === false)
}
else {
this.table.selected = [...new Set([...selection, ...this.table.selected])]
}
}
public clear()
{
this.table.selected = []
}
}
================================================
FILE: src/lib/src/server/handlers/SortHandler.svelte.ts
================================================
import type { TableHandler } from '$lib/src/server'
export default class SortHandler<Row>
{
private table: TableHandler
constructor(table: TableHandler<Row>)
{
this.table = table
}
public set(field: keyof Row)
{
const sort = this.table['sort']
if(!sort || sort.field !== field) {
this.asc(field)
}
else if (sort.direction === 'asc') {
this.desc(field)
}
else if (sort.direction === 'desc') {
this.asc(field)
}
}
public asc(field: keyof Row)
{
this.table['sort'] = { field, direction: 'asc' }
this.table.setPage(1)
}
public desc(field: keyof Row)
{
this.table['sort'] = { field, direction: 'desc' }
this.table.setPage(1)
}
}
================================================
FILE: src/lib/src/server/index.ts
================================================
export { default as TableHandler } from './TableHandler.svelte'
import type { Row, Internationalization } from '$lib/src/shared/index.js'
export {
Datatable,
Search,
RowsPerPage,
Th,
ThSort,
ThFilter,
Pagination,
RowCount,
type Row,
type Field,
type ColumnView,
type Internationalization,
type TableHandlerInterface
} from '$lib/src/shared'
export type State<T extends Row = any> = {
currentPage : number,
rowsPerPage : number,
offset : number,
search ?: string,
sort ?: Sort<T>
filters ?: Filter<T>[]
setTotalRows: (value: number) => void
}
export type TableParams<Row> = {
rowsPerPage ?: number,
totalRows ?: number,
selectBy ?: keyof Row,
debounce ?: number
i18n ?: Internationalization
}
export type Filter<Row> = {
field: keyof Row
value?: unknown
}
export type Sort<Row> = {
field?: keyof Row
direction?: 'asc' | 'desc'
}
================================================
FILE: src/lib/src/shared/Datatable.svelte
================================================
<script lang="ts">
import type { Snippet } from 'svelte'
import { type TableHandlerInterface, Search, RowsPerPage, RowCount, Pagination } from '$lib/src/shared'
type T = $$Generic<Row>
interface Props {
table : TableHandlerInterface<T>
children : Snippet
basic ?: boolean
headless?: boolean
header ?: Snippet
footer ?: Snippet
}
let { table, children, basic = false, headless = false, header = undefined, footer = undefined }: Props = $props()
table.on('change', () => table.element ? table.element.scrollTop = 0 : '')
</script>
<section bind:clientWidth={table.clientWidth} class:svelte-simple-datatable={!headless}>
<header>
{#if header}
{@render header()}
{:else if basic === true}
<Search {table}/>
<RowsPerPage {table}/>
{/if}
</header>
<article bind:this={table.element} class="thin-scrollbar">
{@render children()}
</article>
<footer class:divider={basic}>
{#if footer}
{@render footer()}
{:else if basic === true}
<RowCount {table}/>
<Pagination {table}/>
{/if}
</footer>
</section>
<style>
section {
height: 100%;
display: flex;
flex-direction: column;
background: inherit;
border-radius: inherit;
}
header, footer {
padding: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
article {
position: relative;
height: 100%;
overflow: auto;
background: inherit;
}
article :global(.hidden) {
display: none;
}
article::-webkit-scrollbar {
width: 6px;
height: 6px;
}
article :global(table) {
border-collapse: separate;
border-spacing: 0;
width: 100%;
background: inherit;
}
article :global(table thead) {
position: sticky;
inset-block-start: 0;
background: inherit;
z-index: 1;
}
article :global(thead tr) {
background: inherit;
}
article :global(thead tr th) {
background: inherit;
}
/* optional global style */
.svelte-simple-datatable :global(thead tr:first-child th) {
padding: 8px 20px;
background: inherit;
}
.svelte-simple-datatable :global(tbody) {
background: inherit;
}
.svelte-simple-datatable :global(tbody tr) {
transition: background, 0.2s;
background: inherit;
}
.svelte-simple-datatable :global(tbody tr:hover) {
background: var(--grey-lighten-3, #fafafa);
}
.svelte-simple-datatable :global(tbody td) {
padding: 4px 20px;
border-right: 1px solid var(--grey-lighten, #eee);
border-bottom: 1px solid var(--grey-lighten, #eee);
background: inherit;
}
.svelte-simple-datatable :global(tbody td:last-child) {
border-right: none;
}
.svelte-simple-datatable :global(u.highlight) {
text-decoration: none;
background: rgba(251, 192, 45, 0.6);
border-radius: 2px;
}
.svelte-simple-datatable :global(footer.divider) {
border-top: 1px solid var(--grey, #e0e0e0);
}
</style>
================================================
FILE: src/lib/src/shared/EventDispatcher.ts
================================================
export default class EventDispatcher
{
private listeners = {
change : [] as (() => void)[],
clearFilters: [] as (() => void)[],
clearSearch : [] as (() => void)[]
}
private queue: Set<string> = new Set()
public add(event: keyof EventDispatcher['listeners'], callback: () => void)
{
this.listeners[event].push(callback)
}
public async dispatch(event: keyof EventDispatcher['listeners'])
{
this.queue.add(event)
await new Promise((resolve) => setTimeout(resolve, 40))
if (this.queue.size > 0) {
this.run()
}
this.queue.clear()
}
private run()
{
for (const event of this.queue) {
for (const callback of this.listeners[event]) {
callback()
}
}
// console.log(this.queue)
}
}
================================================
FILE: src/lib/src/shared/Pagination.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface } from '$lib/src/shared'
type T = $$Generic<Row>
let { table }: { table: TableHandlerInterface<T> } = $props()
</script>
<section>
{#if table.pages === undefined}
{@render nopage()}
{:else if table.clientWidth < 600}
{@render small()}
{:else}
{@render ellipsis()}
{/if}
</section>
{#snippet nopage()}
<button type="button" class="small" class:disabled={table.currentPage === 1} onclick={() => table.setPage('previous')}>
❮
</button>
<button type="button" class="page">Page <b>{table.currentPage}</b></button>
<button type="button" class="small" onclick={() => table.setPage('next')}>
❯
</button>
{/snippet}
{#snippet small()}
<button type="button" class="small" class:disabled={table.currentPage === 1} onclick={() => table.setPage(1)}>
❬❬
</button>
<button type="button" class:disabled={table.currentPage === 1} onclick={() => table.setPage('previous')}>
❮
</button>
<button type="button" class:disabled={table.currentPage === table.pageCount} onclick={() => table.setPage('next')}>
❯
</button>
<button type="button" class="small" class:disabled={table.currentPage === table.pageCount} onclick={() => table.setPage('last')}>
❭❭
</button>
{/snippet}
{#snippet ellipsis()}
<button type="button" class:disabled={table.currentPage === 1} onclick={() => table.setPage('previous')}>
{@html table.i18n.previous}
</button>
{#each table.pagesWithEllipsis as page}
<button type="button"
class="bg-darken-active"
class:active={table.currentPage === page}
class:ellipse={page === null}
onclick={() => table.setPage(page)}
>
{page ?? '...'}
</button>
{/each}
<button type="button" class:disabled={table.currentPage === table.pageCount} onclick={() => table.setPage('next')}>
{@html table.i18n.next}
</button>
{/snippet}
<style>
section {
display: flex;
margin: 8px 16px;
}
button {
background: inherit;
height: 32px;
width: 32px;
color: var(--font-grey, #9e9e9e);
cursor: pointer;
font-size: 13px;
margin: 0;
padding: 0;
transition: all, 0.2s;
line-height: 32px;
border: 1px solid var(--grey, #e0e0e0);
border-right: none;
border-radius: 0;
outline: none;
}
button:first-child {
border-radius: 4px 0 0 4px;
}
button:last-child {
border-right: 1px solid var(--grey, #e0e0e0);
border-radius: 0 4px 4px 0;
}
button:first-child:not(.small),
button:last-child:not(.small) {
min-width: 72px;
}
button:not(.active):hover {
background: var(--grey-lighten, #eee);
}
button.ellipse:hover {
background: inherit;
cursor: default;
}
button.active {
background: var(--grey-lighten, #eee);
font-weight: bold;
color: var(--font, #424242);
cursor: default;
}
button.disabled:hover {
background: inherit;
cursor: default;
}
button.page {
width: 80px;
}
</style>
================================================
FILE: src/lib/src/shared/RowCount.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface } from '$lib/src/shared'
type T = $$Generic<Row>
let { table, selection = false }: { table: TableHandlerInterface<T>, selection?: boolean } = $props()
const { start, end, total, selected } = $derived(table.rowCount)
</script>
<aside>
{#if selection}
{@render selectedRows()}
{:else if table.clientWidth < 640}
{@render small()}
{:else}
{@render rowCount()}
{/if}
</aside>
{#snippet selectedRows()}
<b>{selected}</b>
{#if total}
of <b>{total}</b>
{/if}
row(s) selected.
{#if selected > 0}
<button type="button" onclick={() => table.clearSelection()}>❌ Clear</button>
{/if}
{/snippet}
{#snippet small()}
{#if total > 0}
<b>{start}</b>-
<b>{end}</b>/
<b>{total}</b>
{:else}
{table.i18n.noRows}
{/if}
{/snippet}
{#snippet rowCount()}
{#if total > 0}
{@html table.i18n.rowCount
.replace('{start}', `<b>${start}</b>`)
.replace('{end}', `<b>${end}</b>`)
.replace('{total}', `<b>${total}</b>`)}
{:else}
{table.i18n.noRows}
{/if}
{/snippet}
<style>
aside {
margin: 8px 16px;
color: var(--font-grey, #757575);
line-height: 32px;
font-size: 14px;
}
</style>
================================================
FILE: src/lib/src/shared/RowsPerPage.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface } from '$lib/src/shared'
type T = $$Generic<Row>
let { table, options = [5, 10, 20, 50, 100] }: { table: TableHandlerInterface<T>, options?: number[] } = $props()
</script>
<aside>
{#if table.clientWidth > 600}
<span>{table.i18n.show}</span>
{/if}
<select bind:value={table.rowsPerPage} onchange={() => table.setPage(1)}>
{#each options as option}
<option value={option}>
{option}
</option>
{/each}
</select>
{#if table.clientWidth > 600}
<span>{table.i18n.entries}</span>
{/if}
</aside>
<style>
aside {
margin: 8px 16px;
display: flex;
justify-content: flex-start;
align-items: center;
height: 32px;
color: #757575;
}
select {
margin: 0 4px;
background: var(--bg, #fff);
color: var(--font-grey, #757575);
border-radius: 4px;
border: 1px solid var(--grey, #9e9e9e);
padding: 2px 4px;
}
select:focus {
outline: 2px solid var(--grey, #e0e0e0);
}
</style>
================================================
FILE: src/lib/src/shared/Search.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface } from '$lib/src/shared'
type T = $$Generic<Row>
let { table, value }: { table: TableHandlerInterface<T>, value?: string } = $props()
const search = table.createSearch().init(value)
</script>
<input
bind:value={search.value}
oninput={() => search.set()}
placeholder={table.i18n.search}
spellcheck="false"
/>
<style>
input {
margin: 8px 16px;
border: 1px solid var(--grey, #e0e0e0);
color: var(--font-grey, #424242);
border-radius: 4px;
outline: none;
padding: 0 8px;
line-height: 24px;
height: 24px;
background: transparent;
width: 40%;
max-width: 176px;
min-width: 96px;
transition: all, 0.1s;
}
input:focus {
border: 1px solid var(--grey, #e0e0e0);
outline: 2px solid var(--grey, #e0e0e0);
}
input::placeholder {
color: #9e9e9e;
line-height: 24px;
}
</style>
================================================
FILE: src/lib/src/shared/Th.svelte
================================================
<script lang="ts">
import type { Snippet } from 'svelte'
let { children }: { children?: Snippet } = $props()
</script>
<th>
<strong>
{@render children?.()}
</strong>
</th>
<style>
th {
background: inherit;
padding: 8px 20px;
white-space: nowrap;
font-size: 13px;
user-select: none;
text-align: left;
border-bottom: 1px solid var(--grey, #e0e0e0);
}
th strong {
white-space: pre-wrap;
font-weight: bold;
font-size: 13.5px;
line-height: 16px;
}
</style>
================================================
FILE: src/lib/src/shared/ThFilter.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface, Field } from '$lib/src/shared'
import type { Check } from '$lib/src/client'
type T = $$Generic<Row>
type Props = {
table : TableHandlerInterface<T>,
field : Field<T>,
value ?: unknown,
check ?: Check
}
let { table, field, value, check }: Props = $props()
const filter = table.createFilter(field, check).init(value)
</script>
<th>
<input
type="text"
placeholder={table.i18n.filter}
spellcheck="false"
bind:value={filter.value}
oninput={() => filter.set()}
/>
</th>
<style>
th {
border-bottom: 1px solid var(--grey, #e0e0e0);
}
input {
width: 100%;
height: 24px;
border: none;
text-align: left;
padding: 0 20px;
background: inherit;
outline: none;
border-radius: 0;
font-size: 14px;
color: var(--font-grey, #757575);
font-family: Arial, Helvetica, sans-serif;
}
input::placeholder {
color: var(--grey, #bdbdbd);
font-size: 13px;
}
input:focus {
outline: none;
border: none;
}
</style>
================================================
FILE: src/lib/src/shared/ThSort.svelte
================================================
<script lang="ts">
import type { TableHandlerInterface, Field } from '$lib/src/shared'
import type { Snippet } from 'svelte'
type T = $$Generic<Row>
type Props = {
table : TableHandlerInterface<T>,
field : Field<T>,
direction?: 'asc' | 'desc',
children: Snippet
}
let { table, field, direction, children }: Props = $props()
const sort = table.createSort(field).init(direction)
</script>
<th onclick={() => sort.set()} class:active={sort.isActive}>
<div class="flex">
<strong>
{@render children()}
</strong>
<span
class:asc={sort.direction === 'asc'}
class:desc={sort.direction === 'desc'}>
</span>
</div>
</th>
<style>
th {
background: inherit;
padding: 8px 20px;
white-space: nowrap;
font-size: 13px;
user-select: none;
border-bottom: 1px solid var(--grey, #e0e0e0);
cursor: pointer;
}
th strong {
white-space: pre-wrap;
font-size: 13.5px;
line-height: 16px;
text-align: left;
}
div.flex {
padding: 0;
display: flex;
align-items: center;
justify-content: flex-start;
height: 100%;
}
span {
padding-left: 8px;
}
span:before,
span:after {
border: 4px solid transparent;
content: '';
display: block;
height: 0;
width: 0;
}
span:before {
border-bottom-color: var(--grey, #e0e0e0);
margin-top: 2px;
}
span:after {
border-top-color: var(--grey, #e0e0e0);
margin-top: 2px;
}
th.active span.asc:before {
border-bottom-color: var(--font-grey, #9e9e9e);
}
th.active span.desc:after {
border-top-color: var(--font-grey, #9e9e9e);
}
</style>
================================================
FILE: src/lib/src/shared/builders/HighlightBuilder.svelte.ts
================================================
import type { TableHandlerInterface } from '$lib/src/shared'
import { stringify } from '$lib/src/client/core'
export default class HighlightBuilder<Row>
{
public selector : string
private table : TableHandlerInterface<Row>
private interval : NodeJS.Timeout
private keywords = $derived(this.createKeywords())
constructor(table: TableHandlerInterface<Row>, selector: string = 'tbody')
{
this.table = table
this.selector = selector
this.interval = setInterval(() => this.createHighlight(), 200)
}
private createHighlight()
{
if (!this.table?.element) {
return
}
clearInterval(this.interval)
const node = this.table.element.querySelector(this.selector)
this.table.on('change', () => {
// this.reset(node)
this.emphasize(node)
})
}
private createKeywords(): string[]
{
return [
this.table['search'].value ?? null,
// ...this.table.filters.map(filter => filter.value)
].filter(Boolean)
}
private emphasize(node: Node)
{
if (this.keywords.length === 0) return
if (node.nodeType === Node.ELEMENT_NODE) {
for (const child of node.childNodes) {
if (child.nodeName !== 'EM') {
this.emphasize(child)
}
}
}
else if (node.nodeTyp
gitextract_z5jwp21r/ ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── datatables.code-workspace ├── ecosystem.config.cjs ├── mdsvex.config.js ├── package.json ├── src/ │ ├── app.d.ts │ ├── app.html │ ├── hooks.server.ts │ ├── lib/ │ │ ├── index.ts │ │ ├── legacy/ │ │ │ ├── index.ts │ │ │ ├── local/ │ │ │ │ ├── Comparator.ts │ │ │ │ ├── Context.ts │ │ │ │ ├── DataHandler.ts │ │ │ │ ├── Datatable.svelte │ │ │ │ ├── Pagination.svelte │ │ │ │ ├── RowCount.svelte │ │ │ │ ├── RowsPerPage.svelte │ │ │ │ ├── Search.svelte │ │ │ │ ├── Th.svelte │ │ │ │ ├── ThFilter.svelte │ │ │ │ ├── handlers/ │ │ │ │ │ ├── EventHandler.ts │ │ │ │ │ ├── FilterHandler.ts │ │ │ │ │ ├── PageHandler.ts │ │ │ │ │ ├── SearchHandler.ts │ │ │ │ │ ├── SelectHandler.ts │ │ │ │ │ └── SortHandler.ts │ │ │ │ ├── helpers/ │ │ │ │ │ ├── AdvancedFilterHelper.ts │ │ │ │ │ ├── CalculationHelper.ts │ │ │ │ │ └── FilterHelper.ts │ │ │ │ ├── index.ts │ │ │ │ └── utils.ts │ │ │ └── remote/ │ │ │ ├── Context.ts │ │ │ ├── DataHandler.ts │ │ │ ├── Datatable.svelte │ │ │ ├── Pagination.svelte │ │ │ ├── README.md │ │ │ ├── RowCount.svelte │ │ │ ├── RowsPerPage.svelte │ │ │ ├── Search.svelte │ │ │ ├── SelectedCount.svelte │ │ │ ├── Th.svelte │ │ │ ├── ThFilter.svelte │ │ │ ├── handlers/ │ │ │ │ ├── EventHandler.ts │ │ │ │ ├── FilterHandler.ts │ │ │ │ ├── PageHandler.ts │ │ │ │ ├── SearchHandler.ts │ │ │ │ ├── SelectHandler.ts │ │ │ │ ├── SortHandler.ts │ │ │ │ └── TriggerHandler.ts │ │ │ └── index.ts │ │ ├── server/ │ │ │ └── index.ts │ │ ├── src/ │ │ │ ├── client/ │ │ │ │ ├── AbstractTableHandler.svelte.ts │ │ │ │ ├── TableHandler.svelte.ts │ │ │ │ ├── builders/ │ │ │ │ │ ├── AdvancedFilterBuilder.svelte.ts │ │ │ │ │ ├── CSVBuilder.svelte.ts │ │ │ │ │ ├── CalculationBuilder.svelte.ts │ │ │ │ │ ├── FilterBuilder.svelte.ts │ │ │ │ │ ├── QueryBuilder.svelte.ts │ │ │ │ │ ├── RecordFilterBuilder.svelte.ts │ │ │ │ │ ├── SearchBuilder.svelte.ts │ │ │ │ │ └── SortBuilder.svelte.ts │ │ │ │ ├── core/ │ │ │ │ │ ├── check.ts │ │ │ │ │ ├── entry.ts │ │ │ │ │ ├── field.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── rows.ts │ │ │ │ │ └── value.ts │ │ │ │ ├── handlers/ │ │ │ │ │ ├── FilterHandler.svelte.ts │ │ │ │ │ ├── PageHandler.svelte.ts │ │ │ │ │ ├── QueryHandler.svelte.ts │ │ │ │ │ ├── SearchHandler.svelte.ts │ │ │ │ │ ├── SelectHandler.svelte.ts │ │ │ │ │ └── SortHandler.svelte.ts │ │ │ │ └── index.ts │ │ │ ├── server/ │ │ │ │ ├── AbstractTableHandler.svelte.ts │ │ │ │ ├── TableHandler.svelte.ts │ │ │ │ ├── builders/ │ │ │ │ │ ├── FilterBuilder.svelte.ts │ │ │ │ │ ├── SearchBuilder.svelte.ts │ │ │ │ │ └── SortBuilder.svelte.ts │ │ │ │ ├── handlers/ │ │ │ │ │ ├── FetchHandler.svelte.ts │ │ │ │ │ ├── FilterHandler.svelte.ts │ │ │ │ │ ├── PageHandler.svelte.ts │ │ │ │ │ ├── SearchHandler.svelte.ts │ │ │ │ │ ├── SelectHandler.svelte.ts │ │ │ │ │ └── SortHandler.svelte.ts │ │ │ │ └── index.ts │ │ │ └── shared/ │ │ │ ├── Datatable.svelte │ │ │ ├── EventDispatcher.ts │ │ │ ├── Pagination.svelte │ │ │ ├── RowCount.svelte │ │ │ ├── RowsPerPage.svelte │ │ │ ├── Search.svelte │ │ │ ├── Th.svelte │ │ │ ├── ThFilter.svelte │ │ │ ├── ThSort.svelte │ │ │ ├── builders/ │ │ │ │ ├── HighlightBuilder.svelte.ts │ │ │ │ └── ViewBuilder.svelte.ts │ │ │ ├── clsx/ │ │ │ │ ├── Datatable.svelte │ │ │ │ ├── Pagination.svelte │ │ │ │ └── ThSort.svelte │ │ │ └── index.ts │ │ └── style.css │ ├── routes/ │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ ├── Description.svelte │ │ ├── Header.svelte │ │ ├── Header_Github.svelte │ │ ├── Header_MobileNav.svelte │ │ ├── Header_Mode.svelte │ │ ├── Header_Theme.svelte │ │ ├── Header_Version.svelte │ │ ├── about/ │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── Nav.svelte │ │ │ ├── Nav_Mobile.svelte │ │ │ └── [slug]/ │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ ├── api/ │ │ │ └── [mode]/ │ │ │ ├── +layout.server.ts │ │ │ ├── +layout.svelte │ │ │ ├── +page.svelte │ │ │ ├── Nav.svelte │ │ │ ├── Nav_Key.svelte │ │ │ ├── Nav_Mobile.svelte │ │ │ ├── [slug]/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Content.svelte │ │ │ │ └── Content_Ext.svelte │ │ │ ├── content.svx │ │ │ ├── gen/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ └── Board.svelte │ │ │ └── md/ │ │ │ ├── +layout.svelte │ │ │ ├── +layout.ts │ │ │ ├── +page.svelte │ │ │ ├── Nav.svelte │ │ │ ├── Nav_Key.svelte │ │ │ ├── [slug]/ │ │ │ │ ├── +page.svelte │ │ │ │ ├── +page.ts │ │ │ │ ├── AST.svelte │ │ │ │ ├── Content.svelte │ │ │ │ └── Content_Ext.svelte │ │ │ └── content.svx │ │ ├── components/ │ │ │ ├── +layout.svelte │ │ │ └── +page.svelte │ │ ├── docs/ │ │ │ ├── client/ │ │ │ │ ├── +layout.svelte │ │ │ │ ├── +page.server.ts │ │ │ │ ├── add-on/ │ │ │ │ │ ├── csv-export/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── record-filter/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── content.svx │ │ │ │ │ └── data_cars.ts │ │ │ │ ├── calculation/ │ │ │ │ │ ├── avg/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Advanced.svelte │ │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ │ └── code.svx │ │ │ │ │ ├── bounds/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ │ └── code.svx │ │ │ │ │ ├── data_cars.ts │ │ │ │ │ ├── data_grocery.ts │ │ │ │ │ ├── data_parcel.ts │ │ │ │ │ ├── distinct/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ │ └── code.svx │ │ │ │ │ ├── median/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Advanced.svelte │ │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ │ └── code.svx │ │ │ │ │ └── sum/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Advanced.svelte │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ └── code.svx │ │ │ │ ├── filters/ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── check/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Comparators.svelte │ │ │ │ │ │ ├── Comparators_Check.svelte │ │ │ │ │ │ ├── content.svx │ │ │ │ │ │ ├── content2.svx │ │ │ │ │ │ └── data.ts │ │ │ │ │ ├── criteria/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ ├── content.svx │ │ │ │ │ │ └── data.ts │ │ │ │ │ ├── highlight/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── input/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── nested/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Nested.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── getting-started/ │ │ │ │ │ ├── hello-world/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Client.svx │ │ │ │ │ │ └── Main.svelte │ │ │ │ │ ├── i18n/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ └── doc.svx │ │ │ │ │ ├── intro/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── migration/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── overview/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── pagination/ │ │ │ │ │ ├── navigation/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Navigation.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── pages/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── CurrentPage.svelte │ │ │ │ │ │ ├── Pages.svelte │ │ │ │ │ │ ├── PagesWithEllipsis.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── row-count/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── rows-per-page/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── search/ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── highlight/ │ │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Example.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── input/ │ │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Example.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── recursive/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ ├── Main_Search.svelte │ │ │ │ │ │ ├── Main_Tree.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── regex/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── scope/ │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Example.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── select/ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── all/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── row/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── scope/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── sort/ │ │ │ │ │ ├── button/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Main.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ ├── dates/ │ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ │ ├── Example.svelte │ │ │ │ │ │ └── content.svx │ │ │ │ │ └── nested/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Nested.svelte │ │ │ │ │ └── content.svx │ │ │ │ └── view/ │ │ │ │ ├── freeze/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Main.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── ordering/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ └── visible/ │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ └── content.svx │ │ │ └── server/ │ │ │ ├── +layout.svelte │ │ │ ├── +page.server.ts │ │ │ ├── data/ │ │ │ │ ├── invalidate/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── is-loading/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── api.ts │ │ │ │ │ └── content.svx │ │ │ │ └── load/ │ │ │ │ ├── +page.svelte │ │ │ │ └── content.svx │ │ │ ├── filters/ │ │ │ │ └── input/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── content.svx │ │ │ ├── getting-started/ │ │ │ │ ├── hello-world/ │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Main.svelte │ │ │ │ │ ├── api.ts │ │ │ │ │ └── content.svx │ │ │ │ ├── i18n/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Main.svelte │ │ │ │ │ └── doc.svx │ │ │ │ ├── intro/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── migration/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ └── overview/ │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ └── content.svx │ │ │ ├── pagination/ │ │ │ │ ├── navigation/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Navigation.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── pages/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── CurrentPage.svelte │ │ │ │ │ ├── Pages.svelte │ │ │ │ │ ├── PagesWithEllipsis.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── row-count/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ └── rows-per-page/ │ │ │ │ ├── +page.svelte │ │ │ │ └── content.svx │ │ │ ├── search/ │ │ │ │ └── input/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── content.svx │ │ │ ├── select/ │ │ │ │ ├── +layout.server.ts │ │ │ │ ├── Main.svelte │ │ │ │ ├── Main_OLD.svelte │ │ │ │ ├── all/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── content.svx │ │ │ │ ├── api.ts │ │ │ │ └── row/ │ │ │ │ ├── +page.svelte │ │ │ │ └── content.svx │ │ │ ├── sort/ │ │ │ │ └── button/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── content.svx │ │ │ ├── tips/ │ │ │ │ └── sticky-header/ │ │ │ │ ├── +page.svelte │ │ │ │ └── doc.svx │ │ │ └── view/ │ │ │ ├── +layout.server.ts │ │ │ ├── api.ts │ │ │ ├── freeze/ │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ └── content.svx │ │ │ ├── ordering/ │ │ │ │ ├── +page.svelte │ │ │ │ └── content.svx │ │ │ └── visible/ │ │ │ ├── +page.svelte │ │ │ ├── Main.svelte │ │ │ └── content.svx │ │ ├── examples/ │ │ │ ├── client/ │ │ │ │ ├── +layout.svelte │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Test.svelte │ │ │ │ ├── column-ordering/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Client.svx │ │ │ │ │ ├── Main.svelte │ │ │ │ │ └── SortButton.svelte │ │ │ │ ├── crud/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Datatable.svelte │ │ │ │ │ ├── Modal_Create.svelte │ │ │ │ │ ├── Modal_Destroy.svelte │ │ │ │ │ ├── Modal_Update.svelte │ │ │ │ │ ├── api.svelte.ts │ │ │ │ │ ├── doc.svx │ │ │ │ │ └── intro.svx │ │ │ │ ├── distinct/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Basic.svelte │ │ │ │ │ ├── RowCount.svelte │ │ │ │ │ ├── Search.svelte │ │ │ │ │ ├── code.svx │ │ │ │ │ └── data.ts │ │ │ │ ├── hello-world/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Client.svx │ │ │ │ │ ├── Main.svelte │ │ │ │ │ └── page.server.ts │ │ │ │ ├── nested-array/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Client.svx │ │ │ │ │ └── Main.svelte │ │ │ │ ├── pokedex/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── MCF_Main.svelte │ │ │ │ │ ├── MCF_PokemonStats.svelte │ │ │ │ │ ├── MCF_Table.svelte │ │ │ │ │ ├── MCF_TableFilter.svelte │ │ │ │ │ └── data.ts │ │ │ │ ├── shadcn/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Table.svelte │ │ │ │ │ ├── Table_Footer.svelte │ │ │ │ │ ├── Table_Footer_Pagination.svelte │ │ │ │ │ ├── Table_Footer_RowSelection.svelte │ │ │ │ │ ├── Table_Footer_RowsPerPage.svelte │ │ │ │ │ ├── Table_Header.svelte │ │ │ │ │ ├── Table_Header_ColumnVisibility.svelte │ │ │ │ │ ├── Table_Header_Filter.svelte │ │ │ │ │ ├── Table_Th.svelte │ │ │ │ │ └── utils.ts │ │ │ │ ├── test/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ ├── Table.svelte │ │ │ │ │ └── data2.ts │ │ │ │ ├── test-calculation/ │ │ │ │ │ ├── +page.svelte │ │ │ │ │ └── data.ts │ │ │ │ └── tree/ │ │ │ │ ├── +page.svelte │ │ │ │ ├── Client.svx │ │ │ │ ├── Main.svelte │ │ │ │ ├── Main_Search.svelte │ │ │ │ └── Main_Tree.svelte │ │ │ └── server/ │ │ │ ├── +layout.svelte │ │ │ ├── +page.server.ts │ │ │ ├── Features.svelte │ │ │ ├── beer-api/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── example.server.ts │ │ │ ├── hello-world/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── example.server.ts │ │ │ ├── pokedex-api/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── PokemonStats.svelte │ │ │ │ ├── PokemonTypes.svelte │ │ │ │ ├── api.ts │ │ │ │ └── example.server.ts │ │ │ ├── ssr-user/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ └── params.svelte.ts │ │ │ ├── todo-api/ │ │ │ │ ├── +page.server.ts │ │ │ │ ├── +page.svelte │ │ │ │ ├── Main.svelte │ │ │ │ ├── api.ts │ │ │ │ └── example.server.ts │ │ │ └── user-api/ │ │ │ ├── +page.server.ts │ │ │ ├── +page.svelte │ │ │ ├── Main.svelte │ │ │ ├── api.ts │ │ │ └── example.server.ts │ │ └── export/ │ │ └── [mode]/ │ │ ├── gen/ │ │ │ ├── +page.server.ts │ │ │ ├── +page.svelte │ │ │ └── Board.svelte │ │ └── md/ │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── Nav.svelte │ │ ├── Nav_Key.svelte │ │ ├── [slug]/ │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── AST.svelte │ │ │ ├── Content.svelte │ │ │ └── Content_Ext.svelte │ │ └── content.svx │ └── site/ │ ├── Banner.svelte │ ├── Logo.svelte │ ├── Site.svelte.ts │ ├── components/ │ │ ├── Demo.svelte │ │ ├── Demo_Code.svelte │ │ ├── Demo_Code_Icon.svelte │ │ ├── Demo_Code_Nav.svelte │ │ ├── Demo_CopyButton.svelte │ │ ├── Demo_Data.svelte │ │ ├── Highlight.svelte │ │ ├── Install.svelte │ │ ├── Nav_Search.svelte │ │ ├── docs/ │ │ │ ├── Layout.svelte │ │ │ ├── Nav.svelte │ │ │ └── Nav_Mobile.svelte │ │ └── examples/ │ │ ├── Layout.svelte │ │ ├── Nav.svelte │ │ └── Nav_Mobile.svelte │ ├── data/ │ │ ├── cars.ts │ │ ├── data.11000.ts │ │ ├── data.75.ts │ │ ├── data.ts │ │ ├── data_with_null.ts │ │ ├── int-bool-string.ts │ │ ├── pokedex.ts │ │ └── tree.ts │ ├── index.ts │ └── utils/ │ └── viewport.ts ├── static/ │ ├── documents/ │ │ ├── client/ │ │ │ ├── methods.clearFilters.json │ │ │ ├── methods.clearSearch.json │ │ │ ├── methods.clearSelection.json │ │ │ ├── methods.clearSort.json │ │ │ ├── methods.createAdvancedFilter.json │ │ │ ├── methods.createCSV.json │ │ │ ├── methods.createCalculation.json │ │ │ ├── methods.createFilter.json │ │ │ ├── methods.createQuery.json │ │ │ ├── methods.createRecordFilter.json │ │ │ ├── methods.createSearch.json │ │ │ ├── methods.createSort.json │ │ │ ├── methods.createView.json │ │ │ ├── methods.filter.json │ │ │ ├── methods.getSelectedRows.json │ │ │ ├── methods.getView.json │ │ │ ├── methods.on.json │ │ │ ├── methods.select.json │ │ │ ├── methods.selectAll.json │ │ │ ├── methods.setPage.json │ │ │ ├── methods.setRows.json │ │ │ ├── methods.setRowsPerPage.json │ │ │ ├── nav.json │ │ │ ├── properties.allRows.json │ │ │ ├── properties.clientWidth.json │ │ │ ├── properties.currentPage.json │ │ │ ├── properties.element.json │ │ │ ├── properties.filterCount.json │ │ │ ├── properties.filters.json │ │ │ ├── properties.i18n.json │ │ │ ├── properties.isAllSelected.json │ │ │ ├── properties.pageCount.json │ │ │ ├── properties.pages.json │ │ │ ├── properties.pagesWithEllipsis.json │ │ │ ├── properties.queries.json │ │ │ ├── properties.rowCount.json │ │ │ ├── properties.rows.json │ │ │ ├── properties.rowsPerPage.json │ │ │ ├── properties.selected.json │ │ │ ├── types.Check.json │ │ │ ├── types.ColumnView.json │ │ │ ├── types.Criterion.json │ │ │ ├── types.Field.json │ │ │ ├── types.Filter.json │ │ │ ├── types.Internationalization.json │ │ │ ├── types.Row.json │ │ │ ├── types.SearchType.json │ │ │ ├── types.Sort.json │ │ │ ├── types.SortParams.json │ │ │ ├── types.TableHandlerParams.json │ │ │ ├── types.TableParams.json │ │ │ └── types.ViewColumn.json │ │ ├── markdown/ │ │ │ ├── client/ │ │ │ │ ├── methods.clearSort.md │ │ │ │ ├── methods.createAdvancedFilter.md │ │ │ │ ├── methods.createCSV.md │ │ │ │ ├── methods.createCalculation.md │ │ │ │ ├── methods.createRecordFilter.md │ │ │ │ ├── methods.getSelectedRows.md │ │ │ │ ├── methods.setRows.md │ │ │ │ ├── methods.setRowsPerPage.md │ │ │ │ ├── properties.allRows.md │ │ │ │ ├── properties.filters.md │ │ │ │ ├── types.Check.md │ │ │ │ ├── types.Comparator.md │ │ │ │ ├── types.Criterion.md │ │ │ │ ├── types.Filter.md │ │ │ │ ├── types.Internationalization.md │ │ │ │ ├── types.Sort.md │ │ │ │ ├── types.SortParams.md │ │ │ │ └── types.TableParams.md │ │ │ ├── methods.clearFilters.md │ │ │ ├── methods.clearSearch.md │ │ │ ├── methods.clearSelection.md │ │ │ ├── methods.createFilter.md │ │ │ ├── methods.createSearch.md │ │ │ ├── methods.createSort.md │ │ │ ├── methods.createView.md │ │ │ ├── methods.getView.md │ │ │ ├── methods.on.md │ │ │ ├── methods.select.md │ │ │ ├── methods.selectAll.md │ │ │ ├── methods.setPage.md │ │ │ ├── methods.setRowsPerPage.md │ │ │ ├── properties.clientWidth.md │ │ │ ├── properties.currentPage.md │ │ │ ├── properties.element.md │ │ │ ├── properties.filterCount.md │ │ │ ├── properties.i18n.md │ │ │ ├── properties.isAllSelected.md │ │ │ ├── properties.pageCount.md │ │ │ ├── properties.pages.md │ │ │ ├── properties.pagesWithEllipsis.md │ │ │ ├── properties.rowCount.md │ │ │ ├── properties.rows.md │ │ │ ├── properties.rowsPerPage.md │ │ │ ├── properties.selected.md │ │ │ ├── properties.sort.md │ │ │ ├── server/ │ │ │ │ ├── methods.getState.md │ │ │ │ ├── methods.invalidate.md │ │ │ │ ├── methods.load.md │ │ │ │ ├── properties.filters.md │ │ │ │ ├── properties.isLoading.md │ │ │ │ ├── properties.sort.md │ │ │ │ ├── properties.totalRows.md │ │ │ │ ├── types.Filter.md │ │ │ │ ├── types.Sort.md │ │ │ │ └── types.State.md │ │ │ ├── types.ColumnView.md │ │ │ ├── types.Field.md │ │ │ ├── types.Internationalization.md │ │ │ └── types.Row.md │ │ └── server/ │ │ ├── methods.clearFilters.json │ │ ├── methods.clearSearch.json │ │ ├── methods.clearSelection.json │ │ ├── methods.createFilter.json │ │ ├── methods.createSearch.json │ │ ├── methods.createSort.json │ │ ├── methods.createView.json │ │ ├── methods.filter.json │ │ ├── methods.getState.json │ │ ├── methods.getView.json │ │ ├── methods.invalidate.json │ │ ├── methods.load.json │ │ ├── methods.on.json │ │ ├── methods.select.json │ │ ├── methods.selectAll.json │ │ ├── methods.setPage.json │ │ ├── methods.setRowsPerPage.json │ │ ├── methods.setTotalRows.json │ │ ├── nav.json │ │ ├── properties.clientWidth.json │ │ ├── properties.currentPage.json │ │ ├── properties.debounce.json │ │ ├── properties.element.json │ │ ├── properties.events.json │ │ ├── properties.filterCount.json │ │ ├── properties.filters.json │ │ ├── properties.i18n.json │ │ ├── properties.isAllSelected.json │ │ ├── properties.isLoading.json │ │ ├── properties.pageCount.json │ │ ├── properties.pages.json │ │ ├── properties.pagesWithEllipsis.json │ │ ├── properties.rowCount.json │ │ ├── properties.rows.json │ │ ├── properties.rowsPerPage.json │ │ ├── properties.search.json │ │ ├── properties.selectBy.json │ │ ├── properties.selected.json │ │ ├── properties.sort.json │ │ ├── properties.totalRows.json │ │ ├── types.ColumnView.json │ │ ├── types.Field.json │ │ ├── types.Filter.json │ │ ├── types.Internationalization.json │ │ ├── types.Row.json │ │ ├── types.Sort.json │ │ ├── types.State.json │ │ ├── types.TableParams.json │ │ └── types.ViewColumn.json │ ├── fonts/ │ │ ├── Inter/ │ │ │ ├── OFL.txt │ │ │ └── README.txt │ │ └── Roboto/ │ │ └── LICENSE.txt │ ├── global.css │ ├── gros-theme.css │ ├── gros.css │ ├── prism-dark.css │ └── prism-light.css ├── svelte.config.js ├── tsconfig.json └── vite.config.ts
SYMBOL INDEX (450 symbols across 66 files)
FILE: src/lib/legacy/local/Comparator.ts
function stringify (line 100) | function stringify(value: string | number | boolean = null) {
function isNull (line 107) | function isNull(entry: any) {
FILE: src/lib/legacy/local/Context.ts
class Context (line 9) | class Context<Row>
method constructor (line 29) | constructor(data: Row[], params: Params)
method createFilterCount (line 50) | private createFilterCount()
method createFilteredRows (line 55) | private createFilteredRows()
method match (line 92) | private match(entry: Row[keyof Row], value: string|number|boolean|symb...
method createPagedRows (line 110) | private createPagedRows()
method createRowCount (line 126) | private createRowCount()
method createPages (line 144) | private createPages()
method createPagesWithEllipsis (line 155) | private createPagesWithEllipsis()
method createPageCount (line 189) | private createPageCount()
method createIsAllSelected (line 196) | private createIsAllSelected()
FILE: src/lib/legacy/local/DataHandler.ts
type Params (line 15) | type Params = { rowsPerPage?: number, i18n?: Internationalization }
class DataHandler (line 17) | class DataHandler<T extends Row = any>
method constructor (line 27) | constructor(data: T[] = [], params: Params = { rowsPerPage: null })
method setRows (line 38) | public setRows(data: T[])
method getRows (line 45) | public getRows(): Readable<T[]>
method getAllRows (line 50) | public getAllRows(): Readable<T[]>
method getRowCount (line 55) | public getRowCount(): Readable<{ total: number, start: number, end: nu...
method getRowsPerPage (line 60) | public getRowsPerPage(): Writable<number | null>
method getPages (line 65) | public getPages(param = { ellipsis: false }): Readable<number[]>
method getPageCount (line 73) | public getPageCount(): Readable<number>
method getPageNumber (line 78) | public getPageNumber(): Readable<number>
method setPage (line 83) | public setPage(value: number | 'previous' | 'next'): void
method search (line 92) | public search(value: string, scope: Field<T>[] = null)
method clearSearch (line 97) | public clearSearch()
method sort (line 102) | public sort(orderBy: Field<T>, identifier?: string)
method sortAsc (line 108) | public sortAsc(orderBy: Field<T>, identifier?: string)
method sortDesc (line 114) | public sortDesc(orderBy: Field<T>, identifier?: string)
method getSort (line 120) | public getSort(): Writable<{ identifier?: string, direction?: 'asc' | ...
method applySort (line 125) | public applySort( params: { orderBy: Field<T>, direction?: 'asc' | 'de...
method defineSort (line 130) | public defineSort(orderBy: Field<T>, direction?: 'asc' | 'desc')
method clearSort (line 135) | public clearSort()
method filter (line 140) | public filter( value: string | number | null | undefined | boolean | n...
method getFilters (line 145) | public getFilters()
method createFilter (line 150) | public createFilter( filterBy: Field<T>, comparator?: Comparator<T> )
method createAdvancedFilter (line 155) | public createAdvancedFilter(filterBy: Field<T>)
method getFilterCount (line 160) | public getFilterCount(): Readable<number>
method clearFilters (line 165) | public clearFilters(): void
method select (line 170) | public select(value: T | T[keyof T])
method getSelected (line 175) | public getSelected()
method selectAll (line 180) | public selectAll(params: { selectBy?: keyof T; scope?: 'all' | 'curren...
method isAllSelected (line 186) | public isAllSelected(): Readable<boolean>
method on (line 191) | public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: ...
method createCalculation (line 196) | public createCalculation(field: Field<T>, param: { precision: number }...
method translate (line 201) | public translate(i18n: Internationalization): Internationalization
method update (line 225) | public update(data: any[]): void
method applySorting (line 239) | public applySorting( params: { orderBy: Field<T>, direction?: 'asc' | ...
method getSorted (line 248) | public getSorted()
method getTriggerChange (line 254) | public getTriggerChange(): Writable<number>
FILE: src/lib/legacy/local/handlers/EventHandler.ts
class EventHandler (line 4) | class EventHandler
method add (line 14) | public add(event: keyof EventHandler['events'], callback: () => void)
method trigger (line 19) | public trigger(event: keyof EventHandler['events'])
FILE: src/lib/legacy/local/handlers/FilterHandler.ts
type Value (line 8) | type Value = string | number | null | undefined | boolean | number[] | C...
class FilterHandler (line 17) | class FilterHandler<Row>
method constructor (line 23) | constructor(context: Context<Row>)
method set (line 29) | public set(value: Value, filterBy: Field<Row>, comparator: Comparator<...
method clear (line 42) | public clear()
method get (line 49) | public get()
method createCollection (line 58) | private createCollection()
FILE: src/lib/legacy/local/handlers/PageHandler.ts
class PageHandler (line 5) | class PageHandler<Row>
method constructor (line 12) | constructor(context: Context<Row>)
method goto (line 20) | public goto(number: number)
method previous (line 35) | public previous()
method next (line 41) | public next()
FILE: src/lib/legacy/local/handlers/SearchHandler.ts
class SearchHandler (line 5) | class SearchHandler<Row>
method constructor (line 10) | constructor(context: Context<Row>)
method set (line 16) | public set(value: string, scope: Field<Row>[] = null)
method clear (line 28) | public clear()
FILE: src/lib/legacy/local/handlers/SelectHandler.ts
class SelectHandler (line 7) | class SelectHandler<Row>
method constructor (line 16) | constructor(context: Context<Row>)
method set (line 26) | public set(value: Row[keyof Row] | Row)
method all (line 36) | public all(selectBy: keyof Row = null)
method clear (line 56) | public clear()
FILE: src/lib/legacy/local/handlers/SortHandler.ts
class SortHandler (line 6) | class SortHandler<Row>
method constructor (line 13) | constructor(context: Context<Row>)
method set (line 21) | public set(orderBy: Field<Row> = null, uid?: string)
method asc (line 37) | public asc(orderBy: Field<Row>, uid?: string)
method desc (line 60) | public desc(orderBy: Field<Row>, uid?: string)
method apply (line 83) | public apply(params: { orderBy: Field<Row>, direction?: 'asc' | 'desc'...
method clear (line 97) | public clear()
method define (line 103) | public define(orderBy: Field<Row>, direction: 'asc' | 'desc' = 'asc')
method restore (line 110) | private restore()
method log (line 119) | private log(sort: Sort<Row>)
FILE: src/lib/legacy/local/helpers/AdvancedFilterHelper.ts
type Value (line 6) | type Value = string | number | [min: number, max: number]
class AdvancedFilterHandler (line 8) | class AdvancedFilterHandler<Row>
method constructor (line 15) | constructor(filterHandler: FilterHandler<Row>, filterBy: Field<Row>)
method set (line 23) | public set(value: Value, comparator: Comparator<any> = check.isLike)
method getSelected (line 38) | public getSelected()
method clear (line 43) | public clear()
FILE: src/lib/legacy/local/helpers/CalculationHelper.ts
class CalcultationHandler (line 6) | class CalcultationHandler<Row>
method constructor (line 13) | constructor(context: Context<Row>, field: Field<Row>, param: { precisi...
method distinct (line 21) | public distinct(callback: (values: any[]) => any[] = null)
method avg (line 35) | public avg(callback: (values: number[]) => any[] = null)
method sum (line 45) | public sum(callback: (values: number[]) => any[] = null)
method bounds (line 54) | public bounds(callback: (values: number[]) => any[] = null): [min: num...
method setPrecision (line 67) | public setPrecision(value: number)
method round (line 72) | private round(value: number)
FILE: src/lib/legacy/local/helpers/FilterHelper.ts
type Value (line 5) | type Value = string | number | boolean
class FilterHelper (line 7) | class FilterHelper<Row>
method constructor (line 15) | constructor(filterHandler: FilterHandler<Row>, filterBy: Field<Row>, c...
method set (line 24) | public set(value: Value, comparator?: Comparator<any>)
method clear (line 32) | public clear()
method on (line 38) | public on(event: 'clear', callback: () => void)
FILE: src/lib/legacy/local/index.ts
type Internationalization (line 16) | type Internationalization = {
type Row (line 27) | type Row = { [key: string]: any }
type Field (line 28) | type Field<Row> = keyof Row | ((row: Row) => Row[keyof Row])
type Comparator (line 31) | type Comparator<Row> = (entry: Row[keyof Row], value: any) => boolean
type Criterion (line 33) | type Criterion = { value: string | number | [min: number, max: number], ...
type Filter (line 35) | type Filter<Row> = {
type Sort (line 43) | type Sort<Row> = {
type Selectable (line 58) | type Selectable<Row> = Row[keyof Row] | Row
type FilterBy (line 65) | type FilterBy<Row> = Field<Row>
type OrderBy (line 72) | type OrderBy<Row> = Field<Row>
type Order (line 79) | type Order<Row> = Sort<Row>
FILE: src/lib/legacy/remote/Context.ts
class Context (line 6) | class Context<Row>
method constructor (line 26) | constructor(data: Row[], params: Params)
method getState (line 46) | public getState(): State
method createPages (line 64) | private createPages()
method createPagesWithEllipsis (line 77) | private createPagesWithEllipsis()
method createPageCount (line 114) | private createPageCount()
method createRowCount (line 122) | private createRowCount()
method createIsAllSelected (line 140) | private createIsAllSelected()
method createSelectedCount (line 154) | private createSelectedCount()
FILE: src/lib/legacy/remote/DataHandler.ts
type Params (line 12) | type Params = {
class DataHandler (line 19) | class DataHandler<T extends Row = any>
method constructor (line 30) | constructor(data: T[] = [], params: Params = { rowsPerPage: 5 })
method onChange (line 42) | public onChange(callback: (state: State) => Promise<T[]>)
method invalidate (line 47) | public invalidate()
method setRows (line 52) | public setRows(data: T[])
method setTotalRows (line 57) | public setTotalRows(value: number)
method getRows (line 62) | public getRows(): Writable<T[]>
method select (line 67) | public select(value: T[keyof T] | T)
method getSelected (line 72) | public getSelected()
method selectAll (line 77) | public selectAll(): void
method isAllSelected (line 82) | public isAllSelected(): Readable<boolean>
method getSelectedCount (line 87) | public getSelectedCount(): Readable<{ count: number, total: number }>
method clearSelection (line 92) | public clearSelection()
method getRowsPerPage (line 97) | public getRowsPerPage(): Writable<number | null>
method sort (line 102) | public sort(orderBy: keyof T)
method applySort (line 108) | public applySort( params: { orderBy: keyof T, direction?: 'asc' | 'de...
method sortAsc (line 113) | public sortAsc(orderBy: keyof T)
method sortDesc (line 119) | public sortDesc(orderBy: keyof T)
method getSort (line 125) | public getSort(): Writable<Sort<T>>
method search (line 130) | public search(value: string): void
method clearSearch (line 136) | public clearSearch()
method filter (line 141) | public filter(value: string | number, filterBy: keyof T)
method clearFilters (line 147) | public clearFilters(): void
method getPages (line 152) | public getPages(params = { ellipsis: false }): Readable<number[]>
method getPageCount (line 160) | public getPageCount(): Readable<number>
method getPageNumber (line 165) | public getPageNumber(): Writable<number>
method setPage (line 170) | public setPage(value: number | 'previous' | 'next'): void
method getRowCount (line 179) | public getRowCount(): Readable<{ total: number, start: number, end: nu...
method on (line 184) | public on(event: 'change', callback: () => void)
method translate (line 189) | public translate(i18n: Internationalization): Internationalization
method getTriggerChange (line 213) | public getTriggerChange(): Writable<number>
method applySorting (line 222) | public applySorting( params: { orderBy: keyof T, direction?: 'asc' | ...
method getSorted (line 232) | public getSorted()
FILE: src/lib/legacy/remote/handlers/EventHandler.ts
class EventHandler (line 4) | class EventHandler
method add (line 14) | public add(event: keyof EventHandler['events'], callback: () => void)
method trigger (line 19) | public trigger(event: keyof EventHandler['events'])
FILE: src/lib/legacy/remote/handlers/FilterHandler.ts
class FilterHandler (line 5) | class FilterHandler<Row>
method constructor (line 9) | constructor(context: Context<Row>)
method set (line 14) | public set(value: string | number, filterBy: keyof Row )
method remove (line 28) | public remove()
FILE: src/lib/legacy/remote/handlers/PageHandler.ts
class PageHandler (line 5) | class PageHandler<Row>
method constructor (line 15) | constructor(context: Context<Row>)
method get (line 26) | public get()
method goto (line 31) | public goto(number: number)
method previous (line 54) | public previous()
method next (line 60) | public next()
FILE: src/lib/legacy/remote/handlers/SearchHandler.ts
class SearchHandler (line 4) | class SearchHandler<Row>
method constructor (line 8) | constructor(context: Context<Row>) {
method set (line 12) | public set(value: string)
method remove (line 17) | public remove()
FILE: src/lib/legacy/remote/handlers/SelectHandler.ts
class SelectHandler (line 6) | class SelectHandler<Row>
method constructor (line 13) | constructor(context: Context<Row>)
method set (line 21) | public set(value: Row | Row[keyof Row])
method all (line 32) | public all()
method clear (line 55) | public clear()
FILE: src/lib/legacy/remote/handlers/SortHandler.ts
class SortHandler (line 6) | class SortHandler<Row>
method constructor (line 12) | constructor(context: Context<Row>)
method set (line 19) | public set(orderBy: keyof Row = null)
method asc (line 35) | public asc(orderBy: keyof Row)
method desc (line 42) | public desc(orderBy: keyof Row)
method apply (line 49) | public apply(params: { orderBy: keyof Row, direction?: 'asc' | 'desc' ...
FILE: src/lib/legacy/remote/handlers/TriggerHandler.ts
class TriggerHandler (line 5) | class TriggerHandler<Row>
method constructor (line 10) | constructor(context: Context<Row>)
method set (line 15) | public set(callback: (state: State) => Promise<Row[]>)
method invalidate (line 20) | public async invalidate()
FILE: src/lib/legacy/remote/index.ts
type Internationalization (line 14) | type Internationalization = {
type Row (line 26) | type Row = { [key: string]: unknown }
type Filter (line 28) | type Filter<Row> = {
type Sort (line 33) | type Sort<Row> = {
type State (line 39) | type State = {
type Selectable (line 60) | type Selectable<Row> = Row[keyof Row] | Row
type Order (line 69) | type Order<Row> = Sort<Row>
FILE: src/lib/src/client/AbstractTableHandler.svelte.ts
method constructor (line 32) | constructor(data: Row[], params: TableParams<Row>)
method createAllRows (line 40) | private createAllRows()
method createRows (line 62) | private createRows()
method createRowCount (line 71) | private createRowCount()
method createPages (line 85) | private createPages()
method createPagesWithEllipsis (line 94) | private createPagesWithEllipsis()
method createIsAllSelected (line 125) | private createIsAllSelected()
FILE: src/lib/src/client/TableHandler.svelte.ts
class TableHandler (line 24) | class TableHandler<T extends Row = any> extends AbstractTableHandler<T> ...
method constructor (line 35) | constructor(data: T[] = [], params: TableParams<T> = { rowsPerPage: nu...
method setRows (line 47) | public setRows(data: T[]): void
method setRowsPerPage (line 60) | public setRowsPerPage(value: number): void
method setPage (line 66) | public setPage(value: number | 'previous' | 'next' | 'last'): void
method createSearch (line 76) | public createSearch(scope?: Field<T>[]): SearchBuilder<T>
method clearSearch (line 81) | public clearSearch(): void
method createRecordFilter (line 88) | public createRecordFilter(records?: Row[]): RecordFilterBuilder
method createSort (line 93) | public createSort(field: Field<T>, params?: { locales: Intl.LocalesArg...
method clearSort (line 98) | public clearSort()
method clearFilters (line 103) | public clearFilters(): void
method createAdvancedFilter (line 110) | public createAdvancedFilter(field: Field<T>, check?: Check): AdvancedF...
method createFilter (line 115) | public createFilter(field: Field<T>, check?: Check): FilterBuilder<T>
method createQuery (line 120) | public createQuery(): QueryBuilder<T>
method select (line 125) | public select(value: unknown): void
method selectAll (line 130) | public selectAll(params: { scope?: 'all' | 'currentPage' } = {}): void
method getSelectedRows (line 136) | public getSelectedRows(): T[]
method clearSelection (line 141) | public clearSelection(): void
method on (line 146) | public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: ...
method createCalculation (line 151) | public createCalculation(field: Field<T>): CalculationBuilder<T>
method createCSV (line 156) | public createCSV(): CSVBuilder<T>
method createView (line 161) | public createView(columns: ColumnView[]): ViewBuilder<T>
method getView (line 167) | public getView(): ViewBuilder<T>
method translate (line 172) | private translate(i18n: Internationalization): void
FILE: src/lib/src/client/builders/AdvancedFilterBuilder.svelte.ts
class AdvancedFilterBuilder (line 5) | class AdvancedFilterBuilder<Row>
method constructor (line 15) | constructor(filterHandler: FilterHandler<Row>, field: Field<Row>, chec...
method set (line 24) | public set(value: unknown, check?: Check): void
method isNotRecursive (line 39) | public isNotRecursive()
method clear (line 45) | public clear(): void
method cleanup (line 52) | private cleanup()
FILE: src/lib/src/client/builders/CSVBuilder.svelte.ts
class CSVBuilder (line 3) | class CSVBuilder<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method download (line 12) | public download(filename: string)
method get (line 26) | public get(): string
method getRows (line 33) | private getRows()
method getHeader (line 47) | private getHeader()
FILE: src/lib/src/client/builders/CalculationBuilder.svelte.ts
type Sort (line 4) | type Sort = [key: 'value' | 'count', direction: 'asc' | 'desc']
class CalcultationBuilder (line 6) | class CalcultationBuilder<Row>
method constructor (line 12) | constructor(table: TableHandler<Row>, field: Field<Row>)
method distinct (line 18) | public distinct(param?: { sort: Sort }): { value: string, count: numbe...
method avg (line 43) | public avg(param?: { precision: number }): number
method sum (line 51) | public sum(param?: { precision: number }): number
method median (line 58) | public median(param?: { precision: number }): number
method bounds (line 72) | public bounds(): number[]
method round (line 84) | private round(value: number)
FILE: src/lib/src/client/builders/FilterBuilder.svelte.ts
class FilterBuilder (line 6) | class FilterBuilder<Row> implements FilterInterface
method constructor (line 15) | constructor(filterHandler: FilterHandler<Row>, field: Field<Row>, chec...
method set (line 23) | public set(check?: Check)
method init (line 28) | public init(value?: unknown)
method isNotRecursive (line 35) | public isNotRecursive()
method clear (line 41) | public clear()
method cleanup (line 47) | private cleanup()
FILE: src/lib/src/client/builders/QueryBuilder.svelte.ts
class QueryBuilder (line 4) | class QueryBuilder<Row>
method constructor (line 12) | constructor(queryHandler: QueryHandler<Row>)
method from (line 18) | public from(path: string[])
method where (line 24) | public where(filter: (row: any, value?: unknown) => boolean)
method set (line 32) | public set(value?: unknown)
method clear (line 38) | public clear()
method cleanup (line 44) | private cleanup()
FILE: src/lib/src/client/builders/RecordFilterBuilder.svelte.ts
class RecordFilterBuilder (line 4) | class RecordFilterBuilder
method constructor (line 11) | constructor(records: Row[])
method set (line 16) | public set()
method createRecords (line 21) | private createRecords(): readonly Row[]
FILE: src/lib/src/client/builders/SearchBuilder.svelte.ts
class SearchBuilder (line 5) | class SearchBuilder<Row> implements SearchInterface
method constructor (line 11) | constructor(searchHandler: SearchHandler<Row>, scope?: Field<Row>[])
method set (line 18) | public set()
method init (line 23) | public init(value?: string)
method recursive (line 30) | public recursive()
method regex (line 35) | public regex()
method clear (line 40) | public clear()
method cleanup (line 46) | private cleanup()
FILE: src/lib/src/client/builders/SortBuilder.svelte.ts
class SortBuilder (line 6) | class SortBuilder<Row> implements SortInterface
method constructor (line 15) | constructor(sortHandler: SortHandler<Row>, field: Field<Row>, params: ...
method set (line 22) | public set()
method init (line 27) | public init(direction?: 'asc' | 'desc')
method asc (line 34) | public asc()
method desc (line 39) | public desc()
method clear (line 44) | public clear()
method createIsActive (line 49) | private createIsActive()
method createDirection (line 57) | private createDirection()
FILE: src/lib/src/client/core/entry.ts
type Params (line 5) | type Params = {
FILE: src/lib/src/client/core/index.ts
type Search (line 10) | type Search<Row> = {
type Filter (line 17) | type Filter<Row> = {
type Query (line 26) | type Query<Row> = {
type Sort (line 33) | type Sort<Row> = {
FILE: src/lib/src/client/core/rows.ts
function recursive (line 54) | function recursive(i) {
FILE: src/lib/src/client/handlers/FilterHandler.svelte.ts
class FilterHandler (line 5) | class FilterHandler<Row>
method constructor (line 9) | constructor(table: TableHandler<Row>)
method set (line 14) | public set(value: unknown, field: Field<Row>, check: Check = null, id:...
method unset (line 27) | public unset(id: string)
FILE: src/lib/src/client/handlers/PageHandler.svelte.ts
class PageHandler (line 3) | class PageHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method goto (line 12) | public goto(page: number)
method previous (line 22) | public previous()
method next (line 27) | public next()
method last (line 32) | public last()
FILE: src/lib/src/client/handlers/QueryHandler.svelte.ts
class QueryHandler (line 5) | class QueryHandler<Row>
method constructor (line 9) | constructor(table: TableHandler<Row>)
method set (line 14) | public set(path: string[], value: unknown, check: Check, id: string)
method unset (line 21) | public unset(id: string)
FILE: src/lib/src/client/handlers/SearchHandler.svelte.ts
class SearchHandler (line 3) | class SearchHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method set (line 12) | public set(value: string, scope?: Field<Row>[])
method recursive (line 18) | public recursive(value: string, scope?: Field<Row>[])
method regex (line 24) | public regex(pattern: string, scope?: Field<Row>[])
method clear (line 30) | public clear()
FILE: src/lib/src/client/handlers/SelectHandler.svelte.ts
class SelectHandler (line 4) | class SelectHandler<Row>
method constructor (line 8) | constructor(table: TableHandler<Row>)
method set (line 13) | public set(value: unknown)
method all (line 22) | public all(scope: 'all' | 'currentPage')
method clear (line 40) | public clear()
method getRows (line 45) | public getRows()
FILE: src/lib/src/client/handlers/SortHandler.svelte.ts
class SortHandler (line 4) | class SortHandler<Row>
method constructor (line 9) | constructor(table: TableHandler<Row>)
method set (line 15) | public set(field: Field<Row>, id: string, params: SortParams = {})
method asc (line 28) | public asc(field: Field<Row>, id: string, { locales, options }: SortPa...
method desc (line 41) | public desc(field: Field<Row>, id: string, { locales, options }: SortP...
method clear (line 54) | public clear()
method restore (line 60) | public restore()
method save (line 68) | private save(sort: Sort<Row>)
FILE: src/lib/src/client/index.ts
type SortParams (line 30) | type SortParams = {
type Check (line 35) | type Check = (entry: unknown, value: unknown) => boolean
type TableParams (line 37) | type TableParams<Row> = {
type Criterion (line 44) | type Criterion = {
FILE: src/lib/src/server/AbstractTableHandler.svelte.ts
class AbstractTableHandler (line 4) | class AbstractTableHandler<Row>
method constructor (line 28) | constructor(data: Row[], params: TableParams<Row>)
method getState (line 37) | public getState(): State<Row>
method createPages (line 50) | private createPages()
method createPageCount (line 59) | private createPageCount()
method createPagesWithEllipsis (line 65) | private createPagesWithEllipsis()
method createRowCount (line 99) | private createRowCount()
method createIsAllSelected (line 117) | private createIsAllSelected()
FILE: src/lib/src/server/TableHandler.svelte.ts
class TableHandler (line 20) | class TableHandler<T extends Row = any> extends AbstractTableHandler<T> ...
method constructor (line 31) | constructor(data: T[] = [], params: TableParams<T> = { rowsPerPage: 5 })
method load (line 43) | public load(callback: (state: State<T>) => Promise<T[]>): void
method invalidate (line 48) | public invalidate(): void
method setRowsPerPage (line 53) | public setRowsPerPage(value: number)
method setPage (line 59) | public setPage(value: number | 'previous' | 'next' | 'last'): void
method clearSearch (line 69) | public clearSearch(): void
method createSearch (line 74) | public createSearch(): SearchBuilder<T>
method createSort (line 79) | public createSort(field: Field<T>): SortBuilder<T>
method clearFilters (line 87) | public clearFilters(): void
method createFilter (line 93) | public createFilter(field: Field<T>): FilterBuilder<T>
method select (line 101) | public select(value: T[keyof T])
method selectAll (line 106) | public selectAll(): void
method clearSelection (line 111) | public clearSelection(): void
method on (line 116) | public on(event: 'change' | 'clearFilters' | 'clearSearch', callback: ...
method createView (line 121) | public createView(columns: ColumnView[]): ViewBuilder<T>
method getView (line 127) | public getView(): ViewBuilder<T>
method translate (line 132) | private translate(i18n: Internationalization): Internationalization
FILE: src/lib/src/server/builders/FilterBuilder.svelte.ts
class FilterBuilder (line 3) | class FilterBuilder<Row>
method constructor (line 10) | constructor(filterHandler: FilterHandler<Row>, field: keyof Row)
method set (line 17) | public set()
method init (line 26) | public init(value?: string)
method clear (line 34) | public clear()
method cleanup (line 40) | private cleanup()
FILE: src/lib/src/server/builders/SearchBuilder.svelte.ts
class SearchBuilder (line 4) | class SearchBuilder<Row> implements SearchInterface
method constructor (line 10) | constructor(table: TableHandler<Row>)
method set (line 16) | public set()
method init (line 25) | public init(value?: string)
method clear (line 33) | public clear()
method cleanup (line 41) | private cleanup()
FILE: src/lib/src/server/builders/SortBuilder.svelte.ts
class SortBuilder (line 4) | class SortBuilder<Row> implements SortInterface
method constructor (line 11) | constructor(sortHandler: SortHandler<Row>, field: keyof Row)
method set (line 17) | public set()
method init (line 22) | public init(direction?: 'asc' | 'desc')
method asc (line 29) | public asc()
method desc (line 34) | public desc()
method createIsActive (line 39) | private createIsActive()
method createDirection (line 47) | private createDirection()
FILE: src/lib/src/server/handlers/FetchHandler.svelte.ts
class FetchHandler (line 4) | class FetchHandler<Row>
method constructor (line 10) | constructor(table: TableHandler<Row>)
method set (line 15) | public set(callback: (state: State) => Promise<Row[]>)
method invalidate (line 20) | public async invalidate()
method trigger (line 27) | private async trigger()
FILE: src/lib/src/server/handlers/FilterHandler.svelte.ts
class FilterHandler (line 3) | class FilterHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method set (line 12) | public set(value: string | number, field: keyof Row)
method unset (line 20) | public unset(field: keyof Row)
method clear (line 25) | public clear()
FILE: src/lib/src/server/handlers/PageHandler.svelte.ts
class PageHandler (line 3) | class PageHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method goto (line 12) | public goto(number: number)
method previous (line 30) | public previous()
method next (line 35) | public next()
FILE: src/lib/src/server/handlers/SearchHandler.svelte.ts
class SearchHandler (line 3) | class SearchHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>) {
method clear (line 11) | public clear()
FILE: src/lib/src/server/handlers/SelectHandler.svelte.ts
class SelectHandler (line 3) | class SelectHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method set (line 12) | public set(value: Row[keyof Row])
method all (line 22) | public all()
method clear (line 33) | public clear()
FILE: src/lib/src/server/handlers/SortHandler.svelte.ts
class SortHandler (line 3) | class SortHandler<Row>
method constructor (line 7) | constructor(table: TableHandler<Row>)
method set (line 12) | public set(field: keyof Row)
method asc (line 27) | public asc(field: keyof Row)
method desc (line 33) | public desc(field: keyof Row)
FILE: src/lib/src/server/index.ts
type State (line 19) | type State<T extends Row = any> = {
type TableParams (line 29) | type TableParams<Row> = {
type Filter (line 37) | type Filter<Row> = {
type Sort (line 42) | type Sort<Row> = {
FILE: src/lib/src/shared/EventDispatcher.ts
class EventDispatcher (line 2) | class EventDispatcher
method add (line 11) | public add(event: keyof EventDispatcher['listeners'], callback: () => ...
method dispatch (line 16) | public async dispatch(event: keyof EventDispatcher['listeners'])
method run (line 26) | private run()
FILE: src/lib/src/shared/builders/HighlightBuilder.svelte.ts
class HighlightBuilder (line 5) | class HighlightBuilder<Row>
method constructor (line 12) | constructor(table: TableHandlerInterface<Row>, selector: string = 'tbo...
method createHighlight (line 19) | private createHighlight()
method createKeywords (line 32) | private createKeywords(): string[]
method emphasize (line 40) | private emphasize(node: Node)
FILE: src/lib/src/shared/builders/ViewBuilder.svelte.ts
class ViewBuilder (line 3) | class ViewBuilder<Row>
method constructor (line 10) | constructor(table: TableHandlerInterface<Row>, columns: ColumnView[])
method toggle (line 17) | public toggle(name: string)
method createColumns (line 26) | private createColumns(columns: ColumnView[])
method preset (line 57) | private preset()
method freeze (line 72) | private freeze(index: number, left = 0)
method setPosition (line 85) | public setPosition(current: number, destination: number)
FILE: src/lib/src/shared/index.ts
type Row (line 12) | type Row = { [key: string | number | symbol]: any }
type Field (line 14) | type Field<Row> = keyof Row | ((row: Row) => unknown)
type TableHandlerInterface (line 16) | interface TableHandlerInterface<Row> {
type Internationalization (line 34) | type Internationalization = {
type ColumnView (line 45) | type ColumnView = {
type SearchInterface (line 53) | interface SearchInterface {
type FilterInterface (line 59) | interface FilterInterface {
type SortInterface (line 65) | interface SortInterface {
FILE: src/routes/examples/client/crud/api.svelte.ts
class API (line 3) | class API
method create (line 7) | public create(user)
method destroy (line 13) | public destroy(user)
method update (line 18) | public update(user)
FILE: src/routes/examples/server/hello-world/api.ts
type Row (line 3) | type Row = { id: string, name: string, email: string, body: string }
FILE: src/routes/examples/server/pokedex-api/api.ts
type PokemonStat (line 2) | type PokemonStat = {
FILE: src/routes/examples/server/ssr-user/params.svelte.ts
class SSRFilters (line 5) | class SSRFilters {
method constructor (line 8) | constructor() {
method get (line 12) | get(name: string) {
method update (line 16) | update(filters: Record<string, string>) {
method fromState (line 31) | fromState(s: State) {
method clear (line 60) | clear(...params: string[]) {
method isFiltered (line 69) | isFiltered(...params: string[]) {
FILE: src/site/Site.svelte.ts
class Site (line 5) | class Site
method setMode (line 10) | public async setMode(value: 'client' | 'server' | string)
method getMode (line 18) | public getMode()
FILE: src/site/index.ts
type Code (line 32) | type Code = {
FILE: src/site/utils/viewport.ts
function ensureIntersectionObserver (line 3) | function ensureIntersectionObserver() {
function viewport (line 19) | function viewport(element) {
Condensed preview — 640 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,311K chars).
[
{
"path": ".gitignore",
"chars": 154,
"preview": ".DS_Store\nnode_modules\n/build\n/.svelte-kit\n/.vscode\n/package\n/build\n.env\n.env.*\n/dist\n!.env.example\nvite.config.js.times"
},
{
"path": ".npmrc",
"chars": 19,
"preview": "engine-strict=true\n"
},
{
"path": "CHANGELOG.md",
"chars": 6597,
"preview": "# 2.8.0 - 2025-12-17\n### Added\n[client] Disable filter recursion [#187](https://github.com/vincjo/datatables/issues/187)"
},
{
"path": "CONTRIBUTING.md",
"chars": 255,
"preview": "# Contributing\nContributions are welcome.\n\n1. Fork the project\n2. ⚠️ Create your feature branch `git checkout -b myfeatu"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2020 vincjo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 1692,
"preview": "<div align=\"center\">\n <img align=\"center\" src=\"./static/logo.svg\" alt=\"logo\" width=\"172\"/>\n <p align=\"center\">\n "
},
{
"path": "datatables.code-workspace",
"chars": 573,
"preview": "{\n\t\"folders\": [\n\t\t{ \"path\": \"./\" },\n\t\t{ \"path\": \"../autodoc\"}\n\t],\n\t\"settings\": {\n\t\t\"restoreTerminals.terminals\": [\n\t\t\t {"
},
{
"path": "ecosystem.config.cjs",
"chars": 689,
"preview": "module.exports = {\n apps: [\n {\n name: 'datatables',\n script: './build/index.js',\n "
},
{
"path": "mdsvex.config.js",
"chars": 265,
"preview": "import { defineMDSveXConfig as defineConfig } from 'mdsvex'\n\nconst config = defineConfig({\n extensions: ['.svelte.md'"
},
{
"path": "package.json",
"chars": 2848,
"preview": "{\n \"name\": \"@vincjo/datatables\",\n \"version\": \"2.8.0\",\n \"keywords\": [\n \"svelte sveltejs table tables data"
},
{
"path": "src/app.d.ts",
"chars": 304,
"preview": "/// <reference types=\"@sveltejs/kit\" />\n\n// See https://kit.svelte.dev/docs/types#app\n// for information about these int"
},
{
"path": "src/app.html",
"chars": 822,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"\" data-mode=\"\">\n <head>\n <meta charset=\"utf-8\" />\n <link re"
},
{
"path": "src/hooks.server.ts",
"chars": 698,
"preview": "import type { RequestEvent } from '@sveltejs/kit'\n\n\nexport const handle = async ({ event, resolve }) => {\n const them"
},
{
"path": "src/lib/index.ts",
"chars": 596,
"preview": "\nexport {\n // class:\n TableHandler,\n RecordFilter,\n // components:\n Datatable,\n Search,\n RowsPerPag"
},
{
"path": "src/lib/legacy/index.ts",
"chars": 889,
"preview": "// Reexport your entry components here\nimport DataHandler from './local/DataHandler'\nimport Datatable from './local/"
},
{
"path": "src/lib/legacy/local/Comparator.ts",
"chars": 2857,
"preview": "import type { Criterion } from '$lib/legacy/local'\n\nexport const check = {\n\n isLike: (entry: any, value: any) => {\n "
},
{
"path": "src/lib/legacy/local/Context.ts",
"chars": 7743,
"preview": "import { writable, derived, type Writable, type Readable } from 'svelte/store'\nimport type { Filter, Sort, Comparator, C"
},
{
"path": "src/lib/legacy/local/DataHandler.ts",
"chars": 7109,
"preview": "import Context from './Context'\nimport SortHandler from './handlers/SortHandler'\nimport Se"
},
{
"path": "src/lib/legacy/local/Datatable.svelte",
"chars": 2310,
"preview": "<script lang=\"ts\">\n import { type DataHandler, type Row, Search, RowsPerPage, RowCount, Pagination } from '$lib/legac"
},
{
"path": "src/lib/legacy/local/Pagination.svelte",
"chars": 2998,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/local'\n\n type T = $$Generic<Row>\n\n expor"
},
{
"path": "src/lib/legacy/local/RowCount.svelte",
"chars": 919,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/local'\n\n type T = $$Generic<Row>\n\n expor"
},
{
"path": "src/lib/legacy/local/RowsPerPage.svelte",
"chars": 902,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/local'\n\n type T = $$Generic<Row>\n\n expor"
},
{
"path": "src/lib/legacy/local/Search.svelte",
"chars": 880,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/local'\n\n type T = $$Generic<Row>\n\n expor"
},
{
"path": "src/lib/legacy/local/Th.svelte",
"chars": 2051,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Field, Row } from '$lib/legacy/local'\n\n type T = $$Generic<Row>\n\n "
},
{
"path": "src/lib/legacy/local/ThFilter.svelte",
"chars": 1206,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Field, Comparator, Row } from '$lib/legacy/local'\n\n type T = $$Gene"
},
{
"path": "src/lib/legacy/local/handlers/EventHandler.ts",
"chars": 746,
"preview": "import { writable } from 'svelte/store'\n\n\nexport default class EventHandler\n{\n private events = {\n change "
},
{
"path": "src/lib/legacy/local/handlers/FilterHandler.ts",
"chars": 2528,
"preview": "import type { Filter, Field, Comparator, EventHandler, Criterion } from '$lib/legacy/local'\nimport { isNotNull } from '."
},
{
"path": "src/lib/legacy/local/handlers/PageHandler.ts",
"chars": 1347,
"preview": "import type Context from '$lib/legacy/local/Context'\nimport { type Writable, type Readable, get } from 'svelte/store'\nim"
},
{
"path": "src/lib/legacy/local/handlers/SearchHandler.ts",
"chars": 862,
"preview": "import type Context from '$lib/legacy/local/Context'\nimport type { Writable } from 'svelte/store'\nimport type { EventHan"
},
{
"path": "src/lib/legacy/local/handlers/SelectHandler.ts",
"chars": 1798,
"preview": "import type Context from '$lib/legacy/local/Context'\nimport { type Writable, type Readable, get } from 'svelte/store'\nim"
},
{
"path": "src/lib/legacy/local/handlers/SortHandler.ts",
"chars": 4535,
"preview": "import type Context from '$lib/legacy/local/Context'\nimport type { Sort, Field, EventHandler } from '$lib/legacy/local'\n"
},
{
"path": "src/lib/legacy/local/helpers/AdvancedFilterHelper.ts",
"chars": 1608,
"preview": "import type { Field, Comparator, Criterion } from '$lib/legacy/local'\nimport type FilterHandler from '$lib/legacy/local/"
},
{
"path": "src/lib/legacy/local/helpers/CalculationHelper.ts",
"chars": 2779,
"preview": "import type { Field } from '$lib/legacy/local'\nimport type Context from '$lib/legacy/local/Context'\nimport { type Writab"
},
{
"path": "src/lib/legacy/local/helpers/FilterHelper.ts",
"chars": 1285,
"preview": "import type { Field, Comparator } from '$lib/legacy/local'\nimport { check } from '$lib/legacy/local/Comparator'\nimport t"
},
{
"path": "src/lib/legacy/local/index.ts",
"chars": 2067,
"preview": "// Reexport your entry components here\nimport DataHandler from './DataHandler'\nimport Datatable from './Datatable.sv"
},
{
"path": "src/lib/legacy/local/utils.ts",
"chars": 762,
"preview": "import type { Row, Field } from '$lib/legacy/local'\n\nexport const isNull = (value: any) => {\n if (value === null || v"
},
{
"path": "src/lib/legacy/remote/Context.ts",
"chars": 5822,
"preview": "import { type Writable, writable, get, derived, type Readable } from 'svelte/store'\nimport type { State, Sort, Filter } "
},
{
"path": "src/lib/legacy/remote/DataHandler.ts",
"chars": 5783,
"preview": "import Context from './Context'\nimport TriggerHandler from './handlers/TriggerHandler'\nimport SortHandler "
},
{
"path": "src/lib/legacy/remote/Datatable.svelte",
"chars": 2500,
"preview": "<script lang=\"ts\">\n import { type DataHandler, type Row, Search, RowsPerPage, RowCount, SelectedCount, Pagination } f"
},
{
"path": "src/lib/legacy/remote/Pagination.svelte",
"chars": 4055,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/README.md",
"chars": 651,
"preview": "<div align=\"center\">\n <img align=\"center\" src=\"../../../static/logo-remote.svg\" alt=\"logo\" width=\"172\"/>\n <p align"
},
{
"path": "src/lib/legacy/remote/RowCount.svelte",
"chars": 1055,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/RowsPerPage.svelte",
"chars": 986,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/Search.svelte",
"chars": 846,
"preview": "<script lang=\"ts\">\n\timport type { DataHandler, Row } from '$lib/legacy/remote'\n\n\ttype T = $$Generic<Row>\n\n\texport let ha"
},
{
"path": "src/lib/legacy/remote/SelectedCount.svelte",
"chars": 809,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/Th.svelte",
"chars": 2052,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/ThFilter.svelte",
"chars": 1235,
"preview": "<script lang=\"ts\">\n import type { DataHandler, Row } from '$lib/legacy/remote'\n\n type T = $$Generic<Row>\n\n expo"
},
{
"path": "src/lib/legacy/remote/handlers/EventHandler.ts",
"chars": 746,
"preview": "import { writable } from 'svelte/store'\n\n\nexport default class EventHandler\n{\n private events = {\n change "
},
{
"path": "src/lib/legacy/remote/handlers/FilterHandler.ts",
"chars": 785,
"preview": "import type { Filter } from '$lib/legacy/remote'\nimport type Context from '$lib/legacy/remote/Context'\nimport type { Wri"
},
{
"path": "src/lib/legacy/remote/handlers/PageHandler.ts",
"chars": 1947,
"preview": "import type Context from '$lib/legacy/remote/Context'\nimport { type Writable, type Readable, get } from 'svelte/store'\ni"
},
{
"path": "src/lib/legacy/remote/handlers/SearchHandler.ts",
"chars": 412,
"preview": "import type Context from '$lib/legacy/remote/Context'\nimport type { Writable } from 'svelte/store'\n\nexport default class"
},
{
"path": "src/lib/legacy/remote/handlers/SelectHandler.ts",
"chars": 1771,
"preview": "import type Context from '$lib/legacy/remote/Context'\nimport { type Writable, type Readable, get } from 'svelte/store'\n\n"
},
{
"path": "src/lib/legacy/remote/handlers/SortHandler.ts",
"chars": 3947,
"preview": "import type Context from '$lib/legacy/remote/Context'\nimport type { Order } from '$lib/legacy/remote'\nimport { type Writ"
},
{
"path": "src/lib/legacy/remote/handlers/TriggerHandler.ts",
"chars": 661,
"preview": "import type { State } from '$lib/legacy/remote'\nimport type Context from '$lib/legacy/remote/Context'\n\n\nexport default c"
},
{
"path": "src/lib/legacy/remote/index.ts",
"chars": 1808,
"preview": "// Reexport your entry components here\nimport DataHandler from './DataHandler'\nimport Datatable from './Data"
},
{
"path": "src/lib/server/index.ts",
"chars": 343,
"preview": "export {\n // class:\n TableHandler,\n // components:\n Datatable,\n Search,\n RowsPerPage,\n Th,\n ThSo"
},
{
"path": "src/lib/src/client/AbstractTableHandler.svelte.ts",
"chars": 5033,
"preview": "import type { Field, TableParams } from '$lib/src/client'\nimport { EventDispatcher } from '$lib/src/shared'\nimport { ty"
},
{
"path": "src/lib/src/client/TableHandler.svelte.ts",
"chars": 5964,
"preview": "import { untrack } from 'svelte'\nimport AbstractTableHandler from './AbstractTableHandler.svelte'\n\nimpo"
},
{
"path": "src/lib/src/client/builders/AdvancedFilterBuilder.svelte.ts",
"chars": 1868,
"preview": "import type { Field, Check, Criterion } from '$lib/src/client'\nimport type FilterHandler from '$lib/src/cl"
},
{
"path": "src/lib/src/client/builders/CSVBuilder.svelte.ts",
"chars": 1371,
"preview": "import type { TableHandler } from '$lib/src/client'\n\nexport default class CSVBuilder<Row>\n{\n private table: TableHand"
},
{
"path": "src/lib/src/client/builders/CalculationBuilder.svelte.ts",
"chars": 3081,
"preview": "import type { Field, TableHandler } from '$lib/src/client'\nimport { sort, parse } from '$lib/src/client/cor"
},
{
"path": "src/lib/src/client/builders/FilterBuilder.svelte.ts",
"chars": 1417,
"preview": "import type { Field, Check } from '$lib/src/client'\nimport type FilterHandler from '../handlers/FilterHandler.s"
},
{
"path": "src/lib/src/client/builders/QueryBuilder.svelte.ts",
"chars": 1125,
"preview": "import type { Check } from '$lib/src/client'\nimport type QueryHandler from '../handlers/QueryHandler.svelte'\n\n"
},
{
"path": "src/lib/src/client/builders/RecordFilterBuilder.svelte.ts",
"chars": 753,
"preview": "import { match, check, isNotNull } from '$lib/src/client/core'\nimport type { Row } from '$lib/src/client'\n\nexpor"
},
{
"path": "src/lib/src/client/builders/SearchBuilder.svelte.ts",
"chars": 1158,
"preview": "import { type Field } from '$lib/src/client'\nimport type SearchHandler from '../handlers/SearchHandler.svelte'\ni"
},
{
"path": "src/lib/src/client/builders/SortBuilder.svelte.ts",
"chars": 1633,
"preview": "import type { Field, SortParams } from '$lib/src/client'\nimport type SortHandler from '../handlers/SortHan"
},
{
"path": "src/lib/src/client/core/check.ts",
"chars": 3149,
"preview": "import { isNull, stringify, isNotNull } from './value'\nimport type { Criterion, Check } from '$lib/src/client'\n\n\nexport "
},
{
"path": "src/lib/src/client/core/entry.ts",
"chars": 3155,
"preview": "import { isNull, isObject, isObjectArray } from './value'\nimport { check } from './check'\nimport type { Criterion, Check"
},
{
"path": "src/lib/src/client/core/field.ts",
"chars": 578,
"preview": "import type { Field } from '$lib/src/client'\n\nexport const parse = <Row>(field: Field<Row>, id?: string) => {\n if (ty"
},
{
"path": "src/lib/src/client/core/index.ts",
"chars": 880,
"preview": "import type { Field, Check } from '$lib/src/client'\n\n\nexport { isNull, isNotNull, stringify } from './value'\nexport { ma"
},
{
"path": "src/lib/src/client/core/rows.ts",
"chars": 5289,
"preview": "import type { Field } from '$lib/src/client'\nimport { type Search, type Filter, type Query } from '$lib/src/client/core'"
},
{
"path": "src/lib/src/client/core/value.ts",
"chars": 895,
"preview": "\n\nexport const isNull = (value: unknown) => {\n if (value === null || value === undefined || value === '') return true"
},
{
"path": "src/lib/src/client/handlers/FilterHandler.svelte.ts",
"chars": 904,
"preview": "import type { Field, Check, TableHandler } from '$lib/src/client'\nimport { isNotNull, parse } from '$li"
},
{
"path": "src/lib/src/client/handlers/PageHandler.svelte.ts",
"chars": 710,
"preview": "import type { TableHandler } from '$lib/src/client'\n\nexport default class PageHandler<Row>\n{\n private table: TableHan"
},
{
"path": "src/lib/src/client/handlers/QueryHandler.svelte.ts",
"chars": 690,
"preview": "import type { Field, Check, TableHandler } from '$lib/src/client'\nimport { isNotNull } from '$lib/src/client/core'\n\n\nex"
},
{
"path": "src/lib/src/client/handlers/SearchHandler.svelte.ts",
"chars": 880,
"preview": "import { type TableHandler, type Field, type Criterion, check } from '$lib/src/client'\n\nexport default class SearchHandl"
},
{
"path": "src/lib/src/client/handlers/SelectHandler.svelte.ts",
"chars": 1561,
"preview": "import type { TableHandler } from '$lib/src/client'\nimport { parse } from '$lib/src/client/core'\n\nexpo"
},
{
"path": "src/lib/src/client/handlers/SortHandler.svelte.ts",
"chars": 2584,
"preview": "import type { Field, TableHandler, SortParams } from '$lib/src/client'\nimport { type Sort, parse, sort } from '$lib/src/"
},
{
"path": "src/lib/src/client/index.ts",
"chars": 1554,
"preview": "export { default as TableHandler } from './TableHandler.svelte'\nexport { check } from './core'\n\nimport type { Internatio"
},
{
"path": "src/lib/src/server/AbstractTableHandler.svelte.ts",
"chars": 4353,
"preview": "import type { State, Sort, Filter, TableParams } from '$lib/src/server'\nimport { EventDispatcher } from '$lib/src/shared"
},
{
"path": "src/lib/src/server/TableHandler.svelte.ts",
"chars": 4679,
"preview": "import AbstractTableHandler from './AbstractTableHandler.svelte'\n\nimport FetchHandler from './handlers/FetchHand"
},
{
"path": "src/lib/src/server/builders/FilterBuilder.svelte.ts",
"chars": 1076,
"preview": "import type FilterHandler from '../handlers/FilterHandler.svelte'\n\nexport default class FilterBuilder<Row>\n{\n public "
},
{
"path": "src/lib/src/server/builders/SearchBuilder.svelte.ts",
"chars": 1032,
"preview": "import type TableHandler from '../TableHandler.svelte'\nimport type { SearchInterface } from '$lib/src/shared'\n\nexport de"
},
{
"path": "src/lib/src/server/builders/SortBuilder.svelte.ts",
"chars": 1238,
"preview": "import type SortHandler from '../handlers/SortHandler.svelte'\nimport type { SortInterface } from '$lib/src/sha"
},
{
"path": "src/lib/src/server/handlers/FetchHandler.svelte.ts",
"chars": 873,
"preview": "import type { State, TableHandler } from '$lib/src/server'\n\n\nexport default class FetchHandler<Row>\n{\n private table:"
},
{
"path": "src/lib/src/server/handlers/FilterHandler.svelte.ts",
"chars": 732,
"preview": "import type { TableHandler } from '$lib/src/server'\n\nexport default class FilterHandler<Row>\n{\n private table: TableH"
},
{
"path": "src/lib/src/server/handlers/PageHandler.svelte.ts",
"chars": 924,
"preview": "import type { TableHandler } from '$lib/src/server'\n\nexport default class PageHandler<Row>\n{\n private table: TableHan"
},
{
"path": "src/lib/src/server/handlers/SearchHandler.svelte.ts",
"chars": 297,
"preview": "import type { TableHandler } from '$lib/src/server'\n\nexport default class SearchHandler<Row> \n{\n private table: Table"
},
{
"path": "src/lib/src/server/handlers/SelectHandler.svelte.ts",
"chars": 951,
"preview": "import type { TableHandler } from '$lib/src/server'\n\nexport default class SelectHandler<Row>\n{\n private table: TableH"
},
{
"path": "src/lib/src/server/handlers/SortHandler.svelte.ts",
"chars": 814,
"preview": "import type { TableHandler } from '$lib/src/server'\n\nexport default class SortHandler<Row>\n{\n private table: TableHan"
},
{
"path": "src/lib/src/server/index.ts",
"chars": 1001,
"preview": "export { default as TableHandler } from './TableHandler.svelte'\nimport type { Row, Internationalization } from '$lib/sr"
},
{
"path": "src/lib/src/shared/Datatable.svelte",
"chars": 3367,
"preview": "<script lang=\"ts\">\n import type { Snippet } from 'svelte'\n import { type TableHandlerInterface, Search, RowsPerPag"
},
{
"path": "src/lib/src/shared/EventDispatcher.ts",
"chars": 875,
"preview": "\nexport default class EventDispatcher\n{\n private listeners = {\n change : [] as (() => void)[],\n cl"
},
{
"path": "src/lib/src/shared/Pagination.svelte",
"chars": 3357,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface } from '$lib/src/shared'\n type T = $$Generic<Row>\n let "
},
{
"path": "src/lib/src/shared/RowCount.svelte",
"chars": 1361,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface } from '$lib/src/shared'\n\n type T = $$Generic<Row>\n let"
},
{
"path": "src/lib/src/shared/RowsPerPage.svelte",
"chars": 1147,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface } from '$lib/src/shared'\n\n type T = $$Generic<Row>\n let"
},
{
"path": "src/lib/src/shared/Search.svelte",
"chars": 1010,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface } from '$lib/src/shared'\n type T = $$Generic<Row>\n let "
},
{
"path": "src/lib/src/shared/Th.svelte",
"chars": 583,
"preview": "<script lang=\"ts\">\n import type { Snippet } from 'svelte'\n\n let { children }: { children?: Snippet } = $props()\n</"
},
{
"path": "src/lib/src/shared/ThFilter.svelte",
"chars": 1213,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface, Field } from '$lib/src/shared'\n import type { Check } fro"
},
{
"path": "src/lib/src/shared/ThSort.svelte",
"chars": 1875,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface, Field } from '$lib/src/shared'\n import type { Snippet } f"
},
{
"path": "src/lib/src/shared/builders/HighlightBuilder.svelte.ts",
"chars": 2067,
"preview": "import type { TableHandlerInterface } from '$lib/src/shared'\nimport { stringify } from '$lib/src/client/core'\n\n\nexport d"
},
{
"path": "src/lib/src/shared/builders/ViewBuilder.svelte.ts",
"chars": 3425,
"preview": "import type { TableHandlerInterface, ColumnView } from '$lib/src/shared'\n\nexport default class ViewBuilder<Row>\n{\n pu"
},
{
"path": "src/lib/src/shared/clsx/Datatable.svelte",
"chars": 3379,
"preview": "<script lang=\"ts\">\n import type { Snippet } from 'svelte'\n import { type TableHandlerInterface, Search, RowsPerPag"
},
{
"path": "src/lib/src/shared/clsx/Pagination.svelte",
"chars": 3408,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface } from '$lib/src/shared'\n type T = $$Generic<Row>\n let "
},
{
"path": "src/lib/src/shared/clsx/ThSort.svelte",
"chars": 1810,
"preview": "<script lang=\"ts\">\n import type { TableHandlerInterface, Field } from '$lib/src/shared'\n import type { Snippet } f"
},
{
"path": "src/lib/src/shared/index.ts",
"chars": 2349,
"preview": "export { default as Datatable } from './Datatable.svelte'\nexport { default as Pagination } from './Pagina"
},
{
"path": "src/lib/style.css",
"chars": 1234,
"preview": ".svelte-simple-datatable table {\n border-collapse: separate;\n border-spacing: 0;\n width: 100%;\n background: "
},
{
"path": "src/routes/+layout.svelte",
"chars": 1064,
"preview": "<script>\n import Header from './Header.svelte'\n // import { ModalContainer } from 'gros/modal'\n "
},
{
"path": "src/routes/+page.svelte",
"chars": 4977,
"preview": "<script lang=\"ts\">\n import { site } from '$site'\n import { path } from 'gros/page'\n import Description "
},
{
"path": "src/routes/Description.svelte",
"chars": 1539,
"preview": "<script>\n const checked = `<svg width=\"28px\" height=\"28px\" viewBox=\"0 0 24 24\"><path fill=\"#4caf50\" d=\"M5 21q-.825 0-"
},
{
"path": "src/routes/Header.svelte",
"chars": 4524,
"preview": "<script lang=\"ts\">\n import { site, Logo } from '$site'\n import { path } from 'gros/page'\n import Github "
},
{
"path": "src/routes/Header_Github.svelte",
"chars": 2513,
"preview": "<script>\n let { isMobile = false } = $props()\n</script>\n\n{#if isMobile}\n <a href=\"https://github.com/vincjo/datata"
},
{
"path": "src/routes/Header_MobileNav.svelte",
"chars": 2871,
"preview": "<script lang=\"ts\">\n import { slide } from 'svelte/transition'\n import { path } from 'gros/page'\n import "
},
{
"path": "src/routes/Header_Mode.svelte",
"chars": 5019,
"preview": "<script lang=\"ts\">\n import { Tooltip } from 'gros/tooltip'\n import { site } from '$site'\n import { path } "
},
{
"path": "src/routes/Header_Theme.svelte",
"chars": 5821,
"preview": "<script lang=\"ts\">\n import { theme } from 'gros/theme'\n let { isMobile = false } = $props()\n</script>\n\n{#if isMobi"
},
{
"path": "src/routes/Header_Version.svelte",
"chars": 1363,
"preview": "<script lang=\"ts\">\n import { Dropdown } from 'gros/dropdown'\n</script>\n\n\n<Dropdown position=\"bottom-start\">\n <asid"
},
{
"path": "src/routes/about/+layout.svelte",
"chars": 4396,
"preview": "<script lang=\"ts\">\n import Nav from './Nav.svelte'\n import type { Snippet } from 'svelte'\n import MobileNav fro"
},
{
"path": "src/routes/about/+page.svelte",
"chars": 0,
"preview": ""
},
{
"path": "src/routes/about/Nav.svelte",
"chars": 1939,
"preview": "<script lang=\"ts\">\n import { path } from 'gros/page'\n type Props = { nav: { title: string, page: string, icon: str"
},
{
"path": "src/routes/about/Nav_Mobile.svelte",
"chars": 3529,
"preview": "<script lang=\"ts\">\n import { path } from 'gros/page'\n import { clickOutside } from 'gros/action'\n impor"
},
{
"path": "src/routes/about/[slug]/+page.server.ts",
"chars": 598,
"preview": "import { readFileSync } from 'fs'\n\nexport const load = async ({ params }) => {\n const slug = params.slug\n try {\n "
},
{
"path": "src/routes/about/[slug]/+page.svelte",
"chars": 415,
"preview": "<script lang=\"ts\">\n let { data } = $props()\n</script>\n\n<section class=\"about\">\n {@html data.content}\n</section>\n\n\n"
},
{
"path": "src/routes/api/[mode]/+layout.server.ts",
"chars": 553,
"preview": "import { internalFilter } from '$site'\n\nexport const load = async ({ params }) => {\n const response = await fetch(`BA"
},
{
"path": "src/routes/api/[mode]/+layout.svelte",
"chars": 1338,
"preview": "<script lang=\"ts\">\n import Nav from './Nav.svelte'\n import MobibleNav from './Nav_Mobile.svelte'\n import { path"
},
{
"path": "src/routes/api/[mode]/+page.svelte",
"chars": 106,
"preview": "<script>\n import Content from './content.svx'\n</script>\n\n<section class=\"md\">\n <Content/>\n</section>"
},
{
"path": "src/routes/api/[mode]/Nav.svelte",
"chars": 1325,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import Key from './Nav_Key.svelte'\n import "
},
{
"path": "src/routes/api/[mode]/Nav_Key.svelte",
"chars": 2733,
"preview": "<script lang=\"ts\">\n import { site } from '$site'\n import { path } from 'gros/page'\n import { page } "
},
{
"path": "src/routes/api/[mode]/Nav_Mobile.svelte",
"chars": 2512,
"preview": "<script lang=\"ts\">\n import { clickOutside } from 'gros/action'\n import { fly, fade } from 'svelte/transition'\n "
},
{
"path": "src/routes/api/[mode]/[slug]/+page.server.ts",
"chars": 727,
"preview": "\n\nexport const load = async ({ params }) => {\n\n const mode = params.mode\n const [key, name] = params.slug.split('~"
},
{
"path": "src/routes/api/[mode]/[slug]/+page.svelte",
"chars": 846,
"preview": "<script lang=\"ts\">\n import { page } from '$app/stores'\n import Content from './Content.svelte'\n\n let { data } "
},
{
"path": "src/routes/api/[mode]/[slug]/Content.svelte",
"chars": 1088,
"preview": "<script lang=\"ts\">\n import Highlight from '$site/components/Highlight.svelte'\n import Ext from './Content_Ex"
},
{
"path": "src/routes/api/[mode]/[slug]/Content_Ext.svelte",
"chars": 1492,
"preview": "<script lang=\"ts\">\n import Prism from 'prismjs'\n import 'prism-svelte'\n import 'prismjs/components/prism-typesc"
},
{
"path": "src/routes/api/[mode]/content.svx",
"chars": 1268,
"preview": "<script>\n import { site } from '$site'\n</script>\n\n\n<span class=\"index\" style:color=\"var(--primary)\">{site.mode === 'c"
},
{
"path": "src/routes/api/[mode]/gen/+page.server.ts",
"chars": 526,
"preview": "import { dev } from '$app/environment'\n\nexport const load = async (url) => {\n if (!dev) {\n return {\n "
},
{
"path": "src/routes/api/[mode]/gen/+page.svelte",
"chars": 516,
"preview": "<script>\n import Board from './Board.svelte'\n let { data } = $props()\n</script>\n\n<section class=\"flex\">\n <h1>{d"
},
{
"path": "src/routes/api/[mode]/gen/Board.svelte",
"chars": 845,
"preview": "<script>\n import { loading } from 'gros/loading'\n\n\n const generate = async (mode) => {\n loading.start('Gene"
},
{
"path": "src/routes/api/[mode]/md/+layout.svelte",
"chars": 1399,
"preview": "<script lang=\"ts\">\n import Nav from './Nav.svelte'\n import { path } from 'gros/page'\n import { dev } from '$app"
},
{
"path": "src/routes/api/[mode]/md/+layout.ts",
"chars": 567,
"preview": "import { internalFilter } from '$site'\nimport { dev } from '$app/environment'\n\n\nexport const load = async ({ params }) ="
},
{
"path": "src/routes/api/[mode]/md/+page.svelte",
"chars": 107,
"preview": "\n<script>\n import Content from './content.svx'\n</script>\n\n<section class=\"md\">\n <Content/>\n</section>"
},
{
"path": "src/routes/api/[mode]/md/Nav.svelte",
"chars": 1547,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import Key from './Nav_Key.svelte'\n let { n"
},
{
"path": "src/routes/api/[mode]/md/Nav_Key.svelte",
"chars": 2517,
"preview": "<script lang=\"ts\">\n import { site } from '$site'\n import { path } from 'gros/page'\n import { page } from '$app/"
},
{
"path": "src/routes/api/[mode]/md/[slug]/+page.svelte",
"chars": 882,
"preview": "<script lang=\"ts\">\n import { page } from '$app/stores'\n import Content from './Content.svelte'\n import AST fro"
},
{
"path": "src/routes/api/[mode]/md/[slug]/+page.ts",
"chars": 285,
"preview": "\n\nexport const load = async ({ params }) => {\n\n const mode = params.mode\n const [key, name] = params.slug.split('~"
},
{
"path": "src/routes/api/[mode]/md/[slug]/AST.svelte",
"chars": 698,
"preview": "<script lang=\"ts\">\n import { page } from '$app/stores'\n\n let ast = $state(undefined)\n\n const load = async () =>"
},
{
"path": "src/routes/api/[mode]/md/[slug]/Content.svelte",
"chars": 340,
"preview": "<script lang=\"ts\">\n let { data }: { data: any, key: string } = $props()\n</script>\n\n\n<section>\n <h1>{data.name}</h1"
},
{
"path": "src/routes/api/[mode]/md/[slug]/Content_Ext.svelte",
"chars": 1412,
"preview": "<script lang=\"ts\">\n import Prism from 'prismjs'\n import 'prism-svelte'\n import 'prismjs/components/prism-typesc"
},
{
"path": "src/routes/api/[mode]/md/content.svx",
"chars": 291,
"preview": "<script>\n import { site } from '$site'\n</script>\n\n\n{#if site.mode === 'client'}\n <span style:color=\"var(--font)\">C"
},
{
"path": "src/routes/components/+layout.svelte",
"chars": 3396,
"preview": "<script>\n import Layout from '$site/components/docs/Layout.svelte'\n let { children } = $props()\n const root = `"
},
{
"path": "src/routes/components/+page.svelte",
"chars": 0,
"preview": ""
},
{
"path": "src/routes/docs/client/+layout.svelte",
"chars": 12836,
"preview": "<script>\n import Layout from '$site/components/docs/Layout.svelte'\n let { children } = $props()\n const root = `"
},
{
"path": "src/routes/docs/client/+page.server.ts",
"chars": 170,
"preview": "import { redirect } from '@sveltejs/kit'\nimport { path } from 'gros/page'\n\nexport const load = () => {\n redirect(307,"
},
{
"path": "src/routes/docs/client/add-on/csv-export/+page.svelte",
"chars": 115,
"preview": "<script>\n import Content from './content.svx'\n import Main from './Main.svelte'\n</script>\n\n<Content/>\n<Main/>"
},
{
"path": "src/routes/docs/client/add-on/csv-export/Main.svelte",
"chars": 1757,
"preview": "<script lang=\"ts\">\n import { TableHandler, Datatable, ThSort, Search, RowCount, Pagination } from '$lib/src/client'\n "
},
{
"path": "src/routes/docs/client/add-on/csv-export/content.svx",
"chars": 148,
"preview": "\n\n# CSV export\n\n```ts\nconst csv = table.createCSV()\n```\n\n```svelte\n<button onclick={() => csv.download('filename.csv')}>"
},
{
"path": "src/routes/docs/client/add-on/record-filter/+page.svelte",
"chars": 116,
"preview": "<script>\n import Content from './content.svx'\n import Main from './Main.svelte'\n</script>\n\n<Content/>\n\n<Main/>"
},
{
"path": "src/routes/docs/client/add-on/record-filter/Main.svelte",
"chars": 2798,
"preview": "<script lang=\"ts\">\n import { TableHandler, Datatable, ThSort } from '$lib/src/client'\n import { data } from './dat"
},
{
"path": "src/routes/docs/client/add-on/record-filter/content.svx",
"chars": 275,
"preview": "\n# Record filter\n\n```ts\nconst filter = table.createRecordFilter(distinct)\n```\n\n```svelte\n<input type=\"text\" bind:value={"
},
{
"path": "src/routes/docs/client/add-on/record-filter/data_cars.ts",
"chars": 5276,
"preview": "export const data = [{\"id\":1,\"make\":\"GMC\",\"model\":\"Yukon\",\"model_year\":1999,\"color\":\"#2a06a7\",\"price\":73958},\n{\"id\":2,\"m"
},
{
"path": "src/routes/docs/client/calculation/avg/+page.svelte",
"chars": 109,
"preview": "<script>\n import Code from './code.svx'\n</script>\n\n<section>\n <h1>Average</h1>\n <Code />\n</section>\n"
},
{
"path": "src/routes/docs/client/calculation/avg/Advanced.svelte",
"chars": 2475,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_parcel'\n\n con"
},
{
"path": "src/routes/docs/client/calculation/avg/Basic.svelte",
"chars": 2303,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_cars'\n\n const"
},
{
"path": "src/routes/docs/client/calculation/avg/code.svx",
"chars": 606,
"preview": "<script>\n import Basic from './Basic.svelte'\n import Advanced from './Advanced.svelte'\n</script>\n\n### Basic usage\n"
},
{
"path": "src/routes/docs/client/calculation/bounds/+page.svelte",
"chars": 118,
"preview": "<script>\n import Code from './code.svx'\n</script>\n\n<section>\n <h1>Bounds [min, max]</h1>\n <Code />\n</section>"
},
{
"path": "src/routes/docs/client/calculation/bounds/Basic.svelte",
"chars": 2770,
"preview": "<script lang=\"ts\">\n import { TableHandler, Datatable, ThSort } from '$lib/src/client'\n import { data } from '../da"
},
{
"path": "src/routes/docs/client/calculation/bounds/code.svx",
"chars": 264,
"preview": "<script>\n import Basic from './Basic.svelte'\n</script>\n\n### Basic usage\n```ts\nconst [min, max] = $derived(table.creat"
},
{
"path": "src/routes/docs/client/calculation/data_cars.ts",
"chars": 5276,
"preview": "export const data = [{\"id\":1,\"make\":\"GMC\",\"model\":\"Yukon\",\"model_year\":1999,\"color\":\"#2a06a7\",\"price\":73958},\n{\"id\":2,\"m"
},
{
"path": "src/routes/docs/client/calculation/data_grocery.ts",
"chars": 2950,
"preview": "export const data = [{\"id\":1,\"product\":\"Coffee Cup 16oz Foam\",\"price\":58.64},\n{\"id\":2,\"product\":\"Turnip - Wax\",\"price\":4"
},
{
"path": "src/routes/docs/client/calculation/data_parcel.ts",
"chars": 3734,
"preview": "export const data = [{\"id\":1,\"address\":\"53 Mariners Cove Place\",\"width\":35.2579,\"length\":97.6174},\n{\"id\":2,\"address\":\"81"
},
{
"path": "src/routes/docs/client/calculation/distinct/+page.svelte",
"chars": 116,
"preview": "<script>\n import Code from './code.svx'\n</script>\n\n<section>\n <h1>Distinct values</h1>\n <Code />\n</section>"
},
{
"path": "src/routes/docs/client/calculation/distinct/Basic.svelte",
"chars": 2919,
"preview": "<script lang=\"ts\">\n import { TableHandler, Datatable, ThSort } from '$lib/src/client'\n import { data } from '../da"
},
{
"path": "src/routes/docs/client/calculation/distinct/code.svx",
"chars": 729,
"preview": "<script>\n import Basic from './Basic.svelte'\n</script>\n\n### Basic usage\n\n```ts\nconst distinct = $derived.by(() => {\n "
},
{
"path": "src/routes/docs/client/calculation/median/+page.svelte",
"chars": 107,
"preview": "<script>\n import Code from './code.svx'\n</script>\n\n<section>\n <h1>Median</h1>\n <Code />\n</section>"
},
{
"path": "src/routes/docs/client/calculation/median/Advanced.svelte",
"chars": 2462,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_parcel'\n\n\n co"
},
{
"path": "src/routes/docs/client/calculation/median/Basic.svelte",
"chars": 2167,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_grocery'\n\n co"
},
{
"path": "src/routes/docs/client/calculation/median/code.svx",
"chars": 621,
"preview": "<script>\n import Basic from './Basic.svelte'\n import Advanced from './Advanced.svelte'\n</script>\n\n### Basic usage\n"
},
{
"path": "src/routes/docs/client/calculation/sum/+page.svelte",
"chars": 104,
"preview": "<script>\n import Code from './code.svx'\n</script>\n\n<section>\n <h1>Sum</h1>\n <Code />\n</section>"
},
{
"path": "src/routes/docs/client/calculation/sum/Advanced.svelte",
"chars": 2452,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_parcel'\n\n\n co"
},
{
"path": "src/routes/docs/client/calculation/sum/Basic.svelte",
"chars": 2157,
"preview": "<script lang=\"ts\">\n import { TableHandler } from '$lib/src/client'\n import { data } from '../data_grocery'\n\n co"
},
{
"path": "src/routes/docs/client/calculation/sum/code.svx",
"chars": 601,
"preview": "<script>\n import Basic from './Basic.svelte'\n import Advanced from './Advanced.svelte'\n</script>\n\n### Basic usage\n"
},
{
"path": "src/routes/docs/client/filters/Main.svelte",
"chars": 1480,
"preview": "<script>\n import data from '$site/data/data'\n import { TableHandler, Datatable, Th, Pagination } from '$lib/src/cl"
},
{
"path": "src/routes/docs/client/filters/check/+page.svelte",
"chars": 241,
"preview": "<script>\n import Content from './content.svx'\n import Content2 from './content2.svx'\n import Main from '../Main"
},
{
"path": "src/routes/docs/client/filters/check/Comparators.svelte",
"chars": 2173,
"preview": "<script lang=\"ts\">\n import Check from './Comparators_Check.svelte'\n const comparators = [\n { name: 'isLike'"
},
{
"path": "src/routes/docs/client/filters/check/Comparators_Check.svelte",
"chars": 2981,
"preview": "<script>\n import { TableHandler, check } from '$lib/src/client'\n import { Range } from 'gros/form'\n import { da"
},
{
"path": "src/routes/docs/client/filters/check/content.svx",
"chars": 298,
"preview": "# Check function\n\nAdditionally, you can specify a check function to match with values.\n\n<!-- This function reduces boile"
},
{
"path": "src/routes/docs/client/filters/check/content2.svx",
"chars": 349,
"preview": "<script>\n import Comparators from './Comparators.svelte'\n</script>\n\n\n## Available check functions:\n\n```ts\nimport { ch"
},
{
"path": "src/routes/docs/client/filters/check/data.ts",
"chars": 1722,
"preview": "export const data = {\n string: [\n { value: 'Aude' },\n { value: 'Théodule' },\n { value: null },\n "
},
{
"path": "src/routes/docs/client/filters/criteria/+page.svelte",
"chars": 73,
"preview": "<script>\n import Content from './content.svx'\n</script>\n\n\n<Content/>\n\n"
},
{
"path": "src/routes/docs/client/filters/criteria/Main.svelte",
"chars": 3187,
"preview": "\n<script lang=\"ts\">\n import { TableHandler, check } from '$lib/src/client'\n import { data } from './data'\n\n con"
},
{
"path": "src/routes/docs/client/filters/criteria/content.svx",
"chars": 457,
"preview": "<script>\n import Main from './Main.svelte'\n</script>\n\n\n# Multiple criteria filter\n\n```ts\nconst filter = table.createA"
},
{
"path": "src/routes/docs/client/filters/criteria/data.ts",
"chars": 2612,
"preview": "export const data = [{\"id\":1,\"task\":\"nulla suspendisse potenti cras in purus eu magna vulputate luctus cum sociis natoqu"
},
{
"path": "src/routes/docs/client/filters/highlight/+page.svelte",
"chars": 132,
"preview": "<script>\n import Content from './content.svx'\n import Main from '../Main.svelte'\n</script>\n\n\n<Content/>\n\n<Main isH"
},
{
"path": "src/routes/docs/client/filters/highlight/content.svx",
"chars": 369,
"preview": "\n# Highlight results\n\nTo highlight results, define \"highlight\" at `true` in `TableHandler` params:\n\n```ts\nconst table = "
},
{
"path": "src/routes/docs/client/filters/input/+page.svelte",
"chars": 117,
"preview": "<script>\n import Content from './content.svx'\n import Main from '../Main.svelte'\n</script>\n\n<Content/>\n\n<Main/>"
},
{
"path": "src/routes/docs/client/filters/input/content.svx",
"chars": 522,
"preview": "# Filter input \n\n```ts\nconst filter = table.createFilter('last_name')\n```\n\n```svelte\n<input type=\"text\" bind:value={filt"
},
{
"path": "src/routes/docs/client/filters/nested/+page.svelte",
"chars": 123,
"preview": "<script>\n import Content from './content.svx'\n import Nested from './Nested.svelte'\n</script>\n\n\n<Content/>\n\n<Neste"
},
{
"path": "src/routes/docs/client/filters/nested/Nested.svelte",
"chars": 2189,
"preview": "<script>\n import { TableHandler, Datatable, Th } from '$lib/src/client'\n\n const data = [\n { user: { name: '"
},
{
"path": "src/routes/docs/client/filters/nested/content.svx",
"chars": 274,
"preview": "# Nested prop \n\nYou may have nested properties that you need to filter specifically.\n\n`field` can be set as a callback w"
},
{
"path": "src/routes/docs/client/getting-started/hello-world/+page.svelte",
"chars": 102,
"preview": "<script>\n import Client from './Client.svx'\n let { data } = $props()\n</script>\n\n<Client {data}/>"
},
{
"path": "src/routes/docs/client/getting-started/hello-world/Client.svx",
"chars": 1733,
"preview": "<script>\n import Main from './Main.svelte'\n</script>\n\n# Hello World\n\nHere is a preview of what the default datatable "
},
{
"path": "src/routes/docs/client/getting-started/hello-world/Main.svelte",
"chars": 1254,
"preview": "<script lang=\"ts\">\n import { TableHandler, Datatable, ThSort, ThFilter } from '$lib/src/client'\n import myData fro"
}
]
// ... and 440 more files (download for full content)
About this extraction
This page contains the full source code of the vincjo/datatables GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 640 files (3.9 MB), approximately 1.1M tokens, and a symbol index with 450 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.