@tipolox/litgrid-react 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
@@ -35,6 +35,7 @@ Render `UsersGrid` in your React application. `height` defines the viewport used
35
35
 
36
36
  - [Live demo](https://tipolox.com/litgrid/demo)
37
37
  - [Repository](https://github.com/tipolox/litgrid)
38
+ - [v1.1.0 upgrade guide](https://github.com/tipolox/litgrid/blob/main/docs/installation.md#upgrading-to-v110)
38
39
  - [React guide](https://github.com/tipolox/litgrid/blob/main/docs/react-guide.md)
39
40
  - [API reference](https://github.com/tipolox/litgrid/blob/main/docs/api-reference.md)
40
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tipolox/litgrid-react",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "React integration 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,tsx}",
20
+ "!src/**/*.d.ts",
21
+ "!src/**/*.test.{ts,tsx}",
22
+ "!src/**/*.bench.{ts,tsx}"
19
23
  ],
20
24
  "keywords": [
21
25
  "data-grid",
@@ -38,7 +42,7 @@
38
42
  "access": "public"
39
43
  },
40
44
  "dependencies": {
41
- "@tipolox/litgrid-web": "^1.0.3"
45
+ "@tipolox/litgrid-web": "^1.1.0"
42
46
  },
43
47
  "peerDependencies": {
44
48
  "react": "^19.2.5",
@@ -0,0 +1,252 @@
1
+ import '@tipolox/litgrid-web'
2
+ import type {
3
+ BestFitColumnWidth,
4
+ ColumnReorderDetail,
5
+ ColumnStateChangeDetail,
6
+ ColumnVisibilityChangeDetail,
7
+ DataGridElement,
8
+ GridColumn,
9
+ GridColumnState,
10
+ GridConfig,
11
+ GridFilter,
12
+ GridPaginationState,
13
+ GridSelection,
14
+ GridTheme,
15
+ SelectionIntent
16
+ } from '@tipolox/litgrid-web'
17
+ import {
18
+ forwardRef,
19
+ useEffect,
20
+ useImperativeHandle,
21
+ useRef
22
+ } from 'react'
23
+ import { syncGridInputs } from './gridBindings.js'
24
+
25
+ export type DataGridProps = {
26
+ data: unknown[]
27
+ columns?: GridColumn[]
28
+ config?: GridConfig
29
+ theme?: GridTheme
30
+ ariaLabel?: string
31
+ ariaDescription?: string
32
+ screenReaderAnnouncements?: boolean
33
+ height?: number
34
+ rowHeight?: number
35
+ overscan?: number
36
+ columnOverscan?: number
37
+ bestFitSampleSize?: number
38
+ quickSearchDebounceThreshold?: number
39
+ quickSearchDebounceMs?: number
40
+ columnStateStorageKey?: string
41
+ onColumnStateChange?: (detail: ColumnStateChangeDetail) => void
42
+ onColumnReorder?: (detail: ColumnReorderDetail) => void
43
+ onColumnVisibilityChange?: (detail: ColumnVisibilityChangeDetail) => void
44
+ }
45
+
46
+ export type DataGridRef = {
47
+ readonly gridElement: DataGridElement | null
48
+ getRowHeight(rowIndex: number): number
49
+ getColumnWidth(column: GridColumn): number
50
+ setRowHeight(rowIndex: number, height: number): void
51
+ resetRowHeight(rowIndex: number): void
52
+ resetAllRowHeights(): void
53
+ setColumnWidth(columnKey: string, width: number): void
54
+ resetColumnWidth(columnKey: string): void
55
+ resetAllColumnWidths(): void
56
+ moveColumn(columnKey: string, targetIndex: number): void
57
+ getColumnOrder(): string[]
58
+ setColumnOrder(columnKeys: string[]): void
59
+ resetColumnOrder(): void
60
+ setColumnVisible(columnKey: string, visible: boolean): void
61
+ isColumnVisible(columnKey: string): boolean
62
+ getVisibleColumnKeys(): string[]
63
+ resetColumnVisibility(): void
64
+ getColumnState(): GridColumnState
65
+ setColumnState(state: GridColumnState): void
66
+ resetColumnState(): void
67
+ bestFitColumn(columnKey: string): number | null
68
+ bestFitAllColumns(): BestFitColumnWidth[]
69
+ copySelectedCells(): Promise<boolean>
70
+ copySelectedRows(): Promise<boolean>
71
+ setQuickSearch(query: string): void
72
+ clearQuickSearch(): void
73
+ getQuickSearch(): string
74
+ setFilter(filter: GridFilter): void
75
+ clearFilter(columnKey?: string): void
76
+ getFilters(): GridFilter[]
77
+ setPage(pageIndex: number): void
78
+ setPageSize(pageSize: number): void
79
+ getPagination(): GridPaginationState
80
+ getTotalRowCount(): number
81
+ selectRow(rowIndex: number, intent?: SelectionIntent): void
82
+ selectAllRows(): void
83
+ clearSelection(): void
84
+ getSelection(): GridSelection
85
+ isRowSelected(rowIndex: number): boolean
86
+ isCellSelected(rowIndex: number, columnKey: string): boolean
87
+ }
88
+
89
+ export type DataGridInstance = DataGridRef
90
+
91
+ const GRID_UNAVAILABLE_MESSAGE = 'LitGrid is not available before the React component is mounted.'
92
+
93
+ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid(
94
+ {
95
+ data,
96
+ columns = [],
97
+ config = {},
98
+ theme = 'light',
99
+ ariaLabel = 'Data grid',
100
+ ariaDescription = '',
101
+ screenReaderAnnouncements = true,
102
+ height = 320,
103
+ rowHeight = 36,
104
+ overscan = 4,
105
+ columnOverscan = 2,
106
+ bestFitSampleSize = 10,
107
+ quickSearchDebounceThreshold = 10000,
108
+ quickSearchDebounceMs = 150,
109
+ columnStateStorageKey,
110
+ onColumnStateChange,
111
+ onColumnReorder,
112
+ onColumnVisibilityChange
113
+ },
114
+ forwardedRef
115
+ ) {
116
+ const gridRef = useRef<DataGridElement | null>(null)
117
+
118
+ const requireGridElement = (): DataGridElement => {
119
+ if (!gridRef.current) throw new Error(GRID_UNAVAILABLE_MESSAGE)
120
+ return gridRef.current
121
+ }
122
+
123
+ useImperativeHandle(
124
+ forwardedRef,
125
+ () => ({
126
+ get gridElement() {
127
+ return gridRef.current
128
+ },
129
+ getRowHeight: (rowIndex: number) => requireGridElement().getRowHeight(rowIndex),
130
+ getColumnWidth: (column: GridColumn) => requireGridElement().getColumnWidth(column),
131
+ setRowHeight: (rowIndex: number, heightVal: number) => requireGridElement().setRowHeight(rowIndex, heightVal),
132
+ resetRowHeight: (rowIndex: number) => requireGridElement().resetRowHeight(rowIndex),
133
+ resetAllRowHeights: () => requireGridElement().resetAllRowHeights(),
134
+ setColumnWidth: (columnKey: string, width: number) => requireGridElement().setColumnWidth(columnKey, width),
135
+ resetColumnWidth: (columnKey: string) => requireGridElement().resetColumnWidth(columnKey),
136
+ resetAllColumnWidths: () => requireGridElement().resetAllColumnWidths(),
137
+ moveColumn: (columnKey: string, targetIndex: number) => requireGridElement().moveColumn(columnKey, targetIndex),
138
+ getColumnOrder: () => requireGridElement().getColumnOrder(),
139
+ setColumnOrder: (columnKeys: string[]) => requireGridElement().setColumnOrder(columnKeys),
140
+ resetColumnOrder: () => requireGridElement().resetColumnOrder(),
141
+ setColumnVisible: (columnKey: string, visible: boolean) => requireGridElement().setColumnVisible(columnKey, visible),
142
+ isColumnVisible: (columnKey: string) => requireGridElement().isColumnVisible(columnKey),
143
+ getVisibleColumnKeys: () => requireGridElement().getVisibleColumnKeys(),
144
+ resetColumnVisibility: () => requireGridElement().resetColumnVisibility(),
145
+ getColumnState: () => requireGridElement().getColumnState(),
146
+ setColumnState: (state: GridColumnState) => requireGridElement().setColumnState(state),
147
+ resetColumnState: () => requireGridElement().resetColumnState(),
148
+ bestFitColumn: (columnKey: string) => requireGridElement().bestFitColumn(columnKey),
149
+ bestFitAllColumns: () => requireGridElement().bestFitAllColumns(),
150
+ copySelectedCells: () => requireGridElement().copySelectedCells(),
151
+ copySelectedRows: () => requireGridElement().copySelectedRows(),
152
+ setQuickSearch: (query: string) => requireGridElement().setQuickSearch(query),
153
+ clearQuickSearch: () => requireGridElement().clearQuickSearch(),
154
+ getQuickSearch: () => requireGridElement().getQuickSearch(),
155
+ setFilter: (filter: GridFilter) => requireGridElement().setFilter(filter),
156
+ clearFilter: (columnKey?: string) => requireGridElement().clearFilter(columnKey),
157
+ getFilters: () => requireGridElement().getFilters(),
158
+ setPage: (pageIndex: number) => requireGridElement().setPage(pageIndex),
159
+ setPageSize: (pageSize: number) => requireGridElement().setPageSize(pageSize),
160
+ getPagination: () => requireGridElement().getPagination(),
161
+ getTotalRowCount: () => requireGridElement().getTotalRowCount(),
162
+ selectRow: (rowIndex: number, intent?: SelectionIntent) => requireGridElement().selectRow(rowIndex, intent),
163
+ selectAllRows: () => requireGridElement().selectAllRows(),
164
+ clearSelection: () => requireGridElement().clearSelection(),
165
+ getSelection: () => requireGridElement().getSelection(),
166
+ isRowSelected(rowIndex: number) {
167
+ return requireGridElement().isRowSelected(rowIndex)
168
+ },
169
+ isCellSelected(rowIndex: number, columnKey: string) {
170
+ return requireGridElement().isCellSelected(rowIndex, columnKey)
171
+ }
172
+ }),
173
+ []
174
+ )
175
+
176
+ useEffect(() => {
177
+ const grid = gridRef.current
178
+ if (!grid) return
179
+
180
+ syncGridInputs(grid, {
181
+ data,
182
+ columns,
183
+ config,
184
+ theme,
185
+ ariaLabel,
186
+ ariaDescription,
187
+ screenReaderAnnouncements,
188
+ height,
189
+ rowHeight,
190
+ overscan,
191
+ columnOverscan,
192
+ bestFitSampleSize,
193
+ quickSearchDebounceThreshold,
194
+ quickSearchDebounceMs,
195
+ columnStateStorageKey
196
+ })
197
+ }, [
198
+ data,
199
+ columns,
200
+ config,
201
+ theme,
202
+ ariaLabel,
203
+ ariaDescription,
204
+ screenReaderAnnouncements,
205
+ height,
206
+ rowHeight,
207
+ overscan,
208
+ columnOverscan,
209
+ bestFitSampleSize,
210
+ quickSearchDebounceThreshold,
211
+ quickSearchDebounceMs,
212
+ columnStateStorageKey
213
+ ])
214
+
215
+ useEffect(() => {
216
+ const element = gridRef.current
217
+ if (!element || !onColumnStateChange) return
218
+
219
+ const handleColumnStateChange = (event: Event) => {
220
+ onColumnStateChange((event as CustomEvent<ColumnStateChangeDetail>).detail)
221
+ }
222
+
223
+ element.addEventListener('column-state-change', handleColumnStateChange)
224
+ return () => element.removeEventListener('column-state-change', handleColumnStateChange)
225
+ }, [onColumnStateChange])
226
+
227
+ useEffect(() => {
228
+ const element = gridRef.current
229
+ if (!element || !onColumnReorder) return
230
+
231
+ const handleColumnReorder = (event: Event) => {
232
+ onColumnReorder((event as CustomEvent<ColumnReorderDetail>).detail)
233
+ }
234
+
235
+ element.addEventListener('column-reorder', handleColumnReorder)
236
+ return () => element.removeEventListener('column-reorder', handleColumnReorder)
237
+ }, [onColumnReorder])
238
+
239
+ useEffect(() => {
240
+ const element = gridRef.current
241
+ if (!element || !onColumnVisibilityChange) return
242
+
243
+ const handleColumnVisibilityChange = (event: Event) => {
244
+ onColumnVisibilityChange((event as CustomEvent<ColumnVisibilityChangeDetail>).detail)
245
+ }
246
+
247
+ element.addEventListener('column-visibility-change', handleColumnVisibilityChange)
248
+ return () => element.removeEventListener('column-visibility-change', handleColumnVisibilityChange)
249
+ }, [onColumnVisibilityChange])
250
+
251
+ return <yc-grid ref={gridRef}></yc-grid>
252
+ })
@@ -0,0 +1,63 @@
1
+ import type {
2
+ DataGridElement,
3
+ GridColumn,
4
+ GridConfig,
5
+ GridTheme
6
+ } from '@tipolox/litgrid-web'
7
+
8
+ export type ReactGridInputs = {
9
+ data: unknown[]
10
+ columns: GridColumn[]
11
+ config: GridConfig
12
+ theme: GridTheme
13
+ ariaLabel: string
14
+ ariaDescription: string
15
+ screenReaderAnnouncements: boolean
16
+ height: number
17
+ rowHeight: number
18
+ overscan: number
19
+ columnOverscan: number
20
+ bestFitSampleSize: number
21
+ quickSearchDebounceThreshold: number
22
+ quickSearchDebounceMs: number
23
+ columnStateStorageKey?: string
24
+ }
25
+
26
+ const synchronizedInputs = new WeakMap<DataGridElement, Record<string, unknown>>()
27
+
28
+ export function syncGridInputs(
29
+ grid: DataGridElement,
30
+ inputs: ReactGridInputs
31
+ ): void {
32
+ // Keep the existing effect-triggered synchronization contract, including
33
+ // in-place mutations. Navigation is retained by the structural grid setters.
34
+ grid.data = inputs.data
35
+ grid.columns = inputs.columns
36
+ const values = {
37
+ config: inputs.config,
38
+ theme: inputs.theme, ariaLabel: inputs.ariaLabel, ariaDescription: inputs.ariaDescription,
39
+ screenReaderAnnouncements: inputs.screenReaderAnnouncements,
40
+ viewportHeight: inputs.height, virtualRowHeight: inputs.rowHeight,
41
+ overscanCount: inputs.overscan, columnOverscanCount: inputs.columnOverscan,
42
+ bestFitSampleSize: inputs.bestFitSampleSize,
43
+ quickSearchDebounceThreshold: inputs.quickSearchDebounceThreshold,
44
+ quickSearchDebounceMs: inputs.quickSearchDebounceMs,
45
+ columnStateStorageKey: inputs.columnStateStorageKey ?? null
46
+ }
47
+ // Snapshot only the fixed-size configuration, never row data or column content.
48
+ const config = inputs.config
49
+ const configKey = JSON.stringify([
50
+ config.selection?.mode ?? 'none', config.selection?.checkboxes ?? false,
51
+ config.rowHeader?.enabled ?? false, config.rowHeader?.width ?? 56,
52
+ config.pagination?.enabled ?? false,
53
+ Number.isFinite(config.pagination?.pageSize) && config.pagination!.pageSize! > 0 ? Math.max(1, Math.floor(config.pagination!.pageSize!)) : 50,
54
+ Number.isFinite(config.pagination?.pageIndex) && config.pagination!.pageIndex! >= 0 ? Math.floor(config.pagination!.pageIndex!) : 0
55
+ ])
56
+ const previous = synchronizedInputs.get(grid)
57
+ for (const [key, value] of Object.entries(values)) {
58
+ if (!previous || (key === 'config' ? previous.configKey !== configKey : previous[key] !== value)) {
59
+ Reflect.set(grid, key, value)
60
+ }
61
+ }
62
+ synchronizedInputs.set(grid, { ...values, configKey })
63
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export { DataGrid } from './DataGrid.js'
2
+ export type { DataGridInstance, DataGridProps, DataGridRef } from './DataGrid.js'
3
+ export type {
4
+ BestFitColumnWidth,
5
+ ColumnReorderDetail,
6
+ ColumnStateChangeDetail,
7
+ ColumnVisibilityChangeDetail,
8
+ DataGridElement,
9
+ GridColumn,
10
+ GridColumnState,
11
+ GridConfig,
12
+ GridFilter,
13
+ GridPaginationState,
14
+ GridSelection,
15
+ GridTheme,
16
+ SelectionIntent
17
+ } from '@tipolox/litgrid-web'