├── .npmrc ├── src ├── routes │ ├── +page.svelte │ └── examples │ │ ├── IRow.ts │ │ ├── +page.svelte │ │ ├── reset.css │ │ ├── server.ts │ │ ├── LocalTable.svelte │ │ └── RemoteTable.svelte ├── lib │ ├── filter.ts │ ├── functions.ts │ ├── index.ts │ ├── constants.ts │ ├── Row.svelte │ ├── Sort.svelte │ ├── interfaces.ts │ ├── Search.svelte │ ├── sort.ts │ ├── Pagination.svelte │ ├── tableStore.ts │ └── Table.svelte ├── app.html └── app.d.ts ├── static └── favicon.png ├── .gitignore ├── vite.config.ts ├── .eslintignore ├── .prettierignore ├── playwright.config.ts ├── .prettierrc ├── svelte.config.js ├── .eslintrc.cjs ├── tsconfig.json ├── README.md └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | Examples 2 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabiohvp/svelte-table/HEAD/static/favicon.png -------------------------------------------------------------------------------- /src/routes/examples/IRow.ts: -------------------------------------------------------------------------------- 1 | export interface IRow { 2 | name: string; 3 | lastName: string; 4 | age: number; 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/filter.ts: -------------------------------------------------------------------------------- 1 | export function filter(row: any, text: string) { 2 | text = text.toLocaleLowerCase(); 3 | const keys = Object.keys(row); 4 | 5 | for (let k of keys) { 6 | if ((row[k] || '').toString().toLocaleLowerCase().indexOf(text) > -1) { 7 | return true; 8 | } 9 | } 10 | return false; 11 | } 12 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/functions.ts: -------------------------------------------------------------------------------- 1 | export function getPaginationBoundaries(page: number, pageSize: number) { 2 | const start = page * pageSize; 3 | return { 4 | start, 5 | end: start + pageSize 6 | }; 7 | } 8 | 9 | export function getPaginationRowIndex(index: number, page: number, pageSize: number) { 10 | return page * pageSize + index; 11 | } 12 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | // and what to do when importing types 6 | declare namespace App { 7 | // interface Locals {} 8 | // interface PageData {} 9 | // interface Error {} 10 | // interface Platform {} 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from '$lib/Pagination.svelte'; 2 | export * from '$lib/Row.svelte'; 3 | export * from '$lib/Search.svelte'; 4 | export * from '$lib/Sort.svelte'; 5 | export * from '$lib/Table.svelte'; 6 | export * from '$lib/constants'; 7 | export * from '$lib/filter'; 8 | export * from '$lib/functions'; 9 | export * from '$lib/interfaces'; 10 | export * from '$lib/sort'; 11 | export * from '$lib/tableStore'; 12 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /src/routes/examples/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

Client side processing

9 | 10 |
11 |
12 |

Server side processing

13 | 14 |
15 | 16 | 23 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_PAGINATION_LABELS = { 2 | first: 'First', 3 | last: 'Last', 4 | next: 'Next', 5 | previous: 'Previous' 6 | }; 7 | export const DEFAULT_SEARCH_LABELS = { 8 | placeholder: 'Search' 9 | }; 10 | export const DEFAULT_SORT_LABELS = { 11 | asc: { title: 'Ascending', html: '↑' }, 12 | desc: { title: 'Desceding', html: '↓' }, 13 | unsorted: { title: 'Unsorted', html: '⇅' } 14 | }; 15 | export const DEFAULT_TABLE_LABELS = { 16 | empty: 'No records available', 17 | loading: 'Loading data' 18 | }; 19 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/Row.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte-table 2 | 3 | Make your tables using HTML and leave the javascript only for user interaction (pagination/search/sort); 4 | 5 | `npm install @fabiohvp/svelte-table` 6 | or 7 | `yarn add @fabiohvp/svelte-table` 8 | 9 | ## Examples 10 | 11 | You can see the examples in [Sveltelab.dev](https://www.sveltelab.dev/vs33nnuign7coeb) 12 | 13 | PS:. examples are using tableStore to manage table data but it can be managed with local variables 14 | 15 | ## Advanced 16 | 17 | - You can add components to the top or bottom slots. 18 | - Adding top/bottom slot will override the default content (Search/Pagination) but you can still use it by importing and adding them inside your custom slot. 19 | - You can change the default search method by adding an on:search event on Search component. 20 | - Row component is optional and only serves to render odd/even row, you can use instead. 21 | - Sort component expect "key" and you need to implement your own sorting method (see the example below). 22 | 23 | ## Roadmap 24 | 25 | - Add tests -------------------------------------------------------------------------------- /src/lib/Sort.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | 32 | 41 | -------------------------------------------------------------------------------- /src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface PaginationEventArgs { 2 | originalEvent: MouseEvent; 3 | page: number; 4 | pageSize: number; 5 | } 6 | 7 | export interface PaginationLabels { 8 | first: string; 9 | last: string; 10 | next: string; 11 | previous: string; 12 | } 13 | 14 | export interface RowEventArgs { 15 | originalEvent: MouseEvent; 16 | index: number; 17 | } 18 | 19 | export interface SearchEventArgs { 20 | originalEvent: KeyboardEvent; 21 | index: number; 22 | text: string; 23 | type: string; 24 | } 25 | 26 | export interface SearchLabels { 27 | placeholder: string; 28 | } 29 | 30 | export type SortDirection = 'asc' | 'desc'; 31 | 32 | export interface SortEventArgs { 33 | originalEvent: MouseEvent; 34 | key: string; 35 | dir: SortDirection; 36 | type: string; 37 | } 38 | 39 | export interface SortLabels { 40 | asc: { title: string; html: string }; 41 | desc: { title: string; html: string }; 42 | unsorted: { title: string; html: string }; 43 | } 44 | 45 | export interface SortParams { 46 | key: string; 47 | dir: SortDirection; 48 | } 49 | 50 | export interface TableLabels { 51 | empty: string; 52 | loading: string; 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/Search.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 33 | 34 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fabiohvp/svelte-table", 3 | "description": "Svelte-table - a simple way to render your tables", 4 | "version": "0.2.5", 5 | "keywords": [ 6 | "svelte", 7 | "table" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/fabiohvp/svelte-table.git" 12 | }, 13 | "author": "https://github.com/fabiohvp", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/fabiohvp/svelte-table/issues" 17 | }, 18 | "homepage": "https://github.com/fabiohvp/svelte-table#readme", 19 | "type": "module", 20 | "scripts": { 21 | "dev": "vite dev", 22 | "build": "svelte-kit sync && svelte-package", 23 | "prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1", 24 | "test": "playwright test", 25 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 26 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 27 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 28 | "format": "prettier --plugin-search-dir . --write ." 29 | }, 30 | "devDependencies": { 31 | "@playwright/test": "1.25.0", 32 | "@sveltejs/adapter-auto": "next", 33 | "@sveltejs/kit": "next", 34 | "@sveltejs/package": "next", 35 | "@typescript-eslint/eslint-plugin": "^5.27.0", 36 | "@typescript-eslint/parser": "^5.27.0", 37 | "eslint": "^8.16.0", 38 | "eslint-config-prettier": "^8.3.0", 39 | "eslint-plugin-svelte3": "^4.0.0", 40 | "prettier": "^2.6.2", 41 | "prettier-plugin-svelte": "^2.7.0", 42 | "svelte": "^3.44.0", 43 | "svelte-check": "^2.7.1", 44 | "svelte-preprocess": "^4.10.6", 45 | "tslib": "^2.3.1", 46 | "typescript": "^4.7.4", 47 | "vite": "^3.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/routes/examples/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font: inherit; 92 | vertical-align: baseline; 93 | } 94 | 95 | /* HTML5 display-role reset for older browsers */ 96 | article, 97 | aside, 98 | details, 99 | figcaption, 100 | figure, 101 | footer, 102 | header, 103 | hgroup, 104 | menu, 105 | nav, 106 | section { 107 | display: block; 108 | } 109 | 110 | body { 111 | line-height: 1; 112 | } 113 | 114 | ol, 115 | ul { 116 | list-style: none; 117 | } 118 | 119 | blockquote, 120 | q { 121 | quotes: none; 122 | } 123 | 124 | blockquote:before, 125 | blockquote:after, 126 | q:before, 127 | q:after { 128 | content: ''; 129 | content: none; 130 | } 131 | 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } 136 | -------------------------------------------------------------------------------- /src/routes/examples/server.ts: -------------------------------------------------------------------------------- 1 | import { filter } from '$lib/filter'; 2 | import { sortDateByKey, sortNumberByKey, type SortDirection } from '$lib/sort'; 3 | import type { IRow } from './IRow'; 4 | 5 | const originalData: IRow[] = [ 6 | { name: 'a', lastName: 'o', age: 1 }, 7 | { name: 'b', lastName: 'n', age: 2 }, 8 | { name: 'c', lastName: 'm', age: 23 }, 9 | { name: 'd', lastName: 'l', age: 11 }, 10 | { name: 'e', lastName: 'k', age: 132 }, 11 | { name: 'f', lastName: 'j', age: 152 }, 12 | { name: 'g', lastName: 'i', age: 4 }, 13 | { name: 'h', lastName: 'h', age: 432 }, 14 | { name: 'i', lastName: 'g', age: 41 }, 15 | { name: 'k', lastName: 'f', age: 432 }, 16 | { name: 'l', lastName: 'e', age: 552 } 17 | ]; 18 | 19 | export function getAll(): Promise { 20 | return new Promise((resolve, reject) => { 21 | setTimeout(function () { 22 | resolve(originalData); 23 | }, 250); 24 | }); 25 | } 26 | 27 | export function getData( 28 | page: number, 29 | pageSize: number, 30 | text: string, 31 | sorting: { dir: SortDirection; key: string } 32 | ): Promise<{ rows: IRow[]; rowsCount: number }> { 33 | let data: IRow[] = [...originalData]; 34 | 35 | if (sorting) { 36 | if (sorting.key === 'age') { 37 | data = sortNumberByKey(data, sorting.key, sorting.dir); 38 | } else { 39 | data = sortDateByKey(data, sorting.key, sorting.dir); 40 | } 41 | } 42 | 43 | return new Promise((resolve, _reject) => { 44 | setTimeout(function () { 45 | let rows = []; 46 | let rowsCount = data.length; 47 | 48 | if (text && text.length > 0) { 49 | rows = data.filter((row) => filter(row, text)); 50 | rowsCount = rows.length; //need to count before slice 51 | } else { 52 | rows = data; 53 | } 54 | 55 | const pageIndex = page * pageSize; 56 | resolve({ 57 | rows: rows.slice(pageIndex, pageIndex + pageSize), 58 | rowsCount 59 | }); 60 | }, 250); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/lib/sort.ts: -------------------------------------------------------------------------------- 1 | import type { SortDirection, SortEventArgs } from './interfaces'; 2 | 3 | type SortNumberMethod = (a: number, b: number) => number; 4 | type SortStringMethod = (a: string, b: string) => number; 5 | 6 | const sortNumberDict: { asc: SortNumberMethod; desc: SortNumberMethod } = { 7 | asc: (a: number, b: number) => (a > b ? 1 : -1), 8 | desc: (a: number, b: number) => (a > b ? -1 : 1) 9 | }; 10 | 11 | const sortStringDict: { asc: SortStringMethod; desc: SortStringMethod } = { 12 | asc: (a: string, b: string) => a?.localeCompare(b), 13 | desc: (a: string, b: string) => b?.localeCompare(a) 14 | }; 15 | 16 | export type SortFunction = (rows: T[], key: string, dir: SortDirection) => T[]; 17 | 18 | export function getSortAttributes(key: string, currentSorting: SortEventArgs) { 19 | return { 20 | key, 21 | dir: (currentSorting?.key === key ? currentSorting.dir : 'none') as SortDirection 22 | }; 23 | } 24 | 25 | export function sortDateBy(rows: T[], getValue: (row: T) => Date, dir: SortDirection) { 26 | const sortMethod = sortNumberDict[dir ?? 'asc']; 27 | return rows.sort((a, b) => sortMethod(getValue(a).getTime(), getValue(b).getTime())); 28 | } 29 | 30 | export function sortNumberBy(rows: T[], getValue: (row: T) => number, dir: SortDirection) { 31 | const sortMethod = sortNumberDict[dir ?? 'asc']; 32 | return rows.sort((a, b) => sortMethod(getValue(a), getValue(b))); 33 | } 34 | 35 | export function sortStringBy(rows: T[], getValue: (row: T) => string, dir: SortDirection) { 36 | const sortMethod = sortStringDict[dir ?? 'asc']; 37 | return rows.sort((a, b) => sortMethod(getValue(a), getValue(b))); 38 | } 39 | 40 | export function sortDateByKey(rows: any[], key: string, dir: SortDirection) { 41 | const sortMethod = sortNumberDict[dir ?? 'asc']; 42 | return rows.sort((a, b) => sortMethod(a[key], b[key])); 43 | } 44 | 45 | export function sortNumberByKey(rows: any[], key: string, dir: SortDirection) { 46 | const sortMethod = sortNumberDict[dir ?? 'asc']; 47 | return rows.sort((a, b) => sortMethod(a[key], b[key])); 48 | } 49 | 50 | export function sortStringByKey(rows: any[], key: string, dir: SortDirection) { 51 | const sortMethod = sortStringDict[dir ?? 'asc']; 52 | return rows.sort((a, b) => sortMethod(a[key], b[key])); 53 | } 54 | -------------------------------------------------------------------------------- /src/routes/examples/LocalTable.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | 52 | 53 | 57 | 61 | 65 | 66 | 67 | 68 | {#each visibleRows as row, index (row)} 69 | onCellClick(row)}> 70 | 71 | 72 | 73 | 74 | {/each} 75 | 76 |
54 | Name 55 | 56 | 58 | Lastname 59 | 60 | 62 | Age 63 | 64 |
{row.name}{row.lastName}{row.age}
77 | -------------------------------------------------------------------------------- /src/lib/Pagination.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 | Showing {pageIndex} to {pageItemsCount > rowsCount ? rowsCount : pageItemsCount} of {rowsCount} 31 | entries 33 | 34 |
    35 |
  • 36 | 39 |
  • 40 |
  • 41 | 44 |
  • 45 | {#each buttons as button} 46 | {@const buttonNumber = page + button} 47 | {#if buttonNumber >= 0 && buttonNumber <= pageCount} 48 |
  • 49 | 52 |
  • 53 | {/if} 54 | {/each} 55 |
  • 56 | 59 |
  • 60 |
61 |
62 | 63 | 95 | -------------------------------------------------------------------------------- /src/routes/examples/RemoteTable.svelte: -------------------------------------------------------------------------------- 1 | 50 | 51 | 52 |
53 | 54 |
55 | 56 | 57 | 61 | 65 | 69 | 70 | 71 | 72 | {#each visibleRows as row, index (row)} 73 | onCellClick(row)}> 74 | 75 | 76 | 77 | 78 | {/each} 79 | 80 |
81 | 82 |
83 |
58 | Name 59 | 60 | 62 | Lastname 63 | 64 | 66 | Age 67 | 68 |
{row.name}{row.lastName}{row.age}
84 | -------------------------------------------------------------------------------- /src/lib/tableStore.ts: -------------------------------------------------------------------------------- 1 | import { filter } from '$lib/filter'; 2 | import { getPaginationBoundaries, getPaginationRowIndex } from '$lib/functions'; 3 | import type { SortDirection } from '$lib/interfaces'; 4 | import { sortNumberByKey, sortStringByKey, type SortFunction } from '$lib/sort'; 5 | import { writable, type Readable } from 'svelte/store'; 6 | 7 | export type TableData = { 8 | loading: boolean; 9 | local: boolean; 10 | page: number; 11 | pageSize: number; 12 | rows: T[]; 13 | rowsCount: number; 14 | }; 15 | 16 | export type TableStore = Readable> & ReturnType>; 17 | 18 | export function createTableStore(initialState: Partial> = {}) { 19 | const { update, subscribe } = writable({ 20 | loading: false, 21 | local: true, 22 | page: 0, 23 | pageSize: 10, 24 | rows: [], 25 | rowsCount: 0, 26 | ...initialState 27 | }); 28 | 29 | return { 30 | subscribe, 31 | update, 32 | 33 | getPaginationBoundaries: () => { 34 | let page = 0; 35 | let pageSize = 0; 36 | subscribe((s) => { 37 | (page = s.page), (pageSize = s.pageSize); 38 | })(); 39 | return getPaginationBoundaries(page, pageSize); 40 | }, 41 | 42 | getPaginationRowIndex: (index: number) => { 43 | let page = 0; 44 | let pageSize = 0; 45 | subscribe((s) => { 46 | (page = s.page), (pageSize = s.pageSize); 47 | })(); 48 | return getPaginationRowIndex(index, page, pageSize); 49 | }, 50 | 51 | paginate: (page: number, pageSize?: number) => { 52 | update((s) => ({ 53 | ...s, 54 | page, 55 | pageSize: pageSize ?? s.pageSize 56 | })); 57 | }, 58 | 59 | search: (text: string) => { 60 | update((s) => ({ 61 | ...s, 62 | page: 0, 63 | rows: s.rows.filter((row) => filter(row, text)) 64 | })); 65 | }, 66 | 67 | setLoading: (loading: boolean) => { 68 | update((s) => ({ 69 | ...s, 70 | loading 71 | })); 72 | }, 73 | 74 | setRows: (rows: T[], rowsCount: number | undefined = undefined, loading = false, page = 0) => { 75 | update((s) => ({ 76 | ...s, 77 | loading, 78 | page, 79 | rows, 80 | rowsCount: rowsCount ?? rows.length 81 | })); 82 | }, 83 | 84 | sort: ( 85 | key: string, 86 | dir: SortDirection, 87 | typeOrFn: 'string' | 'number' | string | SortFunction = 'string' 88 | ) => { 89 | if (typeof typeOrFn === 'function') { 90 | update((s) => ({ 91 | ...s, 92 | rows: typeOrFn(s.rows, key, dir) 93 | })); 94 | } else if (typeOrFn === 'string') { 95 | update((s) => ({ 96 | ...s, 97 | rows: sortStringByKey(s.rows, key, dir) 98 | })); 99 | } else if (typeOrFn === 'number') { 100 | update((s) => ({ 101 | ...s, 102 | rows: sortNumberByKey(s.rows, key, dir) 103 | })); 104 | } else { 105 | throw Error('Sorting type or function is undefined'); 106 | } 107 | }, 108 | 109 | updateRow: (data: T, index: number) => { 110 | update((s) => { 111 | s.rows[index] = data; 112 | return s; 113 | }); 114 | } 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /src/lib/Table.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 |
43 | 44 |
45 |
46 | 47 | 48 | 49 | {#if loading} 50 | 51 | 52 | 57 | 58 | 59 | {:else if visibleRows.length === 0} 60 | 61 | 62 | 67 | 68 | 69 | {:else} 70 | 71 | {/if} 72 | 73 |
53 | 54 | {@html labels.loading} 55 | 56 |
63 | 64 | {@html labels.empty} 65 | 66 |
74 | 75 | 76 |
77 | 84 |
85 |
86 | 87 | 172 | --------------------------------------------------------------------------------