@tipolox/litgrid-core 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,7 @@ npm install @tipolox/litgrid-core
13
13
  - [Core engine API](https://github.com/tipolox/litgrid/blob/main/docs/api-reference.md#core-engine)
14
14
  - [Installation guide](https://github.com/tipolox/litgrid/blob/main/docs/installation.md)
15
15
  - [Repository](https://github.com/tipolox/litgrid)
16
+ - [v1.1.0 upgrade guide](https://github.com/tipolox/litgrid/blob/main/docs/installation.md#upgrading-to-v110)
16
17
  - [Live demo](https://tipolox.com/litgrid/demo)
17
18
 
18
19
  Licensed under the [MIT License](https://github.com/tipolox/litgrid/blob/main/LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tipolox/litgrid-core",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Framework-agnostic data engine for LitGrid.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,11 @@
15
15
  "./package.json": "./package.json"
16
16
  },
17
17
  "files": [
18
- "dist"
18
+ "dist",
19
+ "src/**/*.ts",
20
+ "!src/**/*.d.ts",
21
+ "!src/**/*.test.ts",
22
+ "!src/**/*.bench.ts"
19
23
  ],
20
24
  "keywords": [
21
25
  "data-grid",
@@ -0,0 +1,38 @@
1
+ import type { GridConfig, ResolvedGridConfig } from '../model/GridConfig.js'
2
+ import type {
3
+ GridSelection,
4
+ GridFilter,
5
+ GridPaginationState,
6
+ GridSortState,
7
+ SelectionIntent,
8
+ SortDirection
9
+ } from '../model/GridState.js'
10
+
11
+ export type GridEngine = {
12
+ setConfig: (config: GridConfig) => void
13
+ getConfig: () => ResolvedGridConfig
14
+ setData: (data: unknown[]) => void
15
+ getRows: () => unknown[]
16
+ setQuickSearch: (query: string) => void
17
+ clearQuickSearch: () => void
18
+ getQuickSearch: () => string
19
+ setFilter: (filter: GridFilter) => void
20
+ clearFilter: (columnKey?: string) => void
21
+ getFilters: () => GridFilter[]
22
+ setPage: (pageIndex: number) => void
23
+ setPageSize: (pageSize: number) => void
24
+ getPagination: () => GridPaginationState
25
+ getTotalRowCount: () => number
26
+ sortBy: (columnKey: string, direction: SortDirection) => void
27
+ clearSort: () => void
28
+ getSort: () => GridSortState
29
+ getRowCount: () => number
30
+ getVisibleRows: (startIndex: number, endIndex: number) => unknown[]
31
+ selectRow: (rowIndex: number, intent?: SelectionIntent) => void
32
+ selectAllRows: () => void
33
+ selectCell: (rowIndex: number, columnKey: string) => void
34
+ clearSelection: () => void
35
+ getSelection: () => GridSelection
36
+ isRowSelected: (rowIndex: number) => boolean
37
+ isCellSelected: (rowIndex: number, columnKey: string) => boolean
38
+ }
@@ -0,0 +1,217 @@
1
+ import type { GridConfig } from '../model/GridConfig.js'
2
+ import { normalizeConfig } from '../model/GridConfig.js'
3
+ import type { GridFilter, GridState, SelectionIntent, SortDirection } from '../model/GridState.js'
4
+ import {
5
+ createEmptySelection,
6
+ getCellKey,
7
+ getNextCellSelection,
8
+ getNextRowSelection
9
+ } from '../features/selection/selection.js'
10
+ import { applyDataTransforms } from '../features/transforms/applyDataTransforms.js'
11
+ import { clampPageIndex, createPaginationState, paginateData } from '../features/pagination/pagination.js'
12
+ import type { GridEngine } from './GridEngine.js'
13
+
14
+ export function createGridEngine(config: GridConfig = {}): GridEngine {
15
+ const initialConfig = normalizeConfig(config)
16
+ let state: GridState = {
17
+ config: initialConfig,
18
+ originalData: [],
19
+ transformedData: [],
20
+ data: [],
21
+ sort: { columnKey: null, direction: null },
22
+ quickSearchQuery: '',
23
+ filters: [],
24
+ pagination: createPaginationState(
25
+ initialConfig.pagination.enabled,
26
+ initialConfig.pagination.pageSize,
27
+ initialConfig.pagination.pageIndex
28
+ ),
29
+ selection: createEmptySelection(initialConfig.selection.mode)
30
+ }
31
+
32
+ function refreshData() {
33
+ state.transformedData = applyDataTransforms(
34
+ state.originalData,
35
+ state.quickSearchQuery,
36
+ state.filters,
37
+ state.sort
38
+ )
39
+ const pageCount = Math.ceil(state.transformedData.length / state.pagination.pageSize)
40
+ state.pagination = {
41
+ ...state.pagination,
42
+ totalRows: state.transformedData.length,
43
+ pageCount,
44
+ pageIndex: clampPageIndex(state.pagination.pageIndex, pageCount)
45
+ }
46
+ state.data = paginateData(state.transformedData, state.pagination)
47
+ }
48
+
49
+ function resetPage() {
50
+ state.pagination = { ...state.pagination, pageIndex: 0 }
51
+ }
52
+
53
+ return {
54
+ setConfig(config: GridConfig) {
55
+ const nextConfig = normalizeConfig(config)
56
+
57
+ state.config = nextConfig
58
+ state.pagination = createPaginationState(
59
+ nextConfig.pagination.enabled,
60
+ nextConfig.pagination.pageSize,
61
+ nextConfig.pagination.pageIndex,
62
+ state.transformedData.length
63
+ )
64
+ refreshData()
65
+
66
+ if (state.selection.mode !== nextConfig.selection.mode) {
67
+ state.selection = createEmptySelection(nextConfig.selection.mode)
68
+ }
69
+ },
70
+ getConfig() {
71
+ return state.config
72
+ },
73
+ setData(data: unknown[]) {
74
+ state.originalData = data ?? []
75
+ resetPage()
76
+ refreshData()
77
+ },
78
+ getRows() {
79
+ return state.data
80
+ },
81
+ setQuickSearch(query: string) {
82
+ state.quickSearchQuery = typeof query === 'string' ? query : ''
83
+ resetPage()
84
+ refreshData()
85
+ },
86
+ clearQuickSearch() {
87
+ state.quickSearchQuery = ''
88
+ resetPage()
89
+ refreshData()
90
+ },
91
+ getQuickSearch() {
92
+ return state.quickSearchQuery
93
+ },
94
+ setFilter(filter: GridFilter) {
95
+ state.filters = [
96
+ ...state.filters.filter((item) => item.columnKey !== filter.columnKey),
97
+ { ...filter }
98
+ ]
99
+ resetPage()
100
+ refreshData()
101
+ },
102
+ clearFilter(columnKey?: string) {
103
+ state.filters = columnKey
104
+ ? state.filters.filter((item) => item.columnKey !== columnKey)
105
+ : []
106
+ resetPage()
107
+ refreshData()
108
+ },
109
+ getFilters() {
110
+ return state.filters.slice()
111
+ },
112
+ setPage(pageIndex: number) {
113
+ const nextPageIndex = Number.isFinite(pageIndex) ? Math.max(0, Math.floor(pageIndex)) : 0
114
+ state.pagination = {
115
+ ...state.pagination,
116
+ pageIndex: clampPageIndex(nextPageIndex, state.pagination.pageCount)
117
+ }
118
+ state.data = paginateData(state.transformedData, state.pagination)
119
+ },
120
+ setPageSize(pageSize: number) {
121
+ if (!Number.isFinite(pageSize) || pageSize <= 0) {
122
+ return
123
+ }
124
+
125
+ state.pagination = {
126
+ ...state.pagination,
127
+ pageSize: Math.max(1, Math.floor(pageSize)),
128
+ pageIndex: 0
129
+ }
130
+ refreshData()
131
+ },
132
+ getPagination() {
133
+ return { ...state.pagination }
134
+ },
135
+ getTotalRowCount() {
136
+ return state.pagination.totalRows
137
+ },
138
+ sortBy(columnKey: string, direction: SortDirection) {
139
+ state.sort = { columnKey, direction }
140
+ resetPage()
141
+ refreshData()
142
+ },
143
+ clearSort() {
144
+ state.sort = { columnKey: null, direction: null }
145
+ resetPage()
146
+ refreshData()
147
+ },
148
+ getSort() {
149
+ return state.sort
150
+ },
151
+ getRowCount() {
152
+ return state.data.length
153
+ },
154
+ getVisibleRows(startIndex: number, endIndex: number) {
155
+ return state.data.slice(startIndex, endIndex)
156
+ },
157
+ selectRow(rowIndex: number, intent: SelectionIntent = 'replace') {
158
+ const nextSelection = getNextRowSelection(
159
+ state.selection,
160
+ state.config.selection.mode,
161
+ rowIndex,
162
+ intent
163
+ )
164
+
165
+ if (nextSelection) {
166
+ state.selection = nextSelection
167
+ }
168
+ },
169
+ selectAllRows() {
170
+ if (state.config.selection.mode !== 'multi-row') {
171
+ return
172
+ }
173
+
174
+ const rowIndexes = new Set<number>()
175
+ for (let rowIndex = 0; rowIndex < state.data.length; rowIndex += 1) {
176
+ rowIndexes.add(rowIndex)
177
+ }
178
+
179
+ state.selection = {
180
+ ...createEmptySelection('multi-row'),
181
+ rowIndex: state.data.length > 0 ? state.data.length - 1 : null,
182
+ anchorRowIndex: state.data.length > 0 ? 0 : null,
183
+ rowIndexes
184
+ }
185
+ },
186
+ selectCell(rowIndex: number, columnKey: string) {
187
+ const nextSelection = getNextCellSelection(
188
+ state.selection,
189
+ state.config.selection.mode,
190
+ rowIndex,
191
+ columnKey
192
+ )
193
+
194
+ if (nextSelection) {
195
+ state.selection = nextSelection
196
+ }
197
+ },
198
+ clearSelection() {
199
+ state.selection = createEmptySelection(state.config.selection.mode)
200
+ },
201
+ getSelection() {
202
+ return state.selection
203
+ },
204
+ isRowSelected(rowIndex: number) {
205
+ return (
206
+ (state.selection.mode === 'row' || state.selection.mode === 'multi-row') &&
207
+ state.selection.rowIndexes.has(rowIndex)
208
+ )
209
+ },
210
+ isCellSelected(rowIndex: number, columnKey: string) {
211
+ return (
212
+ (state.selection.mode === 'cell' || state.selection.mode === 'multi-cell') &&
213
+ state.selection.cells.has(getCellKey(rowIndex, columnKey))
214
+ )
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,63 @@
1
+ import type { GridFilter } from '../../model/GridState.js'
2
+
3
+ function getRowFilterValue(row: unknown, columnKey: string) {
4
+ if (row && typeof row === 'object' && !Array.isArray(row) && columnKey in row) {
5
+ return (row as Record<string, unknown>)[columnKey]
6
+ }
7
+
8
+ return row
9
+ }
10
+
11
+ function isEmpty(value: unknown) {
12
+ return value === null || value === undefined || value === ''
13
+ }
14
+
15
+ function normalizeText(value: unknown) {
16
+ return String(value ?? '').toLocaleLowerCase()
17
+ }
18
+
19
+ function compareValues(left: unknown, right: unknown) {
20
+ if (typeof left === 'number') {
21
+ const numericRight = typeof right === 'number' ? right : Number(right)
22
+ if (!Number.isNaN(numericRight)) {
23
+ return left - numericRight
24
+ }
25
+ }
26
+
27
+ return normalizeText(left).localeCompare(normalizeText(right))
28
+ }
29
+
30
+ function matchesFilter(value: unknown, filter: GridFilter) {
31
+ switch (filter.operator) {
32
+ case 'isEmpty':
33
+ return isEmpty(value)
34
+ case 'isNotEmpty':
35
+ return !isEmpty(value)
36
+ case 'contains':
37
+ return normalizeText(value).includes(normalizeText(filter.value))
38
+ case 'equals':
39
+ return compareValues(value, filter.value) === 0
40
+ case 'startsWith':
41
+ return normalizeText(value).startsWith(normalizeText(filter.value))
42
+ case 'endsWith':
43
+ return normalizeText(value).endsWith(normalizeText(filter.value))
44
+ case 'greaterThan':
45
+ return compareValues(value, filter.value) > 0
46
+ case 'greaterThanOrEqual':
47
+ return compareValues(value, filter.value) >= 0
48
+ case 'lessThan':
49
+ return compareValues(value, filter.value) < 0
50
+ case 'lessThanOrEqual':
51
+ return compareValues(value, filter.value) <= 0
52
+ }
53
+ }
54
+
55
+ export function applyFilterData(data: unknown[], filters: GridFilter[]) {
56
+ if (filters.length === 0) {
57
+ return data.slice()
58
+ }
59
+
60
+ return data.filter((row) =>
61
+ filters.every((filter) => matchesFilter(getRowFilterValue(row, filter.columnKey), filter))
62
+ )
63
+ }
@@ -0,0 +1,35 @@
1
+ import type { GridPaginationState } from '../../model/GridState.js'
2
+
3
+ export function getPageCount(totalRows: number, pageSize: number) {
4
+ return totalRows === 0 ? 0 : Math.ceil(totalRows / pageSize)
5
+ }
6
+
7
+ export function clampPageIndex(pageIndex: number, pageCount: number) {
8
+ return pageCount === 0 ? 0 : Math.min(Math.max(0, pageIndex), pageCount - 1)
9
+ }
10
+
11
+ export function createPaginationState(
12
+ enabled: boolean,
13
+ pageSize: number,
14
+ pageIndex = 0,
15
+ totalRows = 0
16
+ ): GridPaginationState {
17
+ const pageCount = getPageCount(totalRows, pageSize)
18
+
19
+ return {
20
+ enabled,
21
+ pageSize,
22
+ pageIndex: clampPageIndex(pageIndex, pageCount),
23
+ totalRows,
24
+ pageCount
25
+ }
26
+ }
27
+
28
+ export function paginateData(data: unknown[], pagination: GridPaginationState) {
29
+ if (!pagination.enabled) {
30
+ return data
31
+ }
32
+
33
+ const start = pagination.pageIndex * pagination.pageSize
34
+ return data.slice(start, start + pagination.pageSize)
35
+ }
@@ -0,0 +1,31 @@
1
+ function normalizeQuery(query: string) {
2
+ return query.trim().toLocaleLowerCase()
3
+ }
4
+
5
+ function matchesValue(value: unknown, query: string) {
6
+ return (
7
+ (typeof value === 'string' ||
8
+ typeof value === 'number' ||
9
+ typeof value === 'boolean' ||
10
+ typeof value === 'bigint') &&
11
+ String(value).toLocaleLowerCase().includes(query)
12
+ )
13
+ }
14
+
15
+ function matchesRow(row: unknown, query: string) {
16
+ if (row && typeof row === 'object' && !Array.isArray(row)) {
17
+ return Object.values(row as Record<string, unknown>).some((value) => matchesValue(value, query))
18
+ }
19
+
20
+ return matchesValue(row, query)
21
+ }
22
+
23
+ export function applyQuickSearch(data: unknown[], query: string) {
24
+ const normalizedQuery = normalizeQuery(query)
25
+
26
+ if (!normalizedQuery) {
27
+ return data.slice()
28
+ }
29
+
30
+ return data.filter((row) => matchesRow(row, normalizedQuery))
31
+ }
@@ -0,0 +1,111 @@
1
+ import type { SelectionMode } from '../../model/GridConfig.js'
2
+ import type { GridSelection, SelectionIntent } from '../../model/GridState.js'
3
+ import { getRowRange } from '../../utils/range.js'
4
+
5
+ export function createEmptySelection(mode: SelectionMode): GridSelection {
6
+ return {
7
+ mode,
8
+ rowIndex: null,
9
+ columnKey: null,
10
+ anchorRowIndex: null,
11
+ rowIndexes: new Set(),
12
+ cells: new Set()
13
+ }
14
+ }
15
+
16
+ export function getCellKey(rowIndex: number, columnKey: string) {
17
+ return `${rowIndex}:${columnKey}`
18
+ }
19
+
20
+ export function getNextRowSelection(
21
+ selection: GridSelection,
22
+ mode: SelectionMode,
23
+ rowIndex: number,
24
+ intent: SelectionIntent
25
+ ): GridSelection | null {
26
+ if (mode === 'row') {
27
+ return {
28
+ ...createEmptySelection('row'),
29
+ rowIndex,
30
+ anchorRowIndex: rowIndex,
31
+ rowIndexes: new Set([rowIndex])
32
+ }
33
+ }
34
+
35
+ if (mode !== 'multi-row') {
36
+ return null
37
+ }
38
+
39
+ if (intent === 'range') {
40
+ const anchorRowIndex = selection.anchorRowIndex ?? rowIndex
41
+
42
+ return {
43
+ ...createEmptySelection('multi-row'),
44
+ rowIndex,
45
+ anchorRowIndex,
46
+ rowIndexes: getRowRange(anchorRowIndex, rowIndex)
47
+ }
48
+ }
49
+
50
+ if (intent === 'toggle') {
51
+ const rowIndexes = new Set(selection.rowIndexes)
52
+
53
+ if (rowIndexes.has(rowIndex)) {
54
+ rowIndexes.delete(rowIndex)
55
+ } else {
56
+ rowIndexes.add(rowIndex)
57
+ }
58
+
59
+ return {
60
+ ...createEmptySelection('multi-row'),
61
+ rowIndex: rowIndexes.has(rowIndex) ? rowIndex : null,
62
+ anchorRowIndex: rowIndex,
63
+ rowIndexes
64
+ }
65
+ }
66
+
67
+ return {
68
+ ...createEmptySelection('multi-row'),
69
+ rowIndex,
70
+ anchorRowIndex: rowIndex,
71
+ rowIndexes: new Set([rowIndex])
72
+ }
73
+ }
74
+
75
+ export function getNextCellSelection(
76
+ selection: GridSelection,
77
+ mode: SelectionMode,
78
+ rowIndex: number,
79
+ columnKey: string
80
+ ): GridSelection | null {
81
+ if (mode === 'cell') {
82
+ return {
83
+ ...createEmptySelection('cell'),
84
+ rowIndex,
85
+ columnKey,
86
+ cells: new Set([getCellKey(rowIndex, columnKey)])
87
+ }
88
+ }
89
+
90
+ if (mode !== 'multi-cell') {
91
+ return null
92
+ }
93
+
94
+ const cellKey = getCellKey(rowIndex, columnKey)
95
+ const cells = new Set(selection.cells)
96
+
97
+ if (cells.has(cellKey)) {
98
+ cells.delete(cellKey)
99
+ } else {
100
+ cells.add(cellKey)
101
+ }
102
+
103
+ return {
104
+ ...createEmptySelection('multi-cell'),
105
+ rowIndex: cells.has(cellKey) ? rowIndex : null,
106
+ columnKey: cells.has(cellKey) ? columnKey : null,
107
+ cells
108
+ }
109
+ }
110
+
111
+ export { getRowRange }
@@ -0,0 +1,46 @@
1
+ import type { GridSortState } from '../../model/GridState.js'
2
+
3
+ function getRowSortValue(row: unknown, columnKey: string) {
4
+ if (row && typeof row === 'object' && !Array.isArray(row) && columnKey in row) {
5
+ return (row as Record<string, unknown>)[columnKey]
6
+ }
7
+
8
+ return row
9
+ }
10
+
11
+ function normalizeSortValue(value: unknown) {
12
+ if (value === null || value === undefined) {
13
+ return ''
14
+ }
15
+
16
+ if (typeof value === 'number') {
17
+ return value
18
+ }
19
+
20
+ return String(value).toLowerCase()
21
+ }
22
+
23
+ function compareValues(left: unknown, right: unknown) {
24
+ const normalizedLeft = normalizeSortValue(left)
25
+ const normalizedRight = normalizeSortValue(right)
26
+
27
+ if (typeof normalizedLeft === 'number' && typeof normalizedRight === 'number') {
28
+ return normalizedLeft - normalizedRight
29
+ }
30
+
31
+ return String(normalizedLeft).localeCompare(String(normalizedRight))
32
+ }
33
+
34
+ export function applySortData(data: unknown[], sort: GridSortState) {
35
+ if (!sort.columnKey || !sort.direction) {
36
+ return data.slice()
37
+ }
38
+
39
+ return data.slice().sort((a, b) => {
40
+ const left = getRowSortValue(a, sort.columnKey!)
41
+ const right = getRowSortValue(b, sort.columnKey!)
42
+ const comparison = compareValues(left, right)
43
+
44
+ return sort.direction === 'asc' ? comparison : -comparison
45
+ })
46
+ }
@@ -0,0 +1,13 @@
1
+ import type { GridFilter, GridSortState } from '../../model/GridState.js'
2
+ import { applyFilterData } from '../filtering/filtering.js'
3
+ import { applyQuickSearch } from '../quick-search/quickSearch.js'
4
+ import { applySortData } from '../sorting/sorting.js'
5
+
6
+ export function applyDataTransforms(
7
+ data: unknown[],
8
+ quickSearchQuery: string,
9
+ filters: GridFilter[],
10
+ sort: GridSortState
11
+ ) {
12
+ return applySortData(applyFilterData(applyQuickSearch(data, quickSearchQuery), filters), sort)
13
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { createGridEngine } from './core/createGridEngine.js'
2
+ export type { GridEngine } from './core/GridEngine.js'
3
+ export type { GridConfig, GridPaginationConfig, ResolvedGridConfig, SelectionMode } from './model/GridConfig.js'
4
+ export type {
5
+ FilterOperator,
6
+ GridFilter,
7
+ GridPaginationState,
8
+ GridSelection,
9
+ SelectionIntent,
10
+ SortDirection
11
+ } from './model/GridState.js'
@@ -0,0 +1,61 @@
1
+ export type SelectionMode = 'none' | 'row' | 'multi-row' | 'cell' | 'multi-cell'
2
+
3
+ export type GridPaginationConfig = {
4
+ enabled?: boolean
5
+ pageSize?: number
6
+ pageIndex?: number
7
+ }
8
+
9
+ export type GridConfig = {
10
+ selection?: {
11
+ mode?: SelectionMode
12
+ checkboxes?: boolean
13
+ }
14
+ rowHeader?: {
15
+ enabled?: boolean
16
+ width?: number
17
+ }
18
+ pagination?: GridPaginationConfig
19
+ }
20
+
21
+ export type ResolvedGridConfig = {
22
+ selection: {
23
+ mode: SelectionMode
24
+ checkboxes: boolean
25
+ }
26
+ rowHeader: {
27
+ enabled: boolean
28
+ width: number
29
+ }
30
+ pagination: {
31
+ enabled: boolean
32
+ pageSize: number
33
+ pageIndex: number
34
+ }
35
+ }
36
+
37
+ function normalizePageSize(value: number | undefined) {
38
+ return Number.isFinite(value) && value! > 0 ? Math.max(1, Math.floor(value!)) : 50
39
+ }
40
+
41
+ function normalizePageIndex(value: number | undefined) {
42
+ return Number.isFinite(value) && value! >= 0 ? Math.floor(value!) : 0
43
+ }
44
+
45
+ export function normalizeConfig(config: GridConfig = {}): ResolvedGridConfig {
46
+ return {
47
+ selection: {
48
+ mode: config.selection?.mode ?? 'none',
49
+ checkboxes: config.selection?.checkboxes ?? false
50
+ },
51
+ rowHeader: {
52
+ enabled: config.rowHeader?.enabled ?? false,
53
+ width: config.rowHeader?.width ?? 56
54
+ },
55
+ pagination: {
56
+ enabled: config.pagination?.enabled ?? false,
57
+ pageSize: normalizePageSize(config.pagination?.pageSize),
58
+ pageIndex: normalizePageIndex(config.pagination?.pageIndex)
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,57 @@
1
+ import type { ResolvedGridConfig, SelectionMode } from './GridConfig.js'
2
+
3
+ export type SelectionIntent = 'replace' | 'toggle' | 'range'
4
+
5
+ export type GridSelection = {
6
+ mode: SelectionMode
7
+ rowIndex: number | null
8
+ columnKey: string | null
9
+ anchorRowIndex: number | null
10
+ rowIndexes: Set<number>
11
+ cells: Set<string>
12
+ }
13
+
14
+ export type SortDirection = 'asc' | 'desc'
15
+
16
+ export type GridSortState = {
17
+ columnKey: string | null
18
+ direction: SortDirection | null
19
+ }
20
+
21
+ export type FilterOperator =
22
+ | 'contains'
23
+ | 'equals'
24
+ | 'startsWith'
25
+ | 'endsWith'
26
+ | 'isEmpty'
27
+ | 'isNotEmpty'
28
+ | 'greaterThan'
29
+ | 'greaterThanOrEqual'
30
+ | 'lessThan'
31
+ | 'lessThanOrEqual'
32
+
33
+ export type GridFilter = {
34
+ columnKey: string
35
+ operator: FilterOperator
36
+ value?: unknown
37
+ }
38
+
39
+ export type GridPaginationState = {
40
+ enabled: boolean
41
+ pageIndex: number
42
+ pageSize: number
43
+ totalRows: number
44
+ pageCount: number
45
+ }
46
+
47
+ export type GridState = {
48
+ config: ResolvedGridConfig
49
+ originalData: unknown[]
50
+ transformedData: unknown[]
51
+ data: unknown[]
52
+ sort: GridSortState
53
+ quickSearchQuery: string
54
+ filters: GridFilter[]
55
+ pagination: GridPaginationState
56
+ selection: GridSelection
57
+ }
@@ -0,0 +1,11 @@
1
+ export function getRowRange(startIndex: number, endIndex: number) {
2
+ const firstIndex = Math.min(startIndex, endIndex)
3
+ const lastIndex = Math.max(startIndex, endIndex)
4
+ const rowIndexes = new Set<number>()
5
+
6
+ for (let index = firstIndex; index <= lastIndex; index += 1) {
7
+ rowIndexes.add(index)
8
+ }
9
+
10
+ return rowIndexes
11
+ }