├── .changeset ├── README.md └── config.json ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── documentation_change.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE.txt ├── README.md ├── apps └── website │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ ├── app.d.ts │ ├── app.html │ ├── app.pcss │ ├── lib │ │ ├── index.ts │ │ └── sitecomponents │ │ │ ├── GitHubIcon.svelte │ │ │ ├── Header.svelte │ │ │ ├── Message.svelte │ │ │ ├── Progressbar.svelte │ │ │ └── Sidebar.svelte │ ├── routes │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ └── +page.svelte │ └── siteconfig.ts │ ├── static │ ├── favicon.ico │ ├── logo.webp │ └── og-image.webp │ ├── svelte.config.js │ ├── tailwind.config.cjs │ ├── tsconfig.json │ └── vite.config.ts ├── assets └── demo-screenshot.webp ├── package.json ├── packages ├── eslint-config │ ├── index.js │ └── package.json └── svelte-datagrid │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── LICENSE.txt │ ├── README.md │ ├── package.json │ ├── src │ └── lib │ │ ├── DataGridProps.ts │ │ ├── actions │ │ ├── dragAndDrop.ts │ │ └── resizeColumn.ts │ │ ├── components │ │ ├── CheckboxCell.svelte │ │ ├── Datagrid.svelte │ │ ├── Datagrid.test.ts │ │ ├── SelectCell.svelte │ │ └── TextboxCell.svelte │ │ ├── configurations.ts │ │ ├── functions │ │ ├── calculateFunctions.test.ts │ │ ├── calculateFunctions.ts │ │ ├── gridHelpers.test.ts │ │ └── gridHelpers.ts │ │ ├── index.ts │ │ └── types.ts │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['custom'] 4 | }; 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "🐛 Bug report" 2 | description: Report an issue with @gzim/svelte-datagrid 3 | labels: ["type: bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us how in the description. Thanks! 14 | placeholder: Bug description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduction 19 | attributes: 20 | label: Reproduction 21 | description: Please provide a link to a repo or Stackblitz that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: logs 27 | attributes: 28 | label: Logs 29 | description: "Please include browser console and server logs around the time this bug occurred. Optional if provided reproduction. Please try not to insert an image but copy paste the log text." 30 | render: bash 31 | - type: textarea 32 | id: system-info 33 | attributes: 34 | label: System Info 35 | description: Output of `npx envinfo --system --npmPackages svelte,@gzim/svelte-datagrid,@sveltejs/kit,typescript --binaries --browsers` 36 | render: bash 37 | placeholder: System, Binaries, Browsers 38 | validations: 39 | required: true 40 | - type: dropdown 41 | id: severity 42 | attributes: 43 | label: Severity 44 | description: Select the severity of this issue 45 | options: 46 | - annoyance 47 | - blocking an upgrade 48 | - blocking all usage of @gzim/svelte-datagrid 49 | validations: 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_change.yml: -------------------------------------------------------------------------------- 1 | name: 📖 Report Docs Issue 2 | description: Suggest an addition or modification to the documentation 3 | labels: ["type: documentation"] 4 | body: 5 | - type: dropdown 6 | attributes: 7 | label: Change Type 8 | description: What type of change are you proposing? 9 | options: 10 | - Addition 11 | - Correction 12 | - Removal 13 | - Cleanup (formatting, typos, etc.) 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Proposed Changes 20 | description: Describe the proposed changes and why they are necessary 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # Borrowed from https://github.com/skeletonlabs/skeleton 2 | 3 | name: 🛠️ Request New Feature 4 | description: Let us know what you would like to see added. 5 | labels: ["type: feature"] 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the feature in detail (code, mocks, or screenshots encouraged) 11 | validations: 12 | required: true 13 | - type: dropdown 14 | id: category 15 | attributes: 16 | label: What type of pull request would this be? 17 | options: 18 | - "New Feature" 19 | - "Enhancement" 20 | - "Guide" 21 | - "Docs" 22 | - "Other" 23 | - type: textarea 24 | id: references 25 | attributes: 26 | label: Provide relevant links or additional information. 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 🎯 Changes 2 | 3 | 4 | 5 | ## ✅ Checklist 6 | 7 | - [ ] I have given my PR a descriptive title 8 | - [ ] I have run `pnpm run lint` locally on my changes 9 | - [ ] I have run `pnpm run test` locally on my changes 10 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | 9 | concurrency: ${{ github.workflow }}-${{ github.ref }} 10 | 11 | jobs: 12 | release: 13 | name: Changes and Release 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | outputs: 19 | published: ${{ steps.printoutputs.outputs.published }} 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - uses: pnpm/action-setup@v4 26 | with: 27 | version: 8.6.3 28 | - name: Setup Node.js 29 | uses: actions/setup-node@v3 30 | with: 31 | node-version: "20.x" 32 | cache: "pnpm" 33 | 34 | - run: pnpm install --frozen-lockfile 35 | 36 | - name: Package 37 | run: pnpm package 38 | 39 | - name: Create Release Pull Request or Publish to npm 40 | id: changesets 41 | uses: changesets/action@v1 42 | with: 43 | publish: pnpm release 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | 48 | - name: Print Changesets Outputs 49 | run: echo "${{ toJson(steps.changesets.outputs) }}" 50 | 51 | - id: printoutputs 52 | ##if has changeset build and deploy 53 | run: echo "published=${{ steps.changesets.outputs.hasChangesets }}" >> "$GITHUB_OUTPUT" 54 | 55 | - name: Build 56 | if: ${{ steps.changesets.outputs.hasChangesets == 'true' }} 57 | env: 58 | BASE_PATH: "/${{ github.event.repository.name }}" 59 | run: pnpm build 60 | 61 | - name: Upload Artifacts 62 | if: ${{ steps.changesets.outputs.hasChangesets == 'true' }} 63 | uses: actions/upload-pages-artifact@v2 64 | with: 65 | path: "apps/website/build/" 66 | 67 | deploy: 68 | if: ${{ needs.release.outputs.published == 'true' }} 69 | name: Website Deployment 70 | needs: release 71 | runs-on: ubuntu-latest 72 | 73 | permissions: 74 | pages: write 75 | id-token: write 76 | 77 | environment: 78 | name: github-pages 79 | url: ${{ steps.deployment.outputs.page_url }} 80 | 81 | steps: 82 | - name: Deploy 83 | id: deployment 84 | uses: actions/deploy-pages@v2 85 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.number || github.sha }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | Quality: 13 | runs-on: ubuntu-latest 14 | name: Quality Check 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install Node.JS 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 20 24 | 25 | - uses: pnpm/action-setup@v4 26 | name: Install pnpm 27 | id: pnpm-install 28 | with: 29 | version: 8 30 | 31 | # PNPM Store cache setup 32 | - name: Get pnpm store directory 33 | id: pnpm-cache 34 | run: | 35 | echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT 36 | 37 | - name: Setup pnpm cache 38 | uses: actions/cache@v3 39 | with: 40 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 41 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-pnpm-store- 44 | 45 | - name: Install dependencies 46 | run: pnpm install 47 | 48 | - name: Linter 49 | run: pnpm lint 50 | 51 | - name: Tests 52 | run: pnpm test 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # svelte 12 | .svelte-kit 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # local env files 24 | .env.local 25 | .env.development.local 26 | .env.test.local 27 | .env.production.local 28 | 29 | # turbo 30 | .turbo 31 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .svelte-kit 3 | node_modules 4 | /build 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js* 10 | 11 | # Ignore files for PNPM, NPM and YARN 12 | pnpm-lock.yaml 13 | pnpm-workspace.yaml 14 | package-lock.json 15 | yarn.lock 16 | .changeset 17 | .turbo -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": ["DataGrid", "README", "Website"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Gustavo Zimbrón (https://github.com/gzimbron) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/@gzim/svelte-datagrid.svg?style=flat-square)](https://www.npmjs.com/package/@gzim/svelte-datagrid) 2 | 3 | # Svelte DataGrid 4 | 5 | Svelte DataGrid is a high-performance, feature-rich grid component for Svelte. It is designed to handle large datasets and provide a smooth scrolling experience. It is also designed to be accessible and customizable. 6 | 7 | It's based on the excellent (but deprecated) [svelte-data-grid](https://github.com/bsssshhhhhhh/svelte-data-grid). 8 | 9 | ## 👀 Demo website 10 | 11 | [![Demo website](./assets/demo-screenshot.webp)](https://gzimbron.github.io/svelte-datagrid) 12 | 13 | ## 🚀 Features 14 | 15 | - High scrolling performance 16 | - ARIA attributes set on elements 17 | - Lightweight even when displaying a huge dataset due to implementation of a "virtual list" mechanism 18 | - Column headers remain fixed at the top of the grid 19 | - Custom components can be specified to control how individual table cells or column headers are displayed 20 | 21 | ## 📋 TODO 22 | 23 | - [x] Demo website 24 | - [x] Re-ordering columns 25 | - [x] Resizing columns 26 | - ⭐️ Feel free to suggest more features or contribute to the project 27 | 28 | ## ℹ️ Usage: 29 | 30 | Install: 31 | 32 | ```bash 33 | npm install @gzim/svelte-datagrid 34 | ``` 35 | 36 | Import: 37 | 38 | ```javascript 39 | import { Datagrid } from '@gzim/svelte-datagrid'; 40 | 41 | ; 48 | ``` 49 | 50 | Datagrid requires 2 properties to be passed in order to display data: `rows` and `columns`. 51 | 52 | `columns` is an array of objects containing at least 3 properties: `label`, `dataKey`, and `width`. A svelte component can be specified in `headerComponent` and `cellComponent` if any custom cell behavior is required. 53 | 54 | ```typescript 55 | [ 56 | { 57 | label: 'Name', // What will be displayed as the column header 58 | dataKey: 'firstName', // The key of a row to get the column's data from 59 | width: 400 // Width, in pixels, of column 60 | }, 61 | { 62 | label: 'Age', 63 | dataName: 'age', 64 | width: 150 65 | } 66 | ]; 67 | ``` 68 | 69 | `rows` is an array of objects containing the data for each table row. 70 | 71 | ```javascript 72 | [ 73 | { 74 | firstName: 'Gustavo', 75 | age: 34 76 | }, 77 | { 78 | firstName: 'Paulina', 79 | age: 31 80 | }, 81 | { 82 | firstName: 'Daphne', 83 | age: 2 84 | } 85 | ]; 86 | ``` 87 | 88 | ## 📝 Editing Data 89 | 90 | You can use this 3 componets as cellComponent to edit data: 91 | 92 | Import the components: 93 | 94 | ```typescript 95 | import { TextboxCell, SelectCell, CheckboxCell } from '@gzim/svelte-datagrid'; 96 | ``` 97 | 98 | ### Textbox Cell 99 | 100 | Textbox cell will debounce the user input. 101 | 102 | ```typescript 103 | { 104 | label: 'Name', 105 | dataKey: 'name', 106 | width: 250, 107 | cellComponent: TextboxCell 108 | } 109 | ``` 110 | 111 | ### Select Cell 112 | 113 | SelectCell requires that you provide an `options` array in your cell definition: 114 | 115 | ```typescript 116 | { 117 | label: 'Simpsons Character', 118 | dataKey: 'simpsonChar', 119 | width: 200, 120 | cellComponent: SelectCell, 121 | options: [ 122 | { 123 | display: 'Homer', 124 | value: 'homer' 125 | }, 126 | { 127 | display: 'Bart', 128 | value: 'bart' 129 | }, 130 | { 131 | display: 'Lisa', 132 | value: 'lisa' 133 | }, 134 | { 135 | display: 'Marge', 136 | value: 'marge' 137 | }, 138 | { 139 | display: 'Maggie', 140 | value: 'maggie' 141 | } 142 | ] 143 | } 144 | ``` 145 | 146 | ### Checkbox Cell 147 | 148 | CheckboxCell will set the checked state of the checkbox depending on the boolean value of the row's data. 149 | 150 | ```typescript 151 | { 152 | display: 'Pending', 153 | dataName: 'pending', 154 | width: 75, 155 | cellComponent: CheckboxCell 156 | } 157 | ``` 158 | 159 | ## ✨ Custom Cell Components 160 | 161 | To create a custom cell component, create a new Svelte component following the example below. 162 | 163 | Components will be passed the following properties: 164 | 165 | - `rowNumber` - The index of the row within `rows` 166 | - `row` - The entire row object from `rows` 167 | - `column` - The entire column object from `columns` 168 | 169 | MyCustomCell.svelte 170 | 171 | ```html 172 | 194 | 195 |
ADD HERE YOUR CUSTOM CELL CONTENT
196 | 197 | 202 | ``` 203 | 204 | Import the component 205 | 206 | ```typescript 207 | import MyCustomCell from './MyCustomCell.svelte'; 208 | ``` 209 | 210 | `columns` option: 211 | 212 | ```typescript 213 | [ 214 | { 215 | label: 'Icon' 216 | dataKey: 'icon', 217 | width: 300, 218 | cellComponent: MyCustomCell 219 | } 220 | ] 221 | ``` 222 | 223 | ## ✨ Custom Header Components 224 | 225 | Header components can also be specified in `columns` entries as the `headerComponent` property. Header components are only passed `column`, the column object from `columns`. 226 | 227 | ```html 228 | 233 | 234 |
~{ column.label }~
235 | 236 | 241 | ``` 242 | 243 | ## 🛠️ Options and Functions: 244 | 245 | Datagrid provides a few options for controlling the grid and its interactions: 246 | 247 | ### ⚙️ Properties 248 | 249 | - `rowHeight` - The row height in pixels _(Default: 24)_ 250 | - `headerRowHeight` - The row height in pixels _(Default: 24)_ 251 | - `rowsPerPage` - The number of rows to render per page _(Default: rows lenght up to 10)_ 252 | - `extraRows` - Add extra rows to the virtual list to improve scrolling performance _(Default: 0)_ 253 | - `allColumnsDraggable` - Set all columns draggable by default, ignoring the `draggable` property of each column _(Default: false)_ 254 | 255 | ### 💫 Functions exported 256 | 257 | Yoy can bind to the following functions to control the grid: 258 | 259 | - `getGridState` - A function that returns the current grid state. 260 | 261 | ```typescript 262 | const getGridState: () => { 263 | visibleRowsIndexes: { 264 | start: number; 265 | end: number; 266 | }; 267 | scrollTop: number; 268 | scrollLeft: number; 269 | yScrollPercent: number; 270 | xScrollPercent: number; 271 | }; 272 | ``` 273 | 274 | - `scrollToRow` - A function that scrolls the grid to a specific row index. 275 | 276 | ```typescript 277 | const scrollToRow: (rowIndex: number, behavior: ScrollBehavior = 'smooth') => void; 278 | ``` 279 | 280 | ### 💄 Styling 281 | 282 | - `--border` Css: Custom style for grid borders _(Default: 1px)_ 283 | - `--header-border` Custom width for header row border bottom _(Default: 2px)_ 284 | - `--header-border-color` Custom color for header row border bottom _(Default: black)_ 285 | - `--head-bg` Custom background color for header row _(Default: white)_ 286 | - `--cell-bg` Custom background color for body cells _(Default: white)_ 287 | - `--textbox-cell-bg` ustom background color for textbox cells _(Default: white)_ 288 | - `--select-cell-bg` Custom background color for select cells _(Default: white)_ 289 | - `--head-color` Custom color for header row text. 290 | - `--cell-color` Custom color for body cells text 291 | - `--textbox-cell-color` Custom color for textbox cells text 292 | - `--select-cell-color` Custom color for select cells text 293 | - `--no-draggable-opacity` Opacity for NOT draggable columns content when dragging. _(Default: 0.4)_ 294 | - `--no-draggable-fg` CSS color for NOT draggable columns when dragging, this color is used to create an overlay over the column _(Default: rgba(66, 66, 66, 0.5))_ 295 | - `--draggable-bg` CSS Hover color for draggable columns. _(Default: rgba(33, 248, 255, 0.5))_ 296 | - `--dragging-bg` CSS Background color for actual dragging column. _(Default: rgba(33, 255, 151, 0.5))_ 297 | - `--grid-height` Min height for the grid container _(@default RowHeight \* 6)_ 298 | - `--border-resizing` Min height for the grid container _(@default 2px solid #666)_ 299 | 300 | ## Events: 301 | 302 | - `scroll` - Triggered when the grid is scrolled on Y axis. The Y scroll percent position can be accessed from `event.detail` 303 | - `xScroll` - Triggered when the grid is scrolled on X axis. The X scroll percent position can be accessed from `event.detail` 304 | - `valueUpdated` - Triggered when a cell's value is updated. The updated value can be accessed from `event.value`, other data can be accessed from `event.row`, `event.column` and `event.rowIndex` 305 | - `columnsSwapped` - Triggered when columns are swapped. `event.detail` contains `from`, `to` and new `columns` order properties 306 | - `rowClick` - Triggered when a row is clicked. The clicked row can be accessed from `event.detail` 307 | - `rowDblClick` - Triggered when a row is double clicked. The clicked row can be accessed from `event.detail` 308 | 309 | ## Bugs? Suggestions? 310 | 311 | Please file an issue if you find a bug or have a suggestion for a new feature. 312 | -------------------------------------------------------------------------------- /apps/website/.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 | -------------------------------------------------------------------------------- /apps/website/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | extends: ['@mycustom/eslint-config/index.js'] 4 | }; 5 | -------------------------------------------------------------------------------- /apps/website/.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 | -------------------------------------------------------------------------------- /apps/website/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /apps/website/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | node_modules -------------------------------------------------------------------------------- /apps/website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/website/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # website 2 | 3 | ## 0.0.16 4 | 5 | ### Patch Changes 6 | 7 | - b4706c4: Added rowClick and rowDblClick events suggested in issue #36 8 | - Updated dependencies [b4706c4] 9 | - @gzim/svelte-datagrid@0.4.0 10 | 11 | ## 0.0.15 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [eac353a] 16 | - @gzim/svelte-datagrid@0.3.1 17 | 18 | ## 0.0.14 19 | 20 | ### Patch Changes 21 | 22 | - fb7fccd: Add resizable columns example 23 | - Updated dependencies [fb7fccd] 24 | - @gzim/svelte-datagrid@0.3.0 25 | 26 | ## 0.0.13 27 | 28 | ### Patch Changes 29 | 30 | - Updated dependencies [cb53ad7] 31 | - @gzim/svelte-datagrid@0.2.4 32 | 33 | ## 0.0.12 34 | 35 | ### Patch Changes 36 | 37 | - 994fb83: Add controls to show state, and scroll to specific row index 38 | - Updated dependencies [994fb83] 39 | - Updated dependencies [994fb83] 40 | - @gzim/svelte-datagrid@0.2.3 41 | 42 | ## 0.0.11 43 | 44 | ### Patch Changes 45 | 46 | - Updated dependencies [bfc05c9] 47 | - @gzim/svelte-datagrid@0.2.2 48 | 49 | ## 0.0.10 50 | 51 | ### Patch Changes 52 | 53 | - 44772c4: Added property --min-height 54 | - Updated dependencies [44772c4] 55 | - @gzim/svelte-datagrid@0.2.1 56 | 57 | ## 0.0.9 58 | 59 | ### Patch Changes 60 | 61 | - db0eed1: Replace Row Height control from input text to input range 62 | 63 | ## 0.0.8 64 | 65 | ### Patch Changes 66 | 67 | - Updated dependencies [a17d5be] 68 | - Updated dependencies [a17d5be] 69 | - Updated dependencies [a17d5be] 70 | - @gzim/svelte-datagrid@0.2.0 71 | 72 | ## 0.0.7 73 | 74 | ### Patch Changes 75 | 76 | - Updated dependencies [7bb870e] 77 | - @gzim/svelte-datagrid@0.1.1 78 | 79 | ## 0.0.6 80 | 81 | ### Patch Changes 82 | 83 | - f0925cf: Example data table now shows fake persons data 84 | - Updated dependencies [f0925cf] 85 | - @gzim/svelte-datagrid@0.1.0 86 | 87 | ## 0.0.5 88 | 89 | ### Patch Changes 90 | 91 | - Updated dependencies [8536bb8] 92 | - @gzim/svelte-datagrid@0.0.4 93 | 94 | ## 0.0.4 95 | 96 | ### Patch Changes 97 | 98 | - cc21a5d: Test deploy CD 99 | 100 | ## 0.0.3 101 | 102 | ### Patch Changes 103 | 104 | - 33b8d9c: Publish, fix #6 105 | 106 | ## 0.0.2 107 | 108 | ### Patch Changes 109 | 110 | - Updated dependencies [113433a] 111 | - @gzim/svelte-datagrid@0.0.3 112 | 113 | ## 0.0.1 114 | 115 | ### Patch Changes 116 | 117 | - c3fa3b0: v1 from turbo repo separated workspace 118 | -------------------------------------------------------------------------------- /apps/website/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /apps/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.16", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --port 3590 --host 0.0.0.0", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "prepare": "svelte-kit sync", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --check . && eslint .", 13 | "format": "prettier --write ." 14 | }, 15 | "devDependencies": { 16 | "@faker-js/faker": "^8.4.1", 17 | "@mycustom/eslint-config": "workspace:*", 18 | "@sveltejs/adapter-static": "3.0.1", 19 | "@sveltejs/kit": "^2.0.0", 20 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 21 | "@types/eslint": "8.56.0", 22 | "@typescript-eslint/eslint-plugin": "^6.0.0", 23 | "@typescript-eslint/parser": "^6.0.0", 24 | "autoprefixer": "^10.4.16", 25 | "eslint": "^8.56.0", 26 | "eslint-config-prettier": "^9.1.0", 27 | "eslint-plugin-svelte": "^2.35.1", 28 | "postcss": "^8.4.32", 29 | "postcss-load-config": "^5.0.2", 30 | "prettier": "^3.1.1", 31 | "prettier-plugin-svelte": "^3.1.2", 32 | "prettier-plugin-tailwindcss": "^0.5.9", 33 | "svelte": "^4.2.7", 34 | "svelte-check": "^3.6.0", 35 | "sweetalert2": "^11.10.5", 36 | "tailwindcss": "^3.3.6", 37 | "tslib": "^2.4.1", 38 | "typescript": "^5.0.0", 39 | "vite": "^5.0.3" 40 | }, 41 | "dependencies": { 42 | "@gzim/svelte-datagrid": "workspace:*" 43 | }, 44 | "type": "module" 45 | } 46 | -------------------------------------------------------------------------------- /apps/website/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer 10 | ] 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /apps/website/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /apps/website/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/website/src/app.pcss: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | body { 7 | @apply bg-slate-100 min-w-[320px]; 8 | } 9 | 10 | .btn { 11 | @apply inline-block rounded border px-4 py-1 text-sm font-medium text-white hover:bg-transparent focus:outline-none focus:ring transition-colors ease-in duration-200; 12 | } 13 | 14 | .btn-primary { 15 | @apply border-primary bg-primary text-white hover:text-primary active:text-primary; 16 | } 17 | 18 | .btn-secondary { 19 | @apply border-secondary bg-secondary text-white hover:text-secondary active:text-secondary; 20 | } 21 | 22 | .input { 23 | @apply border-2 border-solid border-slate-200 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring; 24 | } 25 | 26 | @tailwind components; 27 | 28 | @tailwind utilities; 29 | -------------------------------------------------------------------------------- /apps/website/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /apps/website/src/lib/sitecomponents/GitHubIcon.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /apps/website/src/lib/sitecomponents/Header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 | 20 |

{SITE_NAME}

21 | {SITE_NAME} 22 |
23 | 24 |
25 | 36 | 37 |
38 |
39 | 40 | 41 | 42 |
43 | 60 |
61 |
62 |
63 |
64 | 65 | {#if showmenu} 66 | 67 | {/if} 68 | 69 | 71 | -------------------------------------------------------------------------------- /apps/website/src/lib/sitecomponents/Message.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /apps/website/src/lib/sitecomponents/Progressbar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | Loading 7 | 8 | 14 | 15 | 48}> {progress}% 16 | 17 | 18 | 19 | 20 |
21 | 22 | 24 | -------------------------------------------------------------------------------- /apps/website/src/lib/sitecomponents/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
10 |
11 |
    12 | {#each MENU_LINKS as { name, url }} 13 |
  • 14 | 19 | {name} 20 | 21 |
  • 22 | {/each} 23 |
24 |
25 |
26 | 27 | 29 | -------------------------------------------------------------------------------- /apps/website/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {SITE_NAME} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /apps/website/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /apps/website/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 136 | 137 | 138 |
139 | console.log(detail)} 151 | on:rowClick={({ detail }) => console.log(detail)} 152 | /> 153 |
154 | 155 | {#if rows.length < 500} 156 | Scroll ~70% of the grid to add more rows programatically (max 500 rows) 157 | {/if} 158 | 159 |
160 |

161 | Rows count: {rows.length} 162 |

163 |
164 | 165 | 173 | 176 | 179 |
180 | 181 |
182 | 186 | 190 | 194 |
195 |
196 | 197 | 214 | -------------------------------------------------------------------------------- /apps/website/src/siteconfig.ts: -------------------------------------------------------------------------------- 1 | export const SITE_NAME = 'Svelte DataGrid'; 2 | export const SITE_DESCRIPTION = 'A high-performance Svelte DataGrid component'; 3 | export const SITE_URL = 'https://gzimbron.github.io/svelte-datagrid'; 4 | export const SITE_IMAGE = `${SITE_URL}/og-image.webp`; 5 | export const SITE_GITHUB_URL = 'https://github.com/gzimbron/svelte-datagrid'; 6 | 7 | export const MENU_LINKS = [{ name: 'GZim', url: 'https://zimbron.dev' }]; 8 | -------------------------------------------------------------------------------- /apps/website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzimbron/svelte-datagrid/5325908e3f6aaf783368b011af6e364ad5ed6e06/apps/website/static/favicon.ico -------------------------------------------------------------------------------- /apps/website/static/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzimbron/svelte-datagrid/5325908e3f6aaf783368b011af6e364ad5ed6e06/apps/website/static/logo.webp -------------------------------------------------------------------------------- /apps/website/static/og-image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzimbron/svelte-datagrid/5325908e3f6aaf783368b011af6e364ad5ed6e06/apps/website/static/og-image.webp -------------------------------------------------------------------------------- /apps/website/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: [vitePreprocess({})], 7 | 8 | kit: { 9 | adapter: adapter(), 10 | paths: { 11 | // eslint-disable-next-line turbo/no-undeclared-env-vars 12 | base: process.argv.includes('dev') ? '' : process.env.BASE_PATH 13 | }, 14 | alias: { 15 | $siteconfig: './src/siteconfig.ts', 16 | '$sitecomponent/*': './src/lib/sitecomponents/*' 17 | } 18 | } 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /apps/website/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config}*/ 2 | const config = { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | 5 | theme: { 6 | extend: { 7 | colors: { 8 | base: '#FDFFFC', 9 | primary: '#c1292e', 10 | secondary: '#235789' 11 | } 12 | } 13 | }, 14 | 15 | plugins: [] 16 | }; 17 | 18 | module.exports = config; 19 | -------------------------------------------------------------------------------- /apps/website/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 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // 16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 17 | // from the referenced tsconfig.json - TypeScript does not merge them in 18 | } 19 | -------------------------------------------------------------------------------- /apps/website/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /assets/demo-screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzimbron/svelte-datagrid/5325908e3f6aaf783368b011af6e364ad5ed6e06/assets/demo-screenshot.webp -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "turbo run build", 5 | "dev": "turbo run dev", 6 | "lint": "turbo run lint", 7 | "test": "turbo run test", 8 | "test:ui": "turbo run test:ui", 9 | "test:watch": "turbo run test:watch", 10 | "format": "prettier --write .", 11 | "package": "turbo run package && cp README.md packages/svelte-datagrid/README.md", 12 | "release": "changeset publish" 13 | }, 14 | "devDependencies": { 15 | "@mycustom/eslint-config": "workspace:*", 16 | "eslint": "^8.56.0", 17 | "prettier": "^3.1.1", 18 | "prettier-plugin-svelte": "^3.1.2", 19 | "turbo": "^1.12.4" 20 | }, 21 | "engines": { 22 | "node": ">=18" 23 | }, 24 | "dependencies": { 25 | "@changesets/cli": "^2.27.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/eslint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | 'prettier', 8 | 'turbo' 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['@typescript-eslint'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 2020, 15 | extraFileExtensions: ['.svelte'] 16 | }, 17 | env: { 18 | browser: true, 19 | es2017: true, 20 | node: true 21 | }, 22 | overrides: [ 23 | { 24 | files: ['*.svelte'], 25 | parser: 'svelte-eslint-parser', 26 | parserOptions: { 27 | parser: '@typescript-eslint/parser' 28 | } 29 | } 30 | ], 31 | ignorePatterns: ['node_modules', 'dist', 'public', 'build'] 32 | }; 33 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mycustom/eslint-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@typescript-eslint/eslint-plugin": "^6.17.0", 7 | "@typescript-eslint/parser": "^6.17.0", 8 | "eslint-config-prettier": "^9.1.0", 9 | "eslint-config-turbo": "^1.11.3", 10 | "eslint-plugin-svelte": "^2.35.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/.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 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | extends: ['@mycustom/eslint-config/index.js'] 4 | }; 5 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | /.changeset 6 | /dist 7 | /.github 8 | node_modules -------------------------------------------------------------------------------- /packages/svelte-datagrid/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @gzim/svelte-datagrid 2 | 3 | ## 0.4.0 4 | 5 | ### Minor Changes 6 | 7 | - b4706c4: Added rowClick and rowDblClick events suggested in issue #36 8 | 9 | ## 0.3.1 10 | 11 | ### Patch Changes 12 | 13 | - eac353a: Fix resizing border 14 | 15 | ## 0.3.0 16 | 17 | ### Minor Changes 18 | 19 | - fb7fccd: Add resizable columns feature 20 | 21 | ## 0.2.4 22 | 23 | ### Patch Changes 24 | 25 | - cb53ad7: Add README.md to deployed package 26 | 27 | ## 0.2.3 28 | 29 | ### Patch Changes 30 | 31 | - 994fb83: Fix: Negative Y scroll on iOS blink 32 | - 994fb83: Add Properties: headerRowHeight & rowsPerPage 33 | 34 | ## 0.2.2 35 | 36 | ### Patch Changes 37 | 38 | - bfc05c9: Fix Head row scroll 39 | 40 | ## 0.2.1 41 | 42 | ### Patch Changes 43 | 44 | - 44772c4: Added property --min-height 45 | 46 | ## 0.2.0 47 | 48 | ### Minor Changes 49 | 50 | - a17d5be: Feat: Add on:xScroll event 51 | - a17d5be: Feat: Export scrollToRow, getGridState functions 52 | 53 | ### Patch Changes 54 | 55 | - a17d5be: Refactor: Replace state class to localreactive variables 56 | 57 | ## 0.1.1 58 | 59 | ### Patch Changes 60 | 61 | - 7bb870e: Fix: Update virtual list value on updated cell 62 | 63 | ## 0.1.0 64 | 65 | ### Minor Changes 66 | 67 | - f0925cf: Feat: Draggable columns added checkout de README.md to view all new properties and events 68 | 69 | ## 0.0.4 70 | 71 | ### Patch Changes 72 | 73 | - 8536bb8: Types: Now on:valueUpdated has the type of the event instead of any 74 | 75 | ## 0.0.3 76 | 77 | ### Patch Changes 78 | 79 | - 113433a: Fix on:valueUpdated returns very first value on column prop 80 | 81 | ## 0.0.2 82 | 83 | ### Patch Changes 84 | 85 | - d447fbd: Update grid view when total rows or row height change; Update % scroll on rows length change 86 | - 44526c1: Fix: Scroll percent showing correctly from 0 to 100% 87 | 88 | ## 0.0.1 89 | 90 | ### Patch Changes 91 | 92 | - 104bab5: Added Homepage to package.json 93 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Gustavo Zimbrón (https://github.com/gzimbron) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /packages/svelte-datagrid/README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/@gzim/svelte-datagrid.svg?style=flat-square)](https://www.npmjs.com/package/@gzim/svelte-datagrid) 2 | 3 | # Svelte DataGrid 4 | 5 | Svelte DataGrid is a high-performance, feature-rich grid component for Svelte. It is designed to handle large datasets and provide a smooth scrolling experience. It is also designed to be accessible and customizable. 6 | 7 | It's based on the excellent (but deprecated) [svelte-data-grid](https://github.com/bsssshhhhhhh/svelte-data-grid). 8 | 9 | ## Demo website 10 | 11 | [![Demo website](./assets/demo-screenshot.webp)](https://gzimbron.github.io/svelte-datagrid) 12 | 13 | ## Features 14 | 15 | - High scrolling performance 16 | - ARIA attributes set on elements 17 | - Lightweight even when displaying a huge dataset due to implementation of a "virtual list" mechanism 18 | - Column headers remain fixed at the top of the grid 19 | - Custom components can be specified to control how individual table cells or column headers are displayed 20 | 21 | ## TODO 22 | 23 | - [x] Demo website 24 | - [x] Re-ordering columns 25 | - [ ] Resizing columns 26 | - [ ] Feel free to suggest more features or contribute to the project 27 | 28 | ## Usage: 29 | 30 | If using within Sapper: 31 | 32 | ```bash 33 | npm install @gzim/svelte-datagrid 34 | ``` 35 | 36 | If using from inside a svelte component: 37 | 38 | ```javascript 39 | import { Datagrid } from '@gzim/svelte-datagrid'; 40 | 41 | ; 48 | ``` 49 | 50 | Datagrid requires 2 properties to be passed in order to display data: `rows` and `columns`. 51 | 52 | `columns` is an array of objects containing at least 3 properties: `label`, `dataKey`, and `width`. A svelte component can be specified in `headerComponent` and `cellComponent` if any custom cell behavior is required. 53 | 54 | ```typescript 55 | [ 56 | { 57 | label: 'Name', // What will be displayed as the column header 58 | dataKey: 'firstName', // The key of a row to get the column's data from 59 | width: 400 // Width, in pixels, of column 60 | }, 61 | { 62 | label: 'Age', 63 | dataName: 'age', 64 | width: 150 65 | } 66 | ]; 67 | ``` 68 | 69 | `rows` is an array of objects containing the data for each table row. 70 | 71 | ```javascript 72 | [ 73 | { 74 | firstName: 'Gustavo', 75 | age: 34 76 | }, 77 | { 78 | firstName: 'Paulina', 79 | age: 31 80 | }, 81 | { 82 | firstName: 'Daphne', 83 | age: 2 84 | } 85 | ]; 86 | ``` 87 | 88 | ## Editing Data 89 | 90 | You can use this 3 componets as cellComponent to edit data: 91 | 92 | Import the components: 93 | 94 | ```typescript 95 | import { TextboxCell, SelectCell, CheckboxCell } from '@gzim/svelte-datagrid'; 96 | ``` 97 | 98 | ### Textbox Cell 99 | 100 | Textbox cell will debounce the user input. 101 | 102 | ```typescript 103 | { 104 | label: 'Name', 105 | dataKey: 'name', 106 | width: 250, 107 | cellComponent: TextboxCell 108 | } 109 | ``` 110 | 111 | ### Select Cell 112 | 113 | SelectCell requires that you provide an `options` array in your cell definition: 114 | 115 | ```typescript 116 | { 117 | label: 'Simpsons Character', 118 | dataKey: 'simpsonChar', 119 | width: 200, 120 | cellComponent: SelectCell, 121 | options: [ 122 | { 123 | display: 'Homer', 124 | value: 'homer' 125 | }, 126 | { 127 | display: 'Bart', 128 | value: 'bart' 129 | }, 130 | { 131 | display: 'Lisa', 132 | value: 'lisa' 133 | }, 134 | { 135 | display: 'Marge', 136 | value: 'marge' 137 | }, 138 | { 139 | display: 'Maggie', 140 | value: 'maggie' 141 | } 142 | ] 143 | } 144 | ``` 145 | 146 | ### Checkbox Cell 147 | 148 | CheckboxCell will set the checked state of the checkbox depending on the boolean value of the row's data. 149 | 150 | ```typescript 151 | { 152 | display: 'Pending', 153 | dataName: 'pending', 154 | width: 75, 155 | cellComponent: CheckboxCell 156 | } 157 | ``` 158 | 159 | ## Custom Cell Components 160 | 161 | To create a custom cell component, create a new Svelte component following the example below. 162 | 163 | Components will be passed the following properties: 164 | 165 | - `rowNumber` - The index of the row within `rows` 166 | - `row` - The entire row object from `rows` 167 | - `column` - The entire column object from `columns` 168 | 169 | MyCustomCell.svelte 170 | 171 | ```html 172 | 194 | 195 |
ADD HERE YOUR CUSTOM CELL CONTENT
196 | 197 | 202 | ``` 203 | 204 | Import the component 205 | 206 | ```typescript 207 | import MyCustomCell from './MyCustomCell.svelte'; 208 | ``` 209 | 210 | `columns` option: 211 | 212 | ```typescript 213 | [ 214 | { 215 | label: 'Icon' 216 | dataKey: 'icon', 217 | width: 300, 218 | cellComponent: MyCustomCell 219 | } 220 | ] 221 | ``` 222 | 223 | ## Custom Header Components 224 | 225 | Header components can also be specified in `columns` entries as the `headerComponent` property. Header components are only passed `column`, the column object from `columns`. 226 | 227 | ```html 228 | 233 | 234 |
~{ column.label }~
235 | 236 | 241 | ``` 242 | 243 | ## Properties: 244 | 245 | Datagrid provides a few options for controlling the grid and its interactions: 246 | 247 | ### Configurations 248 | 249 | - `rowHeight` - The row height in pixels _(Default: 24)_ 250 | - `headerRowHeight` - The row height in pixels _(Default: 24)_ 251 | - `rowsPerPage` - The number of rows to render per page _(Default: rows lenght up to 10)_ 252 | - `extraRows` - Add extra rows to the virtual list to improve scrolling performance _(Default: 0)_ 253 | - `allColumnsDraggable` - Set all columns draggable by default, ignoring the `draggable` property of each column _(Default: false)_ 254 | 255 | ### Functions exported 256 | 257 | Yoy can bind to the following functions to control the grid: 258 | 259 | - `getGridState` - A function that returns the current grid state. 260 | 261 | ```typescript 262 | const getGridState: () => { 263 | visibleRowsIndexes: { 264 | start: number; 265 | end: number; 266 | }; 267 | scrollTop: number; 268 | scrollLeft: number; 269 | yScrollPercent: number; 270 | xScrollPercent: number; 271 | }; 272 | ``` 273 | 274 | - `scrollToRow` - A function that scrolls the grid to a specific row index. 275 | 276 | ```typescript 277 | const scrollToRow: (rowIndex: number) => void; 278 | ``` 279 | 280 | ### Styling 281 | 282 | - `--border` Css: Custom style for grid borders _(Default: 1px)_ 283 | - `--header-border` Custom width for header row border bottom _(Default: 2px)_ 284 | - `--header-border-color` Custom color for header row border bottom _(Default: black)_ 285 | - `--head-bg` Custom background color for header row _(Default: white)_ 286 | - `--cell-bg` Custom background color for body cells _(Default: white)_ 287 | - `--textbox-cell-bg` ustom background color for textbox cells _(Default: white)_ 288 | - `--select-cell-bg` Custom background color for select cells _(Default: white)_ 289 | - `--head-color` Custom color for header row text. 290 | - `--cell-color` Custom color for body cells text 291 | - `--textbox-cell-color` Custom color for textbox cells text 292 | - `--select-cell-color` Custom color for select cells text 293 | - `--no-draggable-opacity` Opacity for NOT draggable columns content when dragging. _(Default: 0.4)_ 294 | - `--no-draggable-fg` CSS color for NOT draggable columns when dragging, this color is used to create an overlay over the column _(Default: rgba(66, 66, 66, 0.5))_ 295 | - `--draggable-bg` CSS Hover color for draggable columns. _(Default: rgba(33, 248, 255, 0.5))_ 296 | - `--dragging-bg` CSS Background color for actual dragging column. _(Default: rgba(33, 255, 151, 0.5))_ 297 | - `--grid-height` Min height for the grid container _(@default RowHeight \* 6)_ 298 | 299 | ## Events: 300 | 301 | - `scroll` - Triggered when the grid is scrolled on Y axis. The Y scroll percent position can be accessed from `event.detail` 302 | - `xScroll` - Triggered when the grid is scrolled on X axis. The X scroll percent position can be accessed from `event.detail` 303 | - `valueUpdated` - Triggered when a cell's value is updated. The updated value can be accessed from `event.value`, other data can be accessed from `event.row`, `event.column` and `event.rowIndex` 304 | - `columnsSwapped` - Triggered when columns are swapped. `event.detail` contains `from`, `to` and new `columns` order properties 305 | 306 | ## Bugs? Suggestions? 307 | 308 | Please file an issue if you find a bug or have a suggestion for a new feature. 309 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gzim/svelte-datagrid", 3 | "version": "0.4.0", 4 | "license": "MIT", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "scripts": { 9 | "dev": "svelte-package -w", 10 | "build": "svelte-package", 11 | "preview": "vite preview", 12 | "prepare": "svelte-kit sync", 13 | "package": "svelte-kit sync && svelte-package && publint", 14 | "prepublishOnly": "svelte-kit sync && npm run package", 15 | "check": "svelte-check --tsconfig ./tsconfig.json", 16 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 17 | "lint": "prettier --check . && eslint .", 18 | "format": "prettier --write .", 19 | "test": "vitest run", 20 | "test:ui": "vitest --ui", 21 | "test:watch": "vitest" 22 | }, 23 | "exports": { 24 | "./package.json": "./package.json", 25 | ".": { 26 | "types": "./dist/index.d.ts", 27 | "svelte": "./dist/index.js" 28 | }, 29 | "./types": { 30 | "types": "./dist/types.d.ts", 31 | "default": "./dist/types.js" 32 | } 33 | }, 34 | "files": [ 35 | "dist", 36 | "!dist/**/*.test.*", 37 | "!dist/**/*.spec.*" 38 | ], 39 | "peerDependencies": { 40 | "svelte": "^3.1.0 || ^4.0.0" 41 | }, 42 | "devDependencies": { 43 | "@changesets/cli": "^2.27.1", 44 | "@mycustom/eslint-config": "workspace:*", 45 | "@sveltejs/adapter-auto": "^3.1.1", 46 | "@sveltejs/kit": "^2.0.0", 47 | "@sveltejs/package": "^2.0.0", 48 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 49 | "@testing-library/jest-dom": "^6.4.2", 50 | "@testing-library/svelte": "^4.1.0", 51 | "@types/eslint": "8.56.0", 52 | "@typescript-eslint/eslint-plugin": "^6.0.0", 53 | "@typescript-eslint/parser": "^6.0.0", 54 | "@vitest/ui": "^1.3.0", 55 | "eslint": "^8.56.0", 56 | "eslint-config-prettier": "^9.1.0", 57 | "eslint-plugin-svelte": "^2.35.1", 58 | "jsdom": "^24.0.0", 59 | "prettier": "^3.1.1", 60 | "prettier-plugin-svelte": "^3.1.2", 61 | "publint": "^0.1.9", 62 | "svelte": "^4.2.7", 63 | "svelte-check": "^3.6.0", 64 | "tslib": "^2.4.1", 65 | "typescript": "^5.0.0", 66 | "vite": "^5.0.11", 67 | "vitest": "^1.2.0" 68 | }, 69 | "svelte": "./dist/index.js", 70 | "types": "./dist/index.d.ts", 71 | "type": "module", 72 | "author": { 73 | "name": "Gustavo Zimbrón", 74 | "email": "gustavo@zimbron.dev", 75 | "url": "https://zimbron.dev" 76 | }, 77 | "repository": { 78 | "type": "git", 79 | "url": "git+https://github.com/gzimbron/svelte-datagrid.git" 80 | }, 81 | "homepage": "https://gzimbron.github.io/svelte-datagrid/", 82 | "bugs": { 83 | "url": "https://github.com/gzimbron/svelte-datagrid/issues" 84 | }, 85 | "keywords": [ 86 | "svelte", 87 | "grid", 88 | "data-grid", 89 | "table", 90 | "data-table" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/DataGridProps.ts: -------------------------------------------------------------------------------- 1 | export interface GridProps { 2 | /** 3 | * Css: Custom style for grid borders: 4 | * @default '1px solid #666' 5 | */ 6 | '--border'?: string; 7 | /** 8 | * Custom width for header row border bottom 9 | * @default '2px' 10 | */ 11 | '--header-border'?: string; 12 | /** 13 | * Custom color for header row border bottom 14 | * @default 'black' 15 | */ 16 | '--header-border-color'?: string; 17 | /** 18 | * Custom background color for header row 19 | * @default 'white' 20 | */ 21 | '--head-bg'?: string; 22 | /** 23 | * Custom background color for body cells 24 | * @default 'white' 25 | */ 26 | '--cell-bg'?: string; 27 | /** 28 | * Custom background color for textbox cells 29 | * @default 'white' 30 | */ 31 | '--textbox-cell-bg'?: string; 32 | /** 33 | * Custom background color for select cells 34 | * @default 'white' 35 | */ 36 | '--select-cell-bg'?: string; 37 | /** 38 | * Custom color for header row text 39 | */ 40 | '--head-color'?: string; 41 | /** 42 | * Custom color for body cells text 43 | */ 44 | '--cell-color'?: string; 45 | /** 46 | * Custom color for textbox cells text 47 | */ 48 | '--textbox-cell-color'?: string; 49 | /** 50 | * Custom color for select cells text 51 | */ 52 | '--select-cell-color'?: string; 53 | /** 54 | * Opacity for NOT draggable columns content when dragging. 55 | * @default '0.4' 56 | */ 57 | '--no-draggable-opacity'?: string; 58 | /** 59 | * CSS color for NOT draggable columns when dragging, this color is used to create an overlay over the column 60 | * @default 'rgba(66, 66, 66, 0.5)' 61 | */ 62 | '--no-draggable-fg'?: string; 63 | /** 64 | * CSS Hover color for draggable columns. 65 | * @default 'rgba(33, 248, 255, 0.5)' 66 | */ 67 | '--draggable-bg'?: string; 68 | /** 69 | * CSS Background color for actual dragging column. 70 | * @default 'rgba(33, 255, 151, 0.5)' 71 | */ 72 | '--dragging-bg'?: string; 73 | /** 74 | * Min height for the grid container 75 | * @default RowHeight * 6 76 | */ 77 | '--grid-height'?: string; 78 | } 79 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/actions/dragAndDrop.ts: -------------------------------------------------------------------------------- 1 | import type { Action } from 'svelte/action'; 2 | 3 | interface ActionParams { 4 | draggable?: boolean; 5 | dropzone?: boolean; 6 | dragStart?: (e: DragEvent) => void; 7 | dragEnd?: (e: DragEvent) => void; 8 | } 9 | 10 | export const dragAndDrop: Action = ( 11 | node, 12 | { draggable, dragEnd, dragStart }: ActionParams = {} 13 | ) => { 14 | if (draggable) { 15 | node.addEventListener('dragstart', (e) => { 16 | const div = e.target as HTMLElement; 17 | const isRezisable = div.classList.contains('resizable'); 18 | 19 | if (isRezisable) { 20 | const resizableWidth = parseInt( 21 | window.getComputedStyle(div, '::after').width.replace('px', '') 22 | ); 23 | 24 | const clickPosition = e.clientX - div.getBoundingClientRect().left; 25 | 26 | if (clickPosition >= div.offsetWidth - resizableWidth - 2) { 27 | e.preventDefault(); 28 | return; 29 | } 30 | } 31 | 32 | dragStart && dragStart(e); 33 | node.classList.add('dragging'); 34 | }); 35 | 36 | node.addEventListener('dragend', (e) => { 37 | dragEnd && dragEnd(e); 38 | node.classList.remove('dragging'); 39 | }); 40 | } 41 | 42 | return { 43 | destroy() { 44 | node.removeEventListener('dragstart', () => {}); 45 | node.removeEventListener('dragend', () => {}); 46 | } 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/actions/resizeColumn.ts: -------------------------------------------------------------------------------- 1 | import type { Action } from 'svelte/action'; 2 | 3 | interface ActionParams { 4 | resizable?: boolean; 5 | startResize?: () => void; 6 | endResize?: () => void; 7 | onResize?: (data: number) => void; 8 | } 9 | 10 | export const reziseColumn: Action = ( 11 | node, 12 | { resizable, onResize, startResize, endResize }: ActionParams = {} 13 | ) => { 14 | if (!resizable) return; 15 | 16 | let lastX = 0; 17 | 18 | const mouseMoveEvent = (e: MouseEvent) => { 19 | if (!onResize) return; 20 | if (e.clientX === lastX) return; 21 | lastX = e.clientX; 22 | onResize(lastX - node.getBoundingClientRect().left); 23 | }; 24 | 25 | const mouseDownEvent = (e: MouseEvent) => { 26 | if (e.button !== 0) { 27 | return; 28 | } 29 | lastX = e.clientX; 30 | 31 | // get node width and style ::after 32 | const nodeAfterWidth = parseInt( 33 | window.getComputedStyle(node, ':after').width.replace('px', '') 34 | ); 35 | const clickPosition = e.clientX - node.getBoundingClientRect().left; 36 | 37 | if (clickPosition < node.offsetWidth - nodeAfterWidth - 2) return; 38 | 39 | startResize && startResize(); 40 | 41 | document.addEventListener('mousemove', mouseMoveEvent); 42 | document.addEventListener('mouseup', mouseUpEvent); 43 | }; 44 | 45 | const mouseUpEvent = () => { 46 | endResize && endResize(); 47 | document.removeEventListener('mousemove', mouseMoveEvent); 48 | }; 49 | 50 | node.addEventListener('mousedown', mouseDownEvent); 51 | 52 | return { 53 | destroy() { 54 | document.removeEventListener('mousedown', mouseDownEvent); 55 | node.removeEventListener('mouseup', () => mouseUpEvent); 56 | } 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/components/CheckboxCell.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 | 39 |
40 | 41 | 46 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/components/Datagrid.svelte: -------------------------------------------------------------------------------- 1 | 259 | 260 |
272 |
273 |
274 | {#each columns as column, i (i)} 275 |
{ 308 | isDragging = true; 309 | columnDragging = i; 310 | }, 311 | dragEnd: (e) => { 312 | isDragging = false; 313 | 314 | const { clientX, clientY } = e; 315 | if ( 316 | clientX < svelteGridWrapper.offsetLeft || 317 | clientX > svelteGridWrapper.offsetLeft + svelteGridWrapper.offsetWidth || 318 | clientY < svelteGridWrapper.offsetTop || 319 | clientY > svelteGridWrapper.offsetTop + svelteGridWrapper.offsetHeight 320 | ) { 321 | resetDraggind(); 322 | return; 323 | } 324 | 325 | svelteGridWrapper.querySelectorAll('.columnheader').forEach((el, index) => { 326 | const { left, right } = el.getBoundingClientRect(); 327 | if ( 328 | clientX > left && 329 | clientX < right && 330 | (allColumnsDraggable || columns[index].draggable) && 331 | index != columnDragging 332 | ) { 333 | swapColumns(columnDragging, index); 334 | } 335 | }); 336 | 337 | resetDraggind(); 338 | } 339 | }} 340 | > 341 |
342 | {#if column.headerComponent} 343 | 344 | {:else} 345 | {column.label || ''} 346 | {/if} 347 |
348 |
349 | {/each} 350 |
351 |
352 | 353 |
354 |
355 | 356 | {#each visibleRows as row, virtualRowIndex} 357 |
{}} 364 | on:click={() => rowClick(row)} 365 | on:dblclick={() => rowDblClick(row)} 366 | > 367 | {#each columns as column, j (j)} 368 |
379 |
380 | {#if column.cellComponent} 381 | 389 | {:else} 390 | {row.data[column.dataKey] || ''} 391 | {/if} 392 |
393 |
394 | {/each} 395 |
396 | {/each} 397 |
398 |
399 | 400 | 577 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/components/Datagrid.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateDefaultRowsPerPage } from '$lib/functions/calculateFunctions.js'; 2 | import type { GridColumn } from '$lib/types.js'; 3 | import { fireEvent, render, screen } from '@testing-library/svelte'; 4 | import { beforeEach, describe, expect, it } from 'vitest'; 5 | import Datagrid from './Datagrid.svelte'; 6 | 7 | interface Cat { 8 | name: string; 9 | age: number; 10 | color: string; 11 | } 12 | const columns: GridColumn[] = [ 13 | { 14 | label: 'Name', 15 | dataKey: 'name', 16 | width: 120 17 | }, 18 | { 19 | label: 'Age', 20 | dataKey: 'age', 21 | width: 80 22 | }, 23 | { 24 | label: 'Color', 25 | dataKey: 'color', 26 | width: 120 27 | } 28 | ]; 29 | 30 | const rows: Cat[] = [ 31 | { name: 'Fluffy', age: 2, color: 'black' }, 32 | { name: 'Whiskers', age: 3, color: 'white' }, 33 | { name: 'Felix', age: 1, color: 'orange' } 34 | ]; 35 | 36 | describe('Datagrid', () => { 37 | beforeEach(() => { 38 | document.body.innerHTML = ''; 39 | }); 40 | 41 | it('should render component', async () => { 42 | const { container } = render(Datagrid, { columns, rows }); 43 | 44 | expect(container.firstChild).not.toBeNull(); 45 | }); 46 | 47 | it('should render 3 column headers', async () => { 48 | const { findAllByTestId } = render(Datagrid, { columns, rows }); 49 | 50 | const columnHeaders = await findAllByTestId('columnheader'); 51 | 52 | expect(columnHeaders).toHaveLength(3); 53 | }); 54 | 55 | it('should fail if no columns are provided', async () => { 56 | expect(() => render(Datagrid, { rows, columns: [] })).toThrow(); 57 | }); 58 | 59 | it('should fail if no rows are provided', async () => { 60 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 61 | expect(() => render(Datagrid, { columns } as any)).toThrow(); 62 | }); 63 | 64 | it('should render if empty rows array is provided', async () => { 65 | const { container } = render(Datagrid, { columns, rows: [] }); 66 | 67 | expect(container.firstChild).not.toBeNull(); 68 | }); 69 | 70 | it(`should render default rows in viewport`, async () => { 71 | const defaultNumRows = calculateDefaultRowsPerPage(rows.length); 72 | 73 | const { findAllByRole } = render(Datagrid, { columns, rows }); 74 | 75 | let rowsResult = await findAllByRole('row'); 76 | 77 | rowsResult = rowsResult.filter((row) => !row.classList.contains('header-row')); 78 | 79 | expect(rowsResult).toHaveLength(defaultNumRows); 80 | }); 81 | 82 | it(`should update visibleRowsIndexes when scroll top by 200`, async () => { 83 | const { component } = render(Datagrid, { 84 | columns, 85 | rows: [...rows, ...rows, ...rows, ...rows, ...rows], 86 | rowsPerPage: 3 87 | }); 88 | 89 | const dataGridBody = (await screen.findAllByRole('rowgroup'))[1]; 90 | 91 | await fireEvent.scroll(dataGridBody, { target: { scrollTop: 200 } }); 92 | 93 | const { visibleRowsIndexes } = component.getGridState(); 94 | 95 | expect(visibleRowsIndexes.start).toBe(8); 96 | expect(visibleRowsIndexes.end).toBe(12); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/components/SelectCell.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 54 | 55 | 70 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/components/TextboxCell.svelte: -------------------------------------------------------------------------------- 1 | 78 | 79 |
80 | 88 |
89 | 90 | 111 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/configurations.ts: -------------------------------------------------------------------------------- 1 | export const MIN_ROW_HEIGHT = 20; 2 | export const MAX_DEFAULT_ROWS_PER_PAGE = 10; 3 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/functions/calculateFunctions.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { 3 | MIN_COLUMN_WIDTH, 4 | calculateGridSpaceWidth, 5 | calculatePercent, 6 | calculateXPositions, 7 | getRowTop, 8 | getVisibleRowsIndexes, 9 | updateColumnWidths 10 | } from './calculateFunctions.js'; 11 | import type { GridColumn } from '$lib/types.js'; 12 | 13 | describe('updateColumnWidths', () => { 14 | it('should return an array of column widths', () => { 15 | const columns = [ 16 | { width: 100 }, 17 | { width: 200 }, 18 | { width: 150 }, 19 | { width: undefined } 20 | ] as GridColumn[]; 21 | 22 | const expected = [100, 200, 150, MIN_COLUMN_WIDTH]; 23 | const result = updateColumnWidths(columns); 24 | 25 | expect(result).toEqual(expected); 26 | }); 27 | 28 | it('should return an array of default column widths if no width is provided', () => { 29 | const columns = [{}, {}, {}] as GridColumn[]; 30 | 31 | const expected = [MIN_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MIN_COLUMN_WIDTH]; 32 | const result = updateColumnWidths(columns); 33 | 34 | expect(result).toEqual(expected); 35 | }); 36 | }); 37 | 38 | describe('calculateXPositions', () => { 39 | const widthColumns = [100, 200, 150, 100]; 40 | 41 | it('should return an array of x positions', () => { 42 | const expected = [0, 100, 300, 450]; 43 | const result = calculateXPositions(widthColumns); 44 | 45 | expect(result).toEqual(expected); 46 | }); 47 | 48 | it('should return the number of columns', () => { 49 | const expected = widthColumns.length; 50 | const result = calculateXPositions(widthColumns).length; 51 | 52 | expect(result).toEqual(expected); 53 | }); 54 | }); 55 | 56 | describe('calculateGridSpaceWidth', () => { 57 | const widthColumns = [100, 200, 150, 100]; 58 | it('should return the sum of the column widths', () => { 59 | const expected = 550; 60 | const result = calculateGridSpaceWidth(widthColumns); 61 | 62 | expect(result).toEqual(expected); 63 | }); 64 | 65 | it('should return a value greater than 0', () => { 66 | const result = calculateGridSpaceWidth(widthColumns); 67 | expect(result).toBeGreaterThan(0); 68 | }); 69 | }); 70 | 71 | describe('getVisibleRowsIndexes', () => { 72 | it('should return the start and end indexes of the visible rows', () => { 73 | const rowHeight = 20; 74 | const scrollTop = 40; 75 | const wrapperHeight = 100; 76 | const totalRows = 100; 77 | const extraRows = 10; 78 | 79 | const expected = { start: 2, end: 17 }; 80 | const result = getVisibleRowsIndexes(rowHeight, scrollTop, wrapperHeight, totalRows, extraRows); 81 | 82 | expect(result).toEqual(expected); 83 | }); 84 | 85 | it('should return the start and end indexes of the visible rows when scrollTop is 0 no extra rows', () => { 86 | const rowHeight = 20; 87 | const scrollTop = 0; 88 | const wrapperHeight = 100; 89 | const totalRows = 100; 90 | const extraRows = 0; 91 | 92 | const expected = { start: 0, end: 5 }; 93 | const result = getVisibleRowsIndexes(rowHeight, scrollTop, wrapperHeight, totalRows, extraRows); 94 | 95 | expect(result).toEqual(expected); 96 | }); 97 | }); 98 | 99 | describe('getRowTop', () => { 100 | it('should return the top position of the row', () => { 101 | const rowHeight = 20; 102 | const rowIndex = 5; 103 | 104 | const expected = 100; 105 | const result = getRowTop(rowIndex, rowHeight); 106 | 107 | expect(result).toEqual(expected); 108 | }); 109 | }); 110 | 111 | describe('calculatePercent', () => { 112 | it('should return the percentage of 23/100 items', () => { 113 | const actual = 23; 114 | const total = 110; 115 | 116 | const expected = 21; 117 | const result = calculatePercent(actual, total); 118 | 119 | expect(result).toEqual(expected); 120 | }); 121 | 122 | it('should return the percentage of 0/100 items', () => { 123 | const actual = 0; 124 | const total = 100; 125 | 126 | const expected = 0; 127 | const result = calculatePercent(actual, total); 128 | 129 | expect(result).toEqual(expected); 130 | }); 131 | 132 | it('should return the percentage of 100/100 items', () => { 133 | const actual = 100; 134 | const total = 100; 135 | 136 | const expected = 100; 137 | const result = calculatePercent(actual, total); 138 | 139 | expect(result).toEqual(expected); 140 | }); 141 | 142 | it('should return the percentage of 50/100 items', () => { 143 | const actual = 50; 144 | const total = 100; 145 | 146 | const expected = 50; 147 | const result = calculatePercent(actual, total); 148 | 149 | expect(result).toEqual(expected); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/functions/calculateFunctions.ts: -------------------------------------------------------------------------------- 1 | import { MAX_DEFAULT_ROWS_PER_PAGE } from '$lib/configurations.js'; 2 | import type { GridColumn } from '$lib/types.js'; 3 | 4 | export const MIN_COLUMN_WIDTH = 20; 5 | 6 | export function updateColumnWidths(columns: GridColumn[]) { 7 | return columns.map((column) => column.width || MIN_COLUMN_WIDTH); 8 | } 9 | 10 | export function calculateXPositions(columnWidths: number[]) { 11 | const xPositions = [0]; 12 | let x = 0; 13 | for (let i = 0; i < columnWidths.length - 1; i++) { 14 | x += columnWidths[i]; 15 | xPositions.push(x); 16 | } 17 | return xPositions; 18 | } 19 | 20 | export function calculateGridSpaceWidth(columnWidths: number[]) { 21 | return columnWidths.reduce((acc, width) => acc + width, 0); 22 | } 23 | 24 | export function getVisibleRowsIndexes( 25 | rowHeight: number, 26 | scrollTop: number, 27 | wrapperHeight: number, 28 | totalRows: number, 29 | extraRows: number 30 | ) { 31 | //console.log({ rowHeight, scrollTop, wrapperHeight, totalRows, extraRows }); 32 | let start = Math.floor(scrollTop / rowHeight); 33 | 34 | if (start < 0) start = 0; 35 | 36 | const end = Math.min(Math.ceil((scrollTop + wrapperHeight) / rowHeight) + extraRows, totalRows); 37 | return { start, end }; 38 | } 39 | 40 | export function getRowTop(rowIndex: number, rowHeight: number) { 41 | return rowIndex * rowHeight; 42 | } 43 | 44 | export function calculatePercent(actual: number, total: number) { 45 | return Math.round((actual / total) * 100); 46 | } 47 | 48 | export function calculateDefaultRowsPerPage(rowsLength: number) { 49 | return rowsLength > MAX_DEFAULT_ROWS_PER_PAGE ? MAX_DEFAULT_ROWS_PER_PAGE : rowsLength; 50 | } 51 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/functions/gridHelpers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { swapGridColums } from './gridHelpers.js'; 3 | import type { GridColumn } from '$lib/types.js'; 4 | 5 | describe('swapGridColums', () => { 6 | it('should swap columns in the grid', () => { 7 | const columns = [ 8 | { label: 'Column 1' }, 9 | { label: 'Column 2' }, 10 | { label: 'Column 3' } 11 | ] as GridColumn[]; 12 | const fromColumn = 0; 13 | const toColumn = 2; 14 | 15 | const result = swapGridColums(columns, fromColumn, toColumn); 16 | 17 | expect(result.columns[toColumn]).toEqual(columns[fromColumn]); 18 | expect(result.columns[fromColumn]).toEqual(columns[toColumn]); 19 | 20 | expect(result.from).toEqual(columns[fromColumn]); 21 | expect(result.to).toEqual(columns[toColumn]); 22 | }); 23 | 24 | it('should keep original columns unchanged', () => { 25 | const columns = [ 26 | { label: 'Column 1' }, 27 | { label: 'Column 2' }, 28 | { label: 'Column 3' } 29 | ] as GridColumn[]; 30 | 31 | const columnsCopy = [...columns]; 32 | 33 | const fromColumn = 0; 34 | const toColumn = 2; 35 | 36 | swapGridColums(columns, fromColumn, toColumn); 37 | 38 | expect(columns).toEqual(columnsCopy); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/functions/gridHelpers.ts: -------------------------------------------------------------------------------- 1 | import type { GridColumn } from '$lib/types.js'; 2 | 3 | export function swapGridColums(columns: GridColumn[], fromColumn: number, toColumn: number) { 4 | const columnsCopy = [...columns]; 5 | const from = columnsCopy[fromColumn]; 6 | const to = columnsCopy[toColumn]; 7 | columnsCopy[fromColumn] = to; 8 | columnsCopy[toColumn] = from; 9 | 10 | return { from, to, columns: [...columnsCopy] }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Datagrid } from '$lib/components/Datagrid.svelte'; 2 | export { default as TextboxCell } from '$lib/components/TextboxCell.svelte'; 3 | export { default as SelectCell } from '$lib/components/SelectCell.svelte'; 4 | export { default as CheckboxCell } from '$lib/components/CheckboxCell.svelte'; 5 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentType, SvelteComponent } from 'svelte'; 2 | 3 | type StringKeys = Extract; 4 | 5 | export type GridColumn = { 6 | label: string; 7 | dataKey: StringKeys; 8 | width: number; 9 | headerComponent?: CustomHeaderComponent; 10 | cellComponent?: CustomCellComponent; 11 | options?: GridColumnOption[]; 12 | draggable?: boolean; 13 | resizable?: boolean; 14 | }; 15 | 16 | export type GridColumnOption = { 17 | value: string; 18 | label: string | (() => string); 19 | }; 20 | 21 | export type GridRow = { 22 | data: T; 23 | i: number; 24 | }; 25 | 26 | export type GridCellUpdated = { 27 | row: T; 28 | column: GridColumn; 29 | value: unknown; 30 | rowIndex: number; 31 | virtualRowIndex: number; 32 | }; 33 | 34 | interface CustomHeaderComponentProps { 35 | column: GridColumn; 36 | } 37 | 38 | interface CustomCellComponentProps { 39 | rowIndex: number; 40 | virtualRowIndex: number; 41 | column: GridColumn; 42 | row: T; 43 | } 44 | 45 | type CustomCellComponentEvents = { 46 | /** 47 | * Event triggered when the value of a cell is updated by the user 48 | */ 49 | valueUpdated: CustomEvent>; 50 | }; 51 | 52 | type CustomCellComponent = ComponentType< 53 | SvelteComponent, CustomCellComponentEvents> 54 | >; 55 | 56 | type CustomHeaderComponent = ComponentType>>; 57 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: [vitePreprocess({})], 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/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 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/svelte-datagrid/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'], 8 | environment: 'jsdom' 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": [".svelte-kit/**", "build/**"] 7 | }, 8 | "lint": {}, 9 | "test": {}, 10 | "test:ui": {}, 11 | "test:watch": {}, 12 | "package": { 13 | "dependsOn": ["^build"] 14 | }, 15 | "dev": { 16 | "cache": false, 17 | "persistent": true 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------