From cd6d8b9ee414ca3282af8dbb5ffed41b783b5a55 Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 21:55:22 +0800 Subject: [PATCH 1/8] chore: fix lint and format scripts --- .eslintignore | 11 +++++++++++ .prettierignore | 3 ++- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..5a3c2f8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +/coverage +pnpm-lock.yaml +/docs \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 00e4e70..5a3c2f8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,4 +7,5 @@ node_modules .env.* !.env.example /coverage -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +/docs \ No newline at end of file diff --git a/package.json b/package.json index d8db95f..e2eea7c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "prepare": "svelte-kit sync", "check": "svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --ignore-path .prettierignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", + "lint": "prettier --ignore-path .prettierignore --check --plugin-search-dir=. . && eslint --ignore-path .eslintignore .", "format": "prettier --ignore-path .prettierignore --write --plugin-search-dir=. .", "test": "jest --collect-coverage", "test:watch": "npm test -- --watch" From 56d34a56dc7b8f31a197a6244fd2a8949c4c891c Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 21:55:56 +0800 Subject: [PATCH 2/8] format --- README.md | 14 +++++++------- tsconfig.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ac08c54..47220e1 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,13 @@ Visit the [documentation](https://svelte-headless-table.bryanmylee.com/) for cod Svelte Headless Table is designed to work **seamlessly** with Svelte. If you love Svelte, you will love Svelte Headless Table. -* **Full TypeScript support** -* Compatible with **SvelteKit** and SSR -* Manage state with Svelte stores -* Headless and fully customizable -* Intuitive column-first declarative model -* Highly performant -* Feature-rich +- **Full TypeScript support** +- Compatible with **SvelteKit** and SSR +- Manage state with Svelte stores +- Headless and fully customizable +- Intuitive column-first declarative model +- Highly performant +- Feature-rich ## All the features you could ever need! diff --git a/tsconfig.json b/tsconfig.json index 2f9fcee..9782253 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "strict": true, "allowSyntheticDefaultImports": true, - "types": ["jest"], + "types": ["jest"] } } From c90a804be5a845c4bc46c217869fdbfa798f87cc Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 21:56:42 +0800 Subject: [PATCH 3/8] feat: allow customization of rowDataId --- src/lib/bodyRows.ts | 20 ++++++++++++++++---- src/lib/createTable.ts | 13 ++++++++++--- src/lib/createViewModel.ts | 9 +++++++-- src/lib/types/Label.ts | 4 ++-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/lib/bodyRows.ts b/src/lib/bodyRows.ts index 4f2e9fe..c0b3eae 100644 --- a/src/lib/bodyRows.ts +++ b/src/lib/bodyRows.ts @@ -61,6 +61,7 @@ export type DataBodyRowInit = Bod Item, Plugins > & { + dataId: string; original: Item; }; @@ -72,6 +73,7 @@ export class DataBodyRow extends original: Item; constructor({ id, + dataId, original, cells, cellForId, @@ -79,13 +81,14 @@ export class DataBodyRow extends parentRow, }: DataBodyRowInit) { super({ id, cells, cellForId, depth, parentRow }); - this.dataId = id; + this.dataId = dataId; this.original = original; } clone({ includeCells = false }: BodyRowCloneProps = {}): DataBodyRow { const clonedRow = new DataBodyRow({ id: this.id, + dataId: this.dataId, cellForId: this.cellForId, cells: this.cells, original: this.original, @@ -145,6 +148,10 @@ export class DisplayBodyRow exten } } +export interface BodyRowsOptions { + rowDataId?: (item: Item) => string; +} + /** * Converts an array of items into an array of table `BodyRow`s based on the column structure. * @param data The data to display. @@ -156,11 +163,14 @@ export const getBodyRows = ( /** * Flat columns before column transformations. */ - flatColumns: FlatColumn[] + flatColumns: FlatColumn[], + { rowDataId }: BodyRowsOptions = {} ): BodyRow[] => { const rows: BodyRow[] = data.map((item, idx) => { + const id = idx.toString(); return new DataBodyRow({ - id: idx.toString(), + id, + dataId: rowDataId !== undefined ? rowDataId(item) : id, original: item, cells: [], cellForId: {}, @@ -248,12 +258,14 @@ export const getColumnedBodyRows = ( subItems: Item[], - parentRow: BodyRow + parentRow: BodyRow, + { rowDataId }: BodyRowsOptions = {} ): BodyRow[] => { const subRows = subItems.map((item, idx) => { const id = `${parentRow.id}>${idx}`; return new DataBodyRow({ id, + dataId: rowDataId !== undefined ? rowDataId(item) : id, original: item, cells: [], cellForId: {}, diff --git a/src/lib/createTable.ts b/src/lib/createTable.ts index 8ed191d..6ddd573 100644 --- a/src/lib/createTable.ts +++ b/src/lib/createTable.ts @@ -15,7 +15,11 @@ import { import type { AnyPlugins } from './types/TablePlugin'; import type { ReadOrWritable } from './utils/store'; import { getDuplicates } from './utils/array'; -import { createViewModel, type TableViewModel } from './createViewModel'; +import { + createViewModel, + type CreateViewModelOptions, + type TableViewModel, +} from './createViewModel'; export class Table { data: ReadOrWritable; @@ -58,8 +62,11 @@ export class Table { return new DisplayColumn(def); } - createViewModel(columns: Column[]): TableViewModel { - return createViewModel(this, columns); + createViewModel( + columns: Column[], + options?: CreateViewModelOptions + ): TableViewModel { + return createViewModel(this, columns, options); } } diff --git a/src/lib/createViewModel.ts b/src/lib/createViewModel.ts index 25e995e..3acd07e 100644 --- a/src/lib/createViewModel.ts +++ b/src/lib/createViewModel.ts @@ -61,9 +61,14 @@ export interface TableState columns: Column[]; } +export interface CreateViewModelOptions { + rowDataId?: (item: Item) => string; +} + export const createViewModel = ( table: Table, - columns: Column[] + columns: Column[], + { rowDataId }: CreateViewModelOptions = {} ): TableViewModel => { const { data, plugins } = table; @@ -71,7 +76,7 @@ export const createViewModel = ( const flatColumns = readable($flatColumns); const originalRows = derived([data, flatColumns], ([$data, $flatColumns]) => { - return getBodyRows($data, $flatColumns); + return getBodyRows($data, $flatColumns, { rowDataId }); }); // _stores need to be defined first to pass into plugins for initialization. diff --git a/src/lib/types/Label.ts b/src/lib/types/Label.ts index 8e39d23..45a0bc5 100644 --- a/src/lib/types/Label.ts +++ b/src/lib/types/Label.ts @@ -1,5 +1,5 @@ import type { BodyRow } from '$lib/bodyRows'; -import type { FlatColumn } from '$lib/columns'; +import type { DataColumn, FlatColumn } from '$lib/columns'; import type { TableState } from '$lib/createViewModel'; import type { RenderConfig } from '$lib/render'; import type { AnyPlugins } from './TablePlugin'; @@ -12,7 +12,7 @@ export type DataLabel = { - column: FlatColumn; + column: DataColumn; row: BodyRow; // Value type does not infer correctly in Svelte // value: Value; From b4ed8f097ddc3580fee4a6f13d2c34189cf148a2 Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 21:57:43 +0800 Subject: [PATCH 4/8] bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 195c081..ae7faf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "svelte-headless-table", - "version": "0.11.1", + "version": "0.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "svelte-headless-table", - "version": "0.11.1", + "version": "0.12.0", "license": "MIT", "dependencies": { "svelte-keyed": "^1.1.5", diff --git a/package.json b/package.json index e2eea7c..f87322f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte-headless-table", - "version": "0.11.1", + "version": "0.12.0", "scripts": { "dev": "vite dev", "build": "vite build", From 4951cc5af0daa057549f55a7457b82267fbcf2ee Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 22:03:38 +0800 Subject: [PATCH 5/8] fix: pass the item index as part of dataId --- src/lib/bodyRows.ts | 6 +++--- src/lib/createViewModel.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/bodyRows.ts b/src/lib/bodyRows.ts index c0b3eae..3b6ac85 100644 --- a/src/lib/bodyRows.ts +++ b/src/lib/bodyRows.ts @@ -149,7 +149,7 @@ export class DisplayBodyRow exten } export interface BodyRowsOptions { - rowDataId?: (item: Item) => string; + rowDataId?: (item: Item, index: number) => string; } /** @@ -170,7 +170,7 @@ export const getBodyRows = ( const id = idx.toString(); return new DataBodyRow({ id, - dataId: rowDataId !== undefined ? rowDataId(item) : id, + dataId: rowDataId !== undefined ? rowDataId(item, idx) : id, original: item, cells: [], cellForId: {}, @@ -265,7 +265,7 @@ export const getSubRows = ( const id = `${parentRow.id}>${idx}`; return new DataBodyRow({ id, - dataId: rowDataId !== undefined ? rowDataId(item) : id, + dataId: rowDataId !== undefined ? rowDataId(item, idx) : id, original: item, cells: [], cellForId: {}, diff --git a/src/lib/createViewModel.ts b/src/lib/createViewModel.ts index 3acd07e..217456f 100644 --- a/src/lib/createViewModel.ts +++ b/src/lib/createViewModel.ts @@ -62,7 +62,7 @@ export interface TableState } export interface CreateViewModelOptions { - rowDataId?: (item: Item) => string; + rowDataId?: (item: Item, index: number) => string; } export const createViewModel = ( From ed24396784d234db8203d60f742beb69514d3e0e Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 22:06:18 +0800 Subject: [PATCH 6/8] fix: tests --- src/lib/bodyCells.DataBodyCell.render.test.ts | 1 + .../bodyCells.DisplayBodyCell.render.test.ts | 1 + src/lib/bodyRows.getBodyRows.test.ts | 32 ++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/lib/bodyCells.DataBodyCell.render.test.ts b/src/lib/bodyCells.DataBodyCell.render.test.ts index 1bee28d..667e816 100644 --- a/src/lib/bodyCells.DataBodyCell.render.test.ts +++ b/src/lib/bodyCells.DataBodyCell.render.test.ts @@ -23,6 +23,7 @@ const user: User = { const row = new DataBodyRow({ id: '0', + dataId: '0', original: user, cells: [], cellForId: {}, diff --git a/src/lib/bodyCells.DisplayBodyCell.render.test.ts b/src/lib/bodyCells.DisplayBodyCell.render.test.ts index c21601b..7c8142a 100644 --- a/src/lib/bodyCells.DisplayBodyCell.render.test.ts +++ b/src/lib/bodyCells.DisplayBodyCell.render.test.ts @@ -23,6 +23,7 @@ const user: User = { const row = new DataBodyRow({ id: '0', + dataId: '0', original: user, cells: [], cellForId: {}, diff --git a/src/lib/bodyRows.getBodyRows.test.ts b/src/lib/bodyRows.getBodyRows.test.ts index e64947b..9d63063 100644 --- a/src/lib/bodyRows.getBodyRows.test.ts +++ b/src/lib/bodyRows.getBodyRows.test.ts @@ -59,7 +59,13 @@ it('transforms empty data', () => { it('transforms data for data columns', () => { const actual = getBodyRows(data, dataColumns); - const row0 = new DataBodyRow({ id: '0', original: data[0], cells: [], cellForId: {} }); + const row0 = new DataBodyRow({ + id: '0', + dataId: '0', + original: data[0], + cells: [], + cellForId: {}, + }); const cells0 = [ new DataBodyCell({ row: row0, @@ -85,7 +91,13 @@ it('transforms data for data columns', () => { }; row0.cellForId = cellForId0; - const row1 = new DataBodyRow({ id: '1', original: data[1], cells: [], cellForId: {} }); + const row1 = new DataBodyRow({ + id: '1', + dataId: '1', + original: data[1], + cells: [], + cellForId: {}, + }); const cells1 = [ new DataBodyCell({ row: row1, @@ -161,7 +173,13 @@ it('transforms data with display columns', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const actual = getBodyRows(data, displayColumns as any); - const row0 = new DataBodyRow({ id: '0', original: data[0], cells: [], cellForId: {} }); + const row0 = new DataBodyRow({ + id: '0', + dataId: '0', + original: data[0], + cells: [], + cellForId: {}, + }); const cells0 = [ new DisplayBodyCell({ row: row0, @@ -181,7 +199,13 @@ it('transforms data with display columns', () => { }; row0.cellForId = cellForId0; - const row1 = new DataBodyRow({ id: '1', original: data[1], cells: [], cellForId: {} }); + const row1 = new DataBodyRow({ + id: '1', + dataId: '1', + original: data[1], + cells: [], + cellForId: {}, + }); const cells1 = [ new DisplayBodyCell({ row: row1, From 141409234fae4d6978b049db9bd139ed0b4c39cf Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 22:08:17 +0800 Subject: [PATCH 7/8] test: custom data id --- src/lib/bodyRows.getBodyRows.test.ts | 100 +++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/lib/bodyRows.getBodyRows.test.ts b/src/lib/bodyRows.getBodyRows.test.ts index 9d63063..495a4b5 100644 --- a/src/lib/bodyRows.getBodyRows.test.ts +++ b/src/lib/bodyRows.getBodyRows.test.ts @@ -154,6 +154,106 @@ it('transforms data for data columns', () => { }); }); +it('transforms data for data columns with custom rowDataId', () => { + const actual = getBodyRows(data, dataColumns, { + rowDataId: ({ firstName, lastName }) => `${firstName}-${lastName}`, + }); + + const row0 = new DataBodyRow({ + id: '0', + dataId: 'Adam-West', + original: data[0], + cells: [], + cellForId: {}, + }); + const cells0 = [ + new DataBodyCell({ + row: row0, + column: dataColumns[0], + value: 'Adam', + }), + new DataBodyCell({ + row: row0, + column: dataColumns[1], + value: 'West', + }), + new DataBodyCell({ + row: row0, + column: dataColumns[2], + value: 75, + }), + ]; + row0.cells = cells0; + const cellForId0 = { + firstName: cells0[0], + lastName: cells0[1], + progress: cells0[2], + }; + row0.cellForId = cellForId0; + + const row1 = new DataBodyRow({ + id: '1', + dataId: 'Becky-White', + original: data[1], + cells: [], + cellForId: {}, + }); + const cells1 = [ + new DataBodyCell({ + row: row1, + column: dataColumns[0], + value: 'Becky', + }), + new DataBodyCell({ + row: row1, + column: dataColumns[1], + value: 'White', + }), + new DataBodyCell({ + row: row1, + column: dataColumns[2], + value: 43, + }), + ]; + row1.cells = cells1; + const cellForId1 = { + firstName: cells1[0], + lastName: cells1[1], + progress: cells1[2], + }; + row1.cellForId = cellForId1; + + const expected: DataBodyRow[] = [row0, row1]; + + [0, 1].forEach((rowIdx) => { + const row = actual[rowIdx]; + expect(row).toBeInstanceOf(DataBodyRow); + if (!(row instanceof DataBodyRow)) { + throw new Error('Incorrect BodyRow subtype'); + } + expect(row.original).toStrictEqual(expected[rowIdx].original); + expect(row.cells.length).toStrictEqual(expected[rowIdx].cells.length); + actual[rowIdx].cells.forEach((_, colIdx) => { + const cell = actual[rowIdx].cells[colIdx]; + expect(cell).toBeInstanceOf(DataBodyCell); + const expectedCell = expected[rowIdx].cells[colIdx]; + if (!(cell instanceof DataBodyCell && expectedCell instanceof DataBodyCell)) { + throw new Error('Incorrect instance type'); + } + expect(cell.value).toStrictEqual(expectedCell.value); + }); + ['firstName', 'lastName', 'progress'].forEach((id) => { + const cell = actual[rowIdx].cellForId[id]; + expect(cell).toBeInstanceOf(DataBodyCell); + const expectedCell = expected[rowIdx].cellForId[id]; + if (!(cell instanceof DataBodyCell && expectedCell instanceof DataBodyCell)) { + throw new Error('Incorrect instance type'); + } + expect(cell.value).toStrictEqual(expectedCell.value); + }); + }); +}); + const checkedLabel = () => 'check'; const expandedLabel = () => 'expanded'; const displayColumns = [ From 39da9f954fd971d7e67798a6667147f373b1400f Mon Sep 17 00:00:00 2001 From: Bryan Lee Date: Fri, 29 Jul 2022 22:15:39 +0800 Subject: [PATCH 8/8] doc: createViewModel rowDataId --- .../docs/[...2]api/[...4]create-view-model.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/src/routes/docs/[...2]api/[...4]create-view-model.md b/docs/src/routes/docs/[...2]api/[...4]create-view-model.md index ef8ccdb..eace14a 100644 --- a/docs/src/routes/docs/[...2]api/[...4]create-view-model.md +++ b/docs/src/routes/docs/[...2]api/[...4]create-view-model.md @@ -17,10 +17,12 @@ sidebar_title: createViewModel --- -### `Table#createViewModel: (columns) => TableViewModel` +### `Table#createViewModel: (columns, options) => TableViewModel` `columns` is an array of `Column` instances returned from [`Table#createColumns`](./create-columns.md#table-createcolumns-columns-column). +`options` is an optional configuration object to configure the view model. + ```ts {4} const table = createTable(data); const columns = table.createColumns(...); @@ -32,3 +34,13 @@ const { tableBodyAttrs, } = viewModel; ``` + +#### `options.rowDataId?: (item, index) => string` + +Defines a custom [`dataId`](./body-row.md#dataid-string) for each row. + +:::admonition type="warning" +In the context of sub-rows, the `index` of the first sub-row is `0`. +::: + +_Defaults to the item index_. \ No newline at end of file