├── .gitignore
├── src
├── main.svelte
├── checkbox-cell.svelte
├── select-cell.svelte
├── textbox-cell.svelte
├── edit-history.js
└── index.svelte
├── TODO.md
├── rollup.config.js
├── LICENSE.txt
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | yarn.lock
4 | package-lock.json
5 | index.mjs
6 | index.js
7 |
--------------------------------------------------------------------------------
/src/main.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - Add accessible ways of resizing and reordering columns using only a keyboard
4 | - Add mechanism for loading chunks of data asynchronously so the entire data set does not need to be loaded at once
5 | - Add ability to affix columns and rows so they remain visible when scrolling horizontally or vertically
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from "rollup-plugin-svelte";
2 | import resolve from "@rollup/plugin-node-resolve";
3 | import pkg from "./package.json";
4 |
5 | const name = pkg.name
6 | .replace(/^(@\S+\/)?(svelte-)?(\S+)/, "$3")
7 | .replace(/^\w/, m => m.toUpperCase())
8 | .replace(/-\w/g, m => m[1].toUpperCase());
9 |
10 | export default {
11 | input: "src/index.svelte",
12 | output: [
13 | { file: pkg.module, format: "es" },
14 | { file: pkg.main, format: "umd", name }
15 | ],
16 | plugins: [svelte(), resolve()]
17 | };
18 |
--------------------------------------------------------------------------------
/src/checkbox-cell.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
29 |
30 |
31 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Brian Simon (https://github.com/bsssshhhhhhh)
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.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-data-grid",
3 | "description": "High-performance svelte data table component for displaying huge datasets",
4 | "version": "3.0.1",
5 | "author": {
6 | "name": "Brian Simon",
7 | "email": "bsssshhhhhhh@gmail.com",
8 | "url": "https://github.com/bsssshhhhhhh"
9 | },
10 | "homepage": "https://github.com/bsssshhhhhhh/svelte-data-grid",
11 | "svelte": "src/main.svelte",
12 | "module": "index.mjs",
13 | "main": "index.js",
14 | "scripts": {
15 | "build": "rollup -c",
16 | "dev": "rollup -wc",
17 | "prepublishOnly": "npm run build"
18 | },
19 | "devDependencies": {
20 | "@rollup/plugin-node-resolve": "^8.0.1",
21 | "rollup": "^2.17.1",
22 | "rollup-plugin-commonjs": "^10.1.0",
23 | "rollup-plugin-svelte": "^5.1.1",
24 | "svelte": "^3.23.2"
25 | },
26 | "keywords": [
27 | "svelte",
28 | "data-grid",
29 | "data-table",
30 | "table",
31 | "virtual-list"
32 | ],
33 | "repository": "bsssshhhhhhh/svelte-data-grid",
34 | "license": "MIT",
35 | "files": [
36 | "src",
37 | "README.md",
38 | "LICENSE.txt"
39 | ],
40 | "dependencies": {
41 | "debounce": "^1.2.0",
42 | "deep-diff": "^1.0.2",
43 | "detect-browser": "^5.0.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/select-cell.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 |
44 |
45 |
99 |
--------------------------------------------------------------------------------
/src/edit-history.js:
--------------------------------------------------------------------------------
1 | import DeepDiff from 'deep-diff';
2 | const applyChange = DeepDiff.applyChange;
3 | const diff = DeepDiff.diff;
4 | /**
5 | * Edit history tracker for a javascript object using deep-diff to generate and apply patches
6 | */
7 | export default class EditHistory {
8 | /**
9 | * Instantiates an instance of EditHistory
10 | * @param {Object} obj The object or array to track
11 | */
12 | constructor(obj) {
13 | this.obj = JSON.parse(JSON.stringify(obj));
14 |
15 | // initialize arrays for forwards and backwards patches
16 | this.forward = [];
17 | this.backward = [];
18 | }
19 |
20 | /**
21 | * Clears all forward and backward patches
22 | */
23 | clear() {
24 | this.forward = [];
25 | this.backward = [];
26 | }
27 |
28 | /**
29 | * Records a change to an object
30 | * @param {Object} newObj The new object
31 | */
32 | recordChange(newObj) {
33 | const patch = {
34 | redo: diff(this.obj, newObj),
35 | undo: diff(newObj, this.obj)
36 | };
37 |
38 | if (!patch.redo || !patch.undo) {
39 | console.warn("Objects could not be diffed");
40 | } else {
41 | this.obj = JSON.parse(JSON.stringify(newObj));
42 | this.backward.push(patch);
43 | }
44 | }
45 |
46 | /**
47 | * Applies the most recent undo patch and returns the new object
48 | * @returns {Object} The tracked object
49 | */
50 | undo() {
51 | if (this.backward.length === 0) {
52 | return null;
53 | }
54 |
55 | // grab the most recent backwards patch
56 | const patch = this.backward.pop();
57 |
58 | // applyChange doesn't accept arrays, only its members
59 | patch.undo.forEach(x => applyChange(this.obj, x));
60 |
61 | // put the patch into the forward queue
62 | this.forward.push(patch);
63 |
64 | return JSON.parse(JSON.stringify(this.obj));
65 | }
66 |
67 | /**
68 | * Applies the most recent redo patch and returns the new object
69 | * @returns {Object} The tracked object
70 | */
71 | redo() {
72 | if (this.forward.length === 0) {
73 | return null;
74 | }
75 |
76 | // grab the most recent forwards patch
77 | const patch = this.forward.pop();
78 |
79 | // applyChange doesn't accept arrays, only its members
80 | patch.redo.forEach(x => applyChange(this.obj, x));
81 |
82 | // put the patch into the backward queue
83 | this.backward.push(patch);
84 |
85 | return JSON.parse(JSON.stringify(this.obj));
86 | }
87 |
88 | /**
89 | * Applies all the undo patches in the queue and returns the new object
90 | * @returns {Object} The tracked object
91 | */
92 | undoAll() {
93 | while (this.backward.length > 0) {
94 | this.undo();
95 | }
96 |
97 | return this.obj;
98 | }
99 |
100 | /**
101 | * Applies all the redo patches in the queue and returns the new object
102 | * @returns {Object} The tracked object
103 | */
104 | redoAll() {
105 | while (this.forward.length > 0) {
106 | this.redo();
107 | }
108 |
109 | return this.obj;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://npmjs.org/package/svelte-data-grid)
2 | # Svelte Data Grid
3 |
4 | ## [Demo](https://bsssshhhhhhh.github.io/svelte-data-grid-demo/)
5 |
6 |
7 | Svelte Data Grid is a svelte v3 component for displaying and editing any amount of data.
8 |
9 | ## Features:
10 | - Excellent scrolling performance
11 | - ARIA attributes set on elements
12 | - Lightweight even when displaying a huge dataset due to implementation of a "virtual list" mechanism
13 | - Column headers remain fixed at the top of the grid
14 | - Custom components can be specified to control how individual table cells or column headers are displayed
15 | - Columns can be resized and reordered
16 |
17 | ## Current Limitations:
18 | - Every row must have the same height and text cannot break onto the next line
19 |
20 | ## Usage:
21 |
22 | If using within Sapper:
23 | ```
24 | npm install svelte-data-grid --save-dev
25 | ```
26 |
27 | If using from inside a svelte component:
28 | ```
29 | import DataGrid from "svelte-data-grid";
30 |
31 | ```
32 |
33 | If using from outside svelte:
34 | ```
35 | import DataGrid from "svelte-data-grid";
36 | const grid = new DataGrid({
37 | target: document.querySelector('#my-grid-wrapper'),
38 | data: {
39 | rows: [ ... ],
40 | columns: [ ... ],
41 | allowResizeFromTableCells: true
42 | }
43 | });
44 |
45 | grid.$on('columnOrderUpdated', () => {
46 | const { columns } = grid.get();
47 | // save new column order
48 | });
49 | ```
50 | To learn more about using DataGrid outside of svelte, read [svelte's guide](https://svelte.dev/docs#Client-side_component_API) on how to interact with a svelte component. It is possible to integrate into any framework.
51 |
52 | DataGrid requires 2 properties to be passed in order to display data: `rows` and `columns`.
53 |
54 | `columns` is an array of objects containing at least 3 properties: `display`, `dataName`, and `width`. A svelte component can be specified in `headerComponent` and `cellComponent` if any custom cell behavior is required.
55 |
56 | ```
57 | [
58 | {
59 | display: 'Fruit Name', // What will be displayed as the column header
60 | dataName: 'fruitName', // The key of a row to get the column's data from
61 | width: 300, // Width, in pixels, of column
62 | disallowResize: true // Optional - disables resizing this column
63 | },
64 | {
65 | display: 'Color',
66 | dataName: 'fruitColor',
67 | width: 600,
68 | myExtraData: 12345
69 | }
70 | ]
71 | ```
72 |
73 |
74 | `rows` is an array of objects containing the data for each table row.
75 |
76 | ```
77 | [
78 | {
79 | fruitName: 'Apple',
80 | fruitColor: 'Red'
81 | },
82 | {
83 | fruitName: 'Blueberry',
84 | fruitColor: 'Blue'
85 | },
86 | {
87 | fruitName: 'Tomato',
88 | fruitColor: 'Red'
89 | }
90 | ]
91 |
92 | ```
93 |
94 | ## Editing Data
95 |
96 | Version 2 added early support for editing data. Due to the lack of using a keyed each block to render the rows, maintaining focus on controls as the user scrolls is a tad wonky. This will be resolved in a future version.
97 |
98 | Import the components:
99 | ```
100 | import TextboxCell from 'svelte-data-grid/src/textbox-cell.svelte';
101 | import SelectCell from 'svelte-data-grid/src/select-cell.svelte';
102 | import CheckboxCell from 'svelte-data-grid/src/checkbox-cell.svelte';
103 | ```
104 |
105 | ### Textbox Cell
106 | Textbox cell will debounce the user input, only recording changes after 400ms has elapsed since the user stops typing.
107 | ```
108 | {
109 | display: 'Name',
110 | dataName: 'name',
111 | width: 250,
112 | cellComponent: TextboxCell
113 | }
114 | ```
115 |
116 | ### Select Cell
117 |
118 | SelectCell requires that you provide an `options` array in your cell definition:
119 | ```
120 | {
121 | display: 'Eye Color',
122 | dataName: 'eyeColor',
123 | width: 75,
124 | cellComponent: SelectCell,
125 | options: [
126 | {
127 | display: 'Green',
128 | value: 'green'
129 | },
130 | {
131 | display: 'Blue',
132 | value: 'blue'
133 | },
134 | {
135 | display: 'Brown',
136 | value: 'brown'
137 | }
138 | ]
139 | }
140 | ```
141 |
142 | ### Checkbox Cell
143 | CheckboxCell will set the checked state of the checkbox depending on the boolean value of the row's data.
144 | ```
145 | {
146 | display: 'Active',
147 | dataName: 'isActive',
148 | width: 75,
149 | cellComponent: CheckboxCell
150 | }
151 | ```
152 |
153 |
154 | ## Custom Cell Components
155 |
156 | Need to customize how your data is displayed or build more complex functionality into your grid? Specify `cellComponent` in your definition in the `columns` property.
157 |
158 | Components will be passed the following properties:
159 | - `rowNumber` - The index of the row within `rows`
160 | - `row` - The entire row object from `rows`
161 | - `column` - The entire column object from `columns`
162 |
163 |
164 | MyCustomCell.svelte
165 | ```
166 |
174 |
175 |
176 | {row.data[column.dataName]}
177 |
178 | ```
179 |
180 | Import the component
181 | ```
182 | import MyCustomCell from './MyCustomCell.svelte';
183 | ```
184 |
185 | `columns` option:
186 | ```
187 | [
188 | {
189 | display: 'Fruit Color'
190 | dataName: 'fruitColor',
191 | width: 300,
192 | cellComponent: MyCustomCell
193 | }
194 | ]
195 | ```
196 |
197 | ## Custom Header Components
198 | Header components can also be specified in `columns` entries as the `headerComponent` property. Header components are only passed `column`, the column object from `columns`.
199 |
200 | ## Options:
201 |
202 | Svelte Data Grid provides a few options for controlling the grid and its interactions:
203 |
204 | - `rowHeight` - The row height in pixels *(Default: 24)*
205 | - `allowResizeFromTableCells` - Allow user to click and drag the right border of a table cell to resize the column *(Default: false)*
206 | - `allowResizeFromTableHeaders` - Allow user to click and drag the right border of a column header to resize the column *(Default: true)*
207 | - `allowColumnReordering` - Allow user to drag a column header to move that column to a new position *(Default: true)*
208 | - `allowColumnAffix` - Allow user to drag the double line to affix columns to the left side of the grid. See section below for caveats *(Default: true if the browser is chrome, false otherwise)*
209 | - `__extraRows` - If it is desired that the virtual list include more DOM rows than are visible, the number of extra rows can be specified in `__extraRows` *(Default: 0)*
210 | - `__columnHeaderResizeCaptureWidth` The width of the element, in pixels, placed at the right border of a column that triggers that column's resize. *(Default: 20)*
211 |
212 |
213 | ## Events:
214 | - `columnOrderUpdated` - Fired when the user has dragged a column to a new position. The updated column order can be accessed from `component.get().columns`
215 | - `columnWidthUpdated` - Fired when a user has resized a column. The updated column width can be accessed from `event.width` and the column index can be accessed from `event.idx`
216 |
217 | ## Column Affixing
218 |
219 | This feature works well on Chrome because Chrome's scroll events are not fired asynchronously from the scroll action. Firefox, Edge, and IE all fire scroll events *after* the overflow container has scroll on screen. This causes a jittery effect that we cannot easily work around while providing a cross-browser solution.
220 |
221 | To fix the jitteriness on Firefox, a setting in about:config can be changed to turn off APZ. Set `layers.async-pan-zoom.enabled` to `false`. Obviously this is not a solution we can reasonably ask users to try, so I'm looking for other solutions.
222 |
223 | ## Bugs? Suggestions?
224 | Feedback is always appreciated. Feel free to open a GitHub issue if DataGrid doesn't work the way you expect or want it to.
225 |
--------------------------------------------------------------------------------
/src/index.svelte:
--------------------------------------------------------------------------------
1 |
737 |
738 |
888 |
889 |
893 |