@octane-xplat/table 0.0.1 → 0.10.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/LICENSE +21 -0
- package/README.md +427 -3
- package/dist/native/ColumnDrag.tsrx.js +237 -0
- package/dist/native/DataGrid.tsrx.js +957 -0
- package/dist/native/GridControls.tsrx.js +424 -0
- package/dist/native/grid-accessibility.js +12 -0
- package/dist/native/grid-layout.js +43 -0
- package/dist/native/index.js +4 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/native/table.js +56 -0
- package/dist/native/useTable.tsrx.js +24 -0
- package/dist/web/ColumnDrag.tsrx.js +85 -0
- package/dist/web/DataGrid.tsrx.js +242 -0
- package/dist/web/GridControls.tsrx.js +114 -0
- package/dist/web/grid-accessibility.js +12 -0
- package/dist/web/grid-layout.js +43 -0
- package/dist/web/index.js +4 -0
- package/dist/web/octane-client-build.json +8 -0
- package/dist/web/table.js +56 -0
- package/dist/web/useTable.tsrx.js +26 -0
- package/package.json +75 -3
- package/src/ColumnDrag.tsrx +123 -0
- package/src/DataGrid.mobile.test.tsrx +55 -0
- package/src/DataGrid.tsrx +350 -0
- package/src/DataGrid.web.test.tsrx +277 -0
- package/src/GridControls.tsrx +157 -0
- package/src/grid-accessibility.ts +14 -0
- package/src/grid-layout.test.ts +32 -0
- package/src/grid-layout.ts +52 -0
- package/src/index.macos.ts +8 -0
- package/src/index.ts +7 -0
- package/src/index.web.ts +8 -0
- package/src/props.ts +122 -0
- package/src/table.test.ts +114 -0
- package/src/table.ts +91 -0
- package/src/useTable.tsrx +34 -0
- package/types/index.d.ts +20 -0
- package/types/index.macos.d.ts +17 -0
- package/types/props.d.ts +96 -0
- package/types/table.d.ts +83 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useRef } from 'octane'
|
|
2
|
+
import { Pressable, HStack as Row, Text, View } from '@octane-xplat/ui'
|
|
3
|
+
import { dragTarget, moveColumn, resizedWidth } from './grid-layout'
|
|
4
|
+
|
|
5
|
+
/** Pan handlers use shared gesture payloads; no browser resize listeners. */
|
|
6
|
+
export function ColumnDrag(props: {
|
|
7
|
+
table: any
|
|
8
|
+
column: any
|
|
9
|
+
resize?: boolean
|
|
10
|
+
reorder?: boolean
|
|
11
|
+
direction?: 'ltr' | 'rtl'
|
|
12
|
+
id?: string
|
|
13
|
+
state: any
|
|
14
|
+
}) {
|
|
15
|
+
void props.state
|
|
16
|
+
const drag = useRef<{
|
|
17
|
+
size: number
|
|
18
|
+
sizing: Record<string, number>
|
|
19
|
+
order: string[]
|
|
20
|
+
columns: any[]
|
|
21
|
+
} | null>(null)
|
|
22
|
+
|
|
23
|
+
const begin = () => {
|
|
24
|
+
drag.current = {
|
|
25
|
+
size: props.column.getSize(),
|
|
26
|
+
sizing: { ...props.table.store.get().columnSizing },
|
|
27
|
+
order: props.table.getAllLeafColumns().map((column: any) => column.id),
|
|
28
|
+
columns: props.table.getVisibleLeafColumns().map((column: any) => {
|
|
29
|
+
const size = column.getSize()
|
|
30
|
+
return { id: column.id, getSize: () => size }
|
|
31
|
+
}),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onPan = (event: any, resize: boolean) => {
|
|
36
|
+
if (event.state === 'began') {
|
|
37
|
+
begin()
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const start = drag.current
|
|
42
|
+
if (!start) return
|
|
43
|
+
if (event.state === 'cancelled') {
|
|
44
|
+
if (resize)
|
|
45
|
+
props.table.setColumnSizing((old: Record<string, number>) => {
|
|
46
|
+
const next = { ...old }
|
|
47
|
+
if (start.sizing[props.column.id] == null) delete next[props.column.id]
|
|
48
|
+
else next[props.column.id] = start.sizing[props.column.id]
|
|
49
|
+
|
|
50
|
+
return next
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
drag.current = null
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const dx = event.dx * (props.direction === 'rtl' ? -1 : 1)
|
|
58
|
+
if (resize && Number.isFinite(dx)) {
|
|
59
|
+
props.table.setColumnSizing((old: any) => ({
|
|
60
|
+
...old,
|
|
61
|
+
[props.column.id]: resizedWidth(props.column, start.size + dx),
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (event.state === 'ended') {
|
|
66
|
+
if (!resize) {
|
|
67
|
+
const target = dragTarget(start.columns, props.column.id, dx)
|
|
68
|
+
props.table.setColumnOrder(moveColumn(start.order, props.column.id, target))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
drag.current = null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const adjust = (delta: number) =>
|
|
76
|
+
props.table.setColumnSizing((old: any) => ({
|
|
77
|
+
...old,
|
|
78
|
+
[props.column.id]: resizedWidth(props.column, props.column.getSize() + delta),
|
|
79
|
+
}))
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Row className="vx-datagrid-handles">
|
|
83
|
+
{props.reorder ? (
|
|
84
|
+
<View
|
|
85
|
+
id={props.id ? `${props.id}-drag` : undefined}
|
|
86
|
+
className="vx-datagrid-handle"
|
|
87
|
+
onPan={(event) => onPan(event, false)}
|
|
88
|
+
accessibilityLabel={`Drag ${props.column.id} to reorder`}
|
|
89
|
+
>
|
|
90
|
+
<Text>↔</Text>
|
|
91
|
+
</View>
|
|
92
|
+
) : null}
|
|
93
|
+
{props.resize ? (
|
|
94
|
+
<>
|
|
95
|
+
<Pressable
|
|
96
|
+
id={props.id ? `${props.id}-narrow` : undefined}
|
|
97
|
+
className="vx-datagrid-handle"
|
|
98
|
+
onPress={() => adjust(-10)}
|
|
99
|
+
accessibilityLabel={`Narrow ${props.column.id}`}
|
|
100
|
+
>
|
|
101
|
+
<Text>−</Text>
|
|
102
|
+
</Pressable>
|
|
103
|
+
<View
|
|
104
|
+
id={props.id ? `${props.id}-resize` : undefined}
|
|
105
|
+
className="vx-datagrid-handle"
|
|
106
|
+
onPan={(event) => onPan(event, true)}
|
|
107
|
+
accessibilityLabel={`Drag to resize ${props.column.id}`}
|
|
108
|
+
>
|
|
109
|
+
<Text>│</Text>
|
|
110
|
+
</View>
|
|
111
|
+
<Pressable
|
|
112
|
+
id={props.id ? `${props.id}-widen` : undefined}
|
|
113
|
+
className="vx-datagrid-handle"
|
|
114
|
+
onPress={() => adjust(10)}
|
|
115
|
+
accessibilityLabel={`Widen ${props.column.id}`}
|
|
116
|
+
>
|
|
117
|
+
<Text>+</Text>
|
|
118
|
+
</Pressable>
|
|
119
|
+
</>
|
|
120
|
+
) : null}
|
|
121
|
+
</Row>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { createObjectContainer, createObjectDriver, createUniversalRoot, flushUniversalSync } from 'octane/universal/native'
|
|
4
|
+
import { useState } from 'octane'
|
|
5
|
+
|
|
6
|
+
vi.mock('@nativescript/core', () => ({ isIOS: false, isAndroid: true, Application: {} }))
|
|
7
|
+
vi.mock('../../ui/src/pan.tsrx', () => ({ usePan: () => undefined }))
|
|
8
|
+
vi.mock('@octane-xplat/ui', () => import('../tests/native-controls.mobile.tsrx'))
|
|
9
|
+
import { DataGrid } from './DataGrid.tsrx'
|
|
10
|
+
|
|
11
|
+
const data = [{ id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' }, { id: 'c', name: 'Charlie' }]
|
|
12
|
+
const columns = [{ accessorKey: 'name', header: 'Name' }]
|
|
13
|
+
|
|
14
|
+
function find(nodes: any[], id: string): any {
|
|
15
|
+
for (const node of nodes) {
|
|
16
|
+
if (node.props?.id === id) return node
|
|
17
|
+
const child = find(node.children ?? [], id)
|
|
18
|
+
if (child) return child
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function Controlled() {
|
|
23
|
+
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({})
|
|
24
|
+
return <DataGrid id="grid" data={data} columns={columns} getRowId={(row) => row.id}
|
|
25
|
+
getRowLabel={(row) => row.original.name} showRowSelection showPagination
|
|
26
|
+
state={{ rowSelection }} options={{ onRowSelectionChange: setRowSelection }}
|
|
27
|
+
initialState={{ pagination: { pageIndex: 0, pageSize: 2 } }} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('native DataGrid selection integration', () => {
|
|
31
|
+
it('updates retained controls for caller state, mixed selection and off-page ordinals', () => {
|
|
32
|
+
const container = createObjectContainer('nativescript')
|
|
33
|
+
const root = createUniversalRoot(container, createObjectDriver('nativescript'))
|
|
34
|
+
root.render(Controlled as any, {})
|
|
35
|
+
const get = (id: string) => find(container.children, id)
|
|
36
|
+
const press = (id: string) => flushUniversalSync(() => container.dispatchEvent(get(id), 'tap', {}))
|
|
37
|
+
const page = get('grid-select-page')
|
|
38
|
+
expect(get('grid-select-a').props.accessibilityLabel).toBe('Select Alice')
|
|
39
|
+
expect(get('grid-select-a').props.accessibilityHint).toBe('Row 1 of 3')
|
|
40
|
+
press('grid-select-a')
|
|
41
|
+
expect(get('grid-select-page')).toBe(page)
|
|
42
|
+
expect(page.props.accessibilityValue).toBe('Partially selected')
|
|
43
|
+
expect(page.props.accessibilityState).toBeUndefined()
|
|
44
|
+
press('grid-select-page')
|
|
45
|
+
expect(page.props.accessibilityValue).toBeUndefined()
|
|
46
|
+
expect(page.props.accessibilityState).toBe('checked')
|
|
47
|
+
press('grid-next')
|
|
48
|
+
expect(get('grid-select-c').props.accessibilityHint).toBe('Row 3 of 3')
|
|
49
|
+
expect(page.props.accessibilityState).toBe('unchecked')
|
|
50
|
+
press('grid-clear-selection')
|
|
51
|
+
press('grid-previous')
|
|
52
|
+
expect(get('grid-select-a').props.accessibilityState).toBe('unchecked')
|
|
53
|
+
root.unmount()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import type { Cell, Header, Row, RowData } from '@tanstack/table-core'
|
|
2
|
+
import {
|
|
3
|
+
CheckboxInput as Checkbox,
|
|
4
|
+
Pressable,
|
|
5
|
+
HStack as GridRow,
|
|
6
|
+
Text,
|
|
7
|
+
TextInput,
|
|
8
|
+
View,
|
|
9
|
+
VirtualList,
|
|
10
|
+
} from '@octane-xplat/ui'
|
|
11
|
+
|
|
12
|
+
import { useTable } from './useTable.tsrx'
|
|
13
|
+
import type { DataGridProps, UseTable } from './props'
|
|
14
|
+
import type { DataGridFeatures } from './table'
|
|
15
|
+
import { columnStyle } from './grid-layout'
|
|
16
|
+
import { gridRowPosition } from './grid-accessibility'
|
|
17
|
+
import { ColumnDrag } from './ColumnDrag.tsrx'
|
|
18
|
+
import { GridControls, GridPagination } from './GridControls.tsrx'
|
|
19
|
+
|
|
20
|
+
/** Evaluates a column `cell`/`header` template (string | fn → node) — the
|
|
21
|
+
* DOM-free half of table-core's `flexRender`. */
|
|
22
|
+
function renderTemplate(template: any, ctx: any) {
|
|
23
|
+
if (template == null) return null
|
|
24
|
+
return typeof template === 'function' ? template(ctx) : template
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Primitive template results draw as Text; nodes pass through. */
|
|
28
|
+
function TemplateContent(props: { content: any }) {
|
|
29
|
+
const content = props.content
|
|
30
|
+
if (content == null || content === false) return null
|
|
31
|
+
if (typeof content === 'string' || typeof content === 'number' || typeof content === 'bigint') {
|
|
32
|
+
return <Text>{String(content)}</Text>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return content
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sortIndicator(column: any) {
|
|
39
|
+
const dir = column.getIsSorted?.()
|
|
40
|
+
return dir === 'asc' ? ' ↑' : dir === 'desc' ? ' ↓' : ''
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DataGridHeaderCell<TData extends RowData>(props: {
|
|
44
|
+
header: Header<DataGridFeatures, TData, any>
|
|
45
|
+
render?: DataGridProps<TData>['renderHeader']
|
|
46
|
+
id?: string
|
|
47
|
+
/** Flat TableState — prop identity forces re-eval of derived reads
|
|
48
|
+
* (`getIsSorted`) on native, where unchanged-prop children are retained. */
|
|
49
|
+
state: any
|
|
50
|
+
table: any
|
|
51
|
+
filters?: boolean
|
|
52
|
+
resize?: boolean
|
|
53
|
+
reorder?: boolean
|
|
54
|
+
direction?: 'ltr' | 'rtl'
|
|
55
|
+
}) {
|
|
56
|
+
const { header } = props
|
|
57
|
+
void props.state
|
|
58
|
+
const column = header.column as any
|
|
59
|
+
const canSort = !header.isPlaceholder && column.getCanSort?.() === true
|
|
60
|
+
const content = props.render
|
|
61
|
+
? props.render(header)
|
|
62
|
+
: (renderTemplate(column.columnDef.header, header.getContext()) ?? column.id)
|
|
63
|
+
|
|
64
|
+
const isPrimitive =
|
|
65
|
+
typeof content === 'string' || typeof content === 'number' || typeof content === 'bigint'
|
|
66
|
+
|
|
67
|
+
// Native Labels can't host span/element children — primitives join the
|
|
68
|
+
// indicator text inside one Text; nodes become siblings of it.
|
|
69
|
+
const inner = isPrimitive ? (
|
|
70
|
+
<Text>
|
|
71
|
+
{String(content)}
|
|
72
|
+
{canSort ? sortIndicator(column) : ''}
|
|
73
|
+
</Text>
|
|
74
|
+
) : (
|
|
75
|
+
<>
|
|
76
|
+
<TemplateContent content={content} />
|
|
77
|
+
{canSort ? <Text>{sortIndicator(column)}</Text> : null}
|
|
78
|
+
</>
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const cls = 'vx-table-cell vx-table-th'
|
|
82
|
+
return (
|
|
83
|
+
<View
|
|
84
|
+
className={cls}
|
|
85
|
+
web={{ role: 'columnheader' }}
|
|
86
|
+
style={columnStyle(column, Boolean(props.resize || props.reorder), props.state.columnSizing)}
|
|
87
|
+
>
|
|
88
|
+
{canSort ? (
|
|
89
|
+
<Pressable
|
|
90
|
+
id={props.id}
|
|
91
|
+
className="vx-table-th--sortable"
|
|
92
|
+
onPress={() => column.toggleSorting()}
|
|
93
|
+
accessibilityLabel={`Sort ${column.id}, ${column.getIsSorted() || 'unsorted'}`}
|
|
94
|
+
>
|
|
95
|
+
<>{inner}</>
|
|
96
|
+
</Pressable>
|
|
97
|
+
) : (
|
|
98
|
+
<View id={props.id}>{inner}</View>
|
|
99
|
+
)}
|
|
100
|
+
{!header.isPlaceholder && props.filters && column.getCanFilter() ? (
|
|
101
|
+
<TextInput
|
|
102
|
+
id={props.id ? `${props.id}-filter` : undefined}
|
|
103
|
+
value={String(column.getFilterValue() ?? '')}
|
|
104
|
+
placeholder={`Filter ${column.id}`}
|
|
105
|
+
accessibilityLabel={`Filter ${column.id}`}
|
|
106
|
+
onChange={(value) => {
|
|
107
|
+
column.setFilterValue(value)
|
|
108
|
+
props.table.setPageIndex(0)
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
) : null}
|
|
112
|
+
{!header.isPlaceholder && column.columns.length === 0 && (props.resize || props.reorder) ? (
|
|
113
|
+
<ColumnDrag
|
|
114
|
+
id={props.id}
|
|
115
|
+
table={props.table}
|
|
116
|
+
column={column}
|
|
117
|
+
resize={props.resize}
|
|
118
|
+
reorder={props.reorder}
|
|
119
|
+
direction={props.direction}
|
|
120
|
+
state={props.state}
|
|
121
|
+
/>
|
|
122
|
+
) : null}
|
|
123
|
+
</View>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function DataGridBodyRow<TData extends RowData>(props: {
|
|
128
|
+
row: Row<DataGridFeatures, TData>
|
|
129
|
+
renderCell?: DataGridProps<TData>['renderCell']
|
|
130
|
+
onRowPress?: DataGridProps<TData>['onRowPress']
|
|
131
|
+
idPrefix?: string
|
|
132
|
+
/** See DataGridHeaderCell.state — forces re-eval of `getIsSelected`. */
|
|
133
|
+
state: any
|
|
134
|
+
position: { ariaIndex: number; label: string }
|
|
135
|
+
selection?: boolean
|
|
136
|
+
fixed?: boolean
|
|
137
|
+
getRowLabel?: (row: any) => string
|
|
138
|
+
}) {
|
|
139
|
+
const { row } = props
|
|
140
|
+
void props.state
|
|
141
|
+
const cells = row.getVisibleCells()
|
|
142
|
+
const inner = (
|
|
143
|
+
<>
|
|
144
|
+
{cells.map((cell: Cell<DataGridFeatures, TData, any>) => (
|
|
145
|
+
<View
|
|
146
|
+
key={cell.id}
|
|
147
|
+
className="vx-table-cell"
|
|
148
|
+
web={{ role: 'cell' }}
|
|
149
|
+
style={columnStyle(cell.column, Boolean(props.fixed), props.state.columnSizing)}
|
|
150
|
+
>
|
|
151
|
+
<TemplateContent
|
|
152
|
+
content={
|
|
153
|
+
props.renderCell
|
|
154
|
+
? props.renderCell(cell)
|
|
155
|
+
: (renderTemplate(cell.column.columnDef.cell, cell.getContext()) ??
|
|
156
|
+
cell.renderValue?.())
|
|
157
|
+
}
|
|
158
|
+
/>
|
|
159
|
+
</View>
|
|
160
|
+
))}
|
|
161
|
+
</>
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const cls =
|
|
165
|
+
'vx-table-row' +
|
|
166
|
+
(props.onRowPress ? ' vx-table-row--pressable' : '') +
|
|
167
|
+
(row.getIsSelected?.() ? ' vx-table-row--selected' : '')
|
|
168
|
+
|
|
169
|
+
const id = props.idPrefix ? `${props.idPrefix}-row-${row.id}` : undefined
|
|
170
|
+
const content = props.onRowPress ? (
|
|
171
|
+
<Pressable
|
|
172
|
+
id={props.selection ? undefined : id}
|
|
173
|
+
className="vx-datagrid-row-action"
|
|
174
|
+
onPress={() => props.onRowPress!(row)}
|
|
175
|
+
accessibilityLabel={props.getRowLabel?.(row) ?? row.id}
|
|
176
|
+
accessibilityHint={props.position.label}
|
|
177
|
+
accessibilityState={{ selected: row.getIsSelected?.() || undefined }}
|
|
178
|
+
>
|
|
179
|
+
{inner}
|
|
180
|
+
</Pressable>
|
|
181
|
+
) : (
|
|
182
|
+
inner
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<GridRow
|
|
187
|
+
id={props.selection || !props.onRowPress ? id : undefined}
|
|
188
|
+
className={cls}
|
|
189
|
+
web={{ role: 'row', 'aria-rowindex': props.position.ariaIndex }}
|
|
190
|
+
accessibilityHint={props.position.label}
|
|
191
|
+
accessibilityState={{ selected: row.getIsSelected?.() || undefined }}
|
|
192
|
+
>
|
|
193
|
+
{props.selection ? (
|
|
194
|
+
<View className="vx-datagrid-selection" web={{ role: 'cell' }}>
|
|
195
|
+
<Checkbox
|
|
196
|
+
id={props.idPrefix ? `${props.idPrefix}-select-${row.id}` : undefined}
|
|
197
|
+
checked={row.getIsSelected()}
|
|
198
|
+
isDisabled={!row.getCanSelect()}
|
|
199
|
+
accessibilityLabel={`Select ${props.getRowLabel?.(row) ?? row.id}`}
|
|
200
|
+
accessibilityHint={props.position.label}
|
|
201
|
+
onCheckedChange={(checked) => row.toggleSelected(checked)}
|
|
202
|
+
/>
|
|
203
|
+
</View>
|
|
204
|
+
) : null}
|
|
205
|
+
{content}
|
|
206
|
+
</GridRow>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** DataGrid — headless `@tanstack/table-core` row model (sorting, filtering,
|
|
211
|
+
* pagination, selection, grouping, expanding) over shared `VirtualList`
|
|
212
|
+
* windowing. One structure on every target; the columns/state contract is
|
|
213
|
+
* the library's `ColumnDef`s plus `TableState` slices. Large-column or
|
|
214
|
+
* platform-recycler presentations belong to platform subpaths. */
|
|
215
|
+
export function DataGrid<TData extends RowData = any>(props: DataGridProps<TData>) {
|
|
216
|
+
const { table, state }: UseTable<DataGridFeatures, TData> = useTable<TData, DataGridFeatures>({
|
|
217
|
+
...props.options,
|
|
218
|
+
defaultColumn: props.showColumnFilters
|
|
219
|
+
? { filterFn: 'includesString', ...props.options?.defaultColumn }
|
|
220
|
+
: props.options?.defaultColumn,
|
|
221
|
+
data: props.data,
|
|
222
|
+
columns: props.columns as any,
|
|
223
|
+
features: props.features as any,
|
|
224
|
+
initialState: props.initialState as any,
|
|
225
|
+
state: props.state as any,
|
|
226
|
+
getRowId: props.getRowId,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const headerGroups = table.getHeaderGroups()
|
|
230
|
+
const rows = table.getRowModel().rows
|
|
231
|
+
// getRowCount uses the filtered pre-pagination model for client data.
|
|
232
|
+
// An unknown manual total must not masquerade as the loaded page length.
|
|
233
|
+
const rowCount = props.options?.manualPagination && props.options.rowCount == null
|
|
234
|
+
? -1
|
|
235
|
+
: table.getRowCount()
|
|
236
|
+
|
|
237
|
+
const ariaRowCount = rowCount < 0 ? -1 : headerGroups.length + rowCount
|
|
238
|
+
return (
|
|
239
|
+
<View
|
|
240
|
+
id={props.id}
|
|
241
|
+
className={['vx-table', 'vx-datagrid', props.className]}
|
|
242
|
+
style={props.style}
|
|
243
|
+
flexGrow={props.flexGrow ?? 1}
|
|
244
|
+
flexShrink={props.flexShrink}
|
|
245
|
+
alignSelf={props.alignSelf}
|
|
246
|
+
row={props.row}
|
|
247
|
+
col={props.col}
|
|
248
|
+
rowSpan={props.rowSpan}
|
|
249
|
+
colSpan={props.colSpan}
|
|
250
|
+
ios={props.ios}
|
|
251
|
+
android={props.android}
|
|
252
|
+
web={props.web}
|
|
253
|
+
accessible={props.accessible}
|
|
254
|
+
accessibilityHint={props.accessibilityHint}
|
|
255
|
+
accessibilityState={props.accessibilityState}
|
|
256
|
+
accessibilityRole={props.accessibilityRole as any}
|
|
257
|
+
accessibilityLabel={props.accessibilityLabel}
|
|
258
|
+
>
|
|
259
|
+
{props.showGlobalFilter || props.showColumnSettings ? (
|
|
260
|
+
<GridControls
|
|
261
|
+
id={props.id}
|
|
262
|
+
table={table}
|
|
263
|
+
state={state}
|
|
264
|
+
search={props.showGlobalFilter}
|
|
265
|
+
settings={props.showColumnSettings}
|
|
266
|
+
/>
|
|
267
|
+
) : null}
|
|
268
|
+
{props.showRowSelection ? (
|
|
269
|
+
<GridRow className="vx-datagrid-controls">
|
|
270
|
+
<Text id={props.id ? `${props.id}-selected-count` : undefined}>
|
|
271
|
+
{Object.values(state.rowSelection).filter(Boolean).length} selected
|
|
272
|
+
</Text>
|
|
273
|
+
<Pressable
|
|
274
|
+
id={props.id ? `${props.id}-clear-selection` : undefined}
|
|
275
|
+
className="vx-datagrid-action"
|
|
276
|
+
onPress={() => table.resetRowSelection(true)}
|
|
277
|
+
accessibilityLabel="Clear selection"
|
|
278
|
+
>
|
|
279
|
+
<Text>Clear selection</Text>
|
|
280
|
+
</Pressable>
|
|
281
|
+
{props.renderSelectionActions?.(table)}
|
|
282
|
+
</GridRow>
|
|
283
|
+
) : null}
|
|
284
|
+
<View
|
|
285
|
+
id={props.id ? `${props.id}-table` : undefined}
|
|
286
|
+
flexGrow={1}
|
|
287
|
+
web={{ role: 'table', 'aria-label': props.accessibilityLabel, 'aria-rowcount': ariaRowCount, 'aria-colcount': table.getVisibleLeafColumns().length + (props.showRowSelection ? 1 : 0) }}
|
|
288
|
+
>
|
|
289
|
+
<View id={props.id ? `${props.id}-header` : undefined} flexShrink={0}>
|
|
290
|
+
{headerGroups.map((group) => (
|
|
291
|
+
<GridRow key={group.id} className="vx-table-head" web={{ role: 'row', 'aria-rowindex': group.depth + 1 }}>
|
|
292
|
+
{props.showRowSelection ? (
|
|
293
|
+
<View className="vx-datagrid-selection" web={{ role: 'columnheader' }}>
|
|
294
|
+
{group.depth === 0 ? (
|
|
295
|
+
<Checkbox
|
|
296
|
+
id={props.id ? `${props.id}-select-page` : undefined}
|
|
297
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
298
|
+
indeterminate={!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected()}
|
|
299
|
+
isDisabled={!rows.some((row) => row.getCanSelect())}
|
|
300
|
+
accessibilityLabel="Select current page"
|
|
301
|
+
onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
|
|
302
|
+
/>
|
|
303
|
+
) : null}
|
|
304
|
+
</View>
|
|
305
|
+
) : null}
|
|
306
|
+
{group.headers.map((header: Header<DataGridFeatures, TData, any>) => (
|
|
307
|
+
<DataGridHeaderCell
|
|
308
|
+
key={header.id}
|
|
309
|
+
header={header}
|
|
310
|
+
render={props.renderHeader}
|
|
311
|
+
id={props.id ? `${props.id}-th-${header.column.id}` : undefined}
|
|
312
|
+
state={state}
|
|
313
|
+
table={table}
|
|
314
|
+
filters={props.showColumnFilters}
|
|
315
|
+
resize={props.resizableColumns}
|
|
316
|
+
reorder={props.reorderableColumns}
|
|
317
|
+
direction={props.direction}
|
|
318
|
+
/>
|
|
319
|
+
))}
|
|
320
|
+
</GridRow>
|
|
321
|
+
))}
|
|
322
|
+
</View>
|
|
323
|
+
<VirtualList
|
|
324
|
+
flexGrow={1}
|
|
325
|
+
className="vx-datagrid-body"
|
|
326
|
+
id={props.id ? `${props.id}-body` : undefined}
|
|
327
|
+
items={rows}
|
|
328
|
+
keyExtractor={(row: Row<DataGridFeatures, TData>) => row.id}
|
|
329
|
+
renderItem={(row: Row<DataGridFeatures, TData>, index: number) => (
|
|
330
|
+
<DataGridBodyRow
|
|
331
|
+
row={row}
|
|
332
|
+
position={gridRowPosition(index, state.pagination, headerGroups.length, rowCount)}
|
|
333
|
+
renderCell={props.renderCell}
|
|
334
|
+
onRowPress={props.onRowPress}
|
|
335
|
+
idPrefix={props.id}
|
|
336
|
+
state={state}
|
|
337
|
+
selection={props.showRowSelection}
|
|
338
|
+
getRowLabel={props.getRowLabel}
|
|
339
|
+
fixed={Boolean(props.resizableColumns || props.reorderableColumns)}
|
|
340
|
+
/>
|
|
341
|
+
)}
|
|
342
|
+
renderEmpty={props.renderEmpty}
|
|
343
|
+
/>
|
|
344
|
+
</View>
|
|
345
|
+
{props.showPagination ? (
|
|
346
|
+
<GridPagination id={props.id} table={table} state={state} sizes={props.pageSizeOptions} />
|
|
347
|
+
) : null}
|
|
348
|
+
</View>
|
|
349
|
+
)
|
|
350
|
+
}
|