@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,242 @@
|
|
|
1
|
+
import { useTable } from "./useTable.tsrx.js";
|
|
2
|
+
import { columnStyle } from "./grid-layout.js";
|
|
3
|
+
import { gridRowPosition } from "./grid-accessibility.js";
|
|
4
|
+
import { ColumnDrag } from "./ColumnDrag.tsrx.js";
|
|
5
|
+
import { GridControls, GridPagination } from "./GridControls.tsrx.js";
|
|
6
|
+
import { Fragment, createScopedElement, createScopedValue, hookSlots, positionalChildren, withSlot } from "octane";
|
|
7
|
+
import { createElementFromConfig, deferRecord, isRenderCall } from "octane/internal/client";
|
|
8
|
+
import { CheckboxInput, HStack, Pressable, Text, TextInput, View, VirtualList } from "@octane-xplat/ui";
|
|
9
|
+
//#region src/DataGrid.tsrx
|
|
10
|
+
function DataGridHeaderCell$record(canSort, cls, column, header, inner, props) {
|
|
11
|
+
return createScopedElement(View, {
|
|
12
|
+
className: cls,
|
|
13
|
+
web: { role: "columnheader" },
|
|
14
|
+
style: columnStyle(column, Boolean(props.resize || props.reorder), props.state.columnSizing)
|
|
15
|
+
}, () => positionalChildren([
|
|
16
|
+
canSort ? createScopedValue(() => createScopedElement(Pressable, {
|
|
17
|
+
id: props.id,
|
|
18
|
+
className: "vx-table-th--sortable",
|
|
19
|
+
onPress: () => column.toggleSorting(),
|
|
20
|
+
accessibilityLabel: `Sort ${column.id}, ${column.getIsSorted() || "unsorted"}`
|
|
21
|
+
}, () => createScopedElement(Fragment, {}, () => positionalChildren([inner])), "c:9c774b76")) : createScopedValue(() => createScopedElement(View, { id: props.id }, () => inner, "c:8dd2c3bd")),
|
|
22
|
+
!header.isPlaceholder && props.filters && column.getCanFilter() ? createScopedValue(() => createElementFromConfig("c:262f383b", TextInput, {
|
|
23
|
+
id: props.id ? `${props.id}-filter` : void 0,
|
|
24
|
+
value: String(column.getFilterValue() ?? ""),
|
|
25
|
+
placeholder: `Filter ${column.id}`,
|
|
26
|
+
accessibilityLabel: `Filter ${column.id}`,
|
|
27
|
+
onChange: (value) => {
|
|
28
|
+
column.setFilterValue(value);
|
|
29
|
+
props.table.setPageIndex(0);
|
|
30
|
+
}
|
|
31
|
+
})) : null,
|
|
32
|
+
!header.isPlaceholder && column.columns.length === 0 && (props.resize || props.reorder) ? createScopedValue(() => createElementFromConfig("c:9ccad299", ColumnDrag, {
|
|
33
|
+
id: props.id,
|
|
34
|
+
table: props.table,
|
|
35
|
+
column,
|
|
36
|
+
resize: props.resize,
|
|
37
|
+
reorder: props.reorder,
|
|
38
|
+
direction: props.direction,
|
|
39
|
+
state: props.state
|
|
40
|
+
})) : null
|
|
41
|
+
]), "c:b6406c85");
|
|
42
|
+
}
|
|
43
|
+
function DataGridBodyRow$record(cls, content, id, props, row) {
|
|
44
|
+
return createScopedElement(HStack, {
|
|
45
|
+
id: props.selection || !props.onRowPress ? id : void 0,
|
|
46
|
+
className: cls,
|
|
47
|
+
web: {
|
|
48
|
+
role: "row",
|
|
49
|
+
"aria-rowindex": props.position.ariaIndex
|
|
50
|
+
},
|
|
51
|
+
accessibilityHint: props.position.label,
|
|
52
|
+
accessibilityState: { selected: row.getIsSelected?.() || void 0 }
|
|
53
|
+
}, () => positionalChildren([props.selection ? createScopedValue(() => createScopedElement(View, {
|
|
54
|
+
className: "vx-datagrid-selection",
|
|
55
|
+
web: { role: "cell" }
|
|
56
|
+
}, () => createScopedValue(() => createElementFromConfig("c:a819c8d2", CheckboxInput, {
|
|
57
|
+
id: props.idPrefix ? `${props.idPrefix}-select-${row.id}` : void 0,
|
|
58
|
+
checked: row.getIsSelected(),
|
|
59
|
+
isDisabled: !row.getCanSelect(),
|
|
60
|
+
accessibilityLabel: `Select ${props.getRowLabel?.(row) ?? row.id}`,
|
|
61
|
+
accessibilityHint: props.position.label,
|
|
62
|
+
onCheckedChange: (checked) => row.toggleSelected(checked)
|
|
63
|
+
})), "c:3d3ff402")) : null, content]), "c:75c97115");
|
|
64
|
+
}
|
|
65
|
+
var _h$0 = /* @__PURE__ */ Symbol(/* @__PURE__ */ hookSlots(1));
|
|
66
|
+
function DataGrid$record(ariaRowCount, headerGroups, props, rowCount, rows, state, table) {
|
|
67
|
+
return createScopedElement(View, {
|
|
68
|
+
id: props.id,
|
|
69
|
+
className: [
|
|
70
|
+
"vx-table",
|
|
71
|
+
"vx-datagrid",
|
|
72
|
+
props.className
|
|
73
|
+
],
|
|
74
|
+
style: props.style,
|
|
75
|
+
flexGrow: props.flexGrow ?? 1,
|
|
76
|
+
flexShrink: props.flexShrink,
|
|
77
|
+
alignSelf: props.alignSelf,
|
|
78
|
+
row: props.row,
|
|
79
|
+
col: props.col,
|
|
80
|
+
rowSpan: props.rowSpan,
|
|
81
|
+
colSpan: props.colSpan,
|
|
82
|
+
ios: props.ios,
|
|
83
|
+
android: props.android,
|
|
84
|
+
web: props.web,
|
|
85
|
+
accessible: props.accessible,
|
|
86
|
+
accessibilityHint: props.accessibilityHint,
|
|
87
|
+
accessibilityState: props.accessibilityState,
|
|
88
|
+
accessibilityRole: props.accessibilityRole,
|
|
89
|
+
accessibilityLabel: props.accessibilityLabel
|
|
90
|
+
}, () => positionalChildren([
|
|
91
|
+
props.showGlobalFilter || props.showColumnSettings ? createScopedValue(() => createElementFromConfig("c:c46c08f0", GridControls, {
|
|
92
|
+
id: props.id,
|
|
93
|
+
table,
|
|
94
|
+
state,
|
|
95
|
+
search: props.showGlobalFilter,
|
|
96
|
+
settings: props.showColumnSettings
|
|
97
|
+
})) : null,
|
|
98
|
+
props.showRowSelection ? createScopedElement(HStack, { className: "vx-datagrid-controls" }, () => positionalChildren([
|
|
99
|
+
createScopedValue(() => createScopedElement(Text, { id: props.id ? `${props.id}-selected-count` : void 0 }, () => positionalChildren([Object.values(state.rowSelection).filter(Boolean).length, " selected"]), "c:884ce760")),
|
|
100
|
+
createScopedValue(() => createScopedElement(Pressable, {
|
|
101
|
+
id: props.id ? `${props.id}-clear-selection` : void 0,
|
|
102
|
+
className: "vx-datagrid-action",
|
|
103
|
+
onPress: () => table.resetRowSelection(true),
|
|
104
|
+
accessibilityLabel: "Clear selection"
|
|
105
|
+
}, () => createElementFromConfig("c:382b9cf3", Text, {}, ["Clear selection"]), "c:fe7db179")),
|
|
106
|
+
props.renderSelectionActions?.(table)
|
|
107
|
+
]), "c:1e3c09a4") : null,
|
|
108
|
+
createScopedValue(() => createScopedElement(View, {
|
|
109
|
+
id: props.id ? `${props.id}-table` : void 0,
|
|
110
|
+
flexGrow: 1,
|
|
111
|
+
web: {
|
|
112
|
+
role: "table",
|
|
113
|
+
"aria-label": props.accessibilityLabel,
|
|
114
|
+
"aria-rowcount": ariaRowCount,
|
|
115
|
+
"aria-colcount": table.getVisibleLeafColumns().length + (props.showRowSelection ? 1 : 0)
|
|
116
|
+
}
|
|
117
|
+
}, () => positionalChildren([createScopedValue(() => createScopedElement(View, {
|
|
118
|
+
id: props.id ? `${props.id}-header` : void 0,
|
|
119
|
+
flexShrink: 0
|
|
120
|
+
}, () => headerGroups.map((group) => createScopedElement(HStack, {
|
|
121
|
+
key: group.id,
|
|
122
|
+
className: "vx-table-head",
|
|
123
|
+
web: {
|
|
124
|
+
role: "row",
|
|
125
|
+
"aria-rowindex": group.depth + 1
|
|
126
|
+
}
|
|
127
|
+
}, () => positionalChildren([props.showRowSelection ? createScopedValue(() => createScopedElement(View, {
|
|
128
|
+
className: "vx-datagrid-selection",
|
|
129
|
+
web: { role: "columnheader" }
|
|
130
|
+
}, () => group.depth === 0 ? createScopedValue(() => createElementFromConfig("c:7f7ed838", CheckboxInput, {
|
|
131
|
+
id: props.id ? `${props.id}-select-page` : void 0,
|
|
132
|
+
checked: table.getIsAllPageRowsSelected(),
|
|
133
|
+
indeterminate: !table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected(),
|
|
134
|
+
isDisabled: !rows.some((row) => row.getCanSelect()),
|
|
135
|
+
accessibilityLabel: "Select current page",
|
|
136
|
+
onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked)
|
|
137
|
+
})) : null, "c:9005f88e")) : null, group.headers.map((header) => createElementFromConfig("c:ec2568e0", DataGridHeaderCell, {
|
|
138
|
+
key: header.id,
|
|
139
|
+
header,
|
|
140
|
+
render: props.renderHeader,
|
|
141
|
+
id: props.id ? `${props.id}-th-${header.column.id}` : void 0,
|
|
142
|
+
state,
|
|
143
|
+
table,
|
|
144
|
+
filters: props.showColumnFilters,
|
|
145
|
+
resize: props.resizableColumns,
|
|
146
|
+
reorder: props.reorderableColumns,
|
|
147
|
+
direction: props.direction
|
|
148
|
+
}))]), "c:f0882298")), "c:f76ea045")), createScopedValue(() => createElementFromConfig("c:1fe3bf49", VirtualList, {
|
|
149
|
+
flexGrow: 1,
|
|
150
|
+
className: "vx-datagrid-body",
|
|
151
|
+
id: props.id ? `${props.id}-body` : void 0,
|
|
152
|
+
items: rows,
|
|
153
|
+
keyExtractor: (row) => row.id,
|
|
154
|
+
renderItem: (row, index) => createScopedValue(() => createElementFromConfig("c:c17292e4", DataGridBodyRow, {
|
|
155
|
+
row,
|
|
156
|
+
position: gridRowPosition(index, state.pagination, headerGroups.length, rowCount),
|
|
157
|
+
renderCell: props.renderCell,
|
|
158
|
+
onRowPress: props.onRowPress,
|
|
159
|
+
idPrefix: props.id,
|
|
160
|
+
state,
|
|
161
|
+
selection: props.showRowSelection,
|
|
162
|
+
getRowLabel: props.getRowLabel,
|
|
163
|
+
fixed: Boolean(props.resizableColumns || props.reorderableColumns)
|
|
164
|
+
})),
|
|
165
|
+
renderEmpty: props.renderEmpty
|
|
166
|
+
}))]), "c:75a1e8c3")),
|
|
167
|
+
props.showPagination ? createScopedValue(() => createElementFromConfig("c:cb40f9c9", GridPagination, {
|
|
168
|
+
id: props.id,
|
|
169
|
+
table,
|
|
170
|
+
state,
|
|
171
|
+
sizes: props.pageSizeOptions
|
|
172
|
+
})) : null
|
|
173
|
+
]), "c:38b91cee");
|
|
174
|
+
}
|
|
175
|
+
function renderTemplate(template, ctx) {
|
|
176
|
+
if (template == null) return null;
|
|
177
|
+
return typeof template === "function" ? template(ctx) : template;
|
|
178
|
+
}
|
|
179
|
+
function TemplateContent(props) {
|
|
180
|
+
const content = props.content;
|
|
181
|
+
if (content == null || content === false) return null;
|
|
182
|
+
if (typeof content === "string" || typeof content === "number" || typeof content === "bigint") return createScopedElement(Text, {}, () => String(content), "c:0e8d5bed");
|
|
183
|
+
return content ?? null;
|
|
184
|
+
}
|
|
185
|
+
function sortIndicator(column) {
|
|
186
|
+
const dir = column.getIsSorted?.();
|
|
187
|
+
return dir === "asc" ? " ↑" : dir === "desc" ? " ↓" : "";
|
|
188
|
+
}
|
|
189
|
+
function DataGridHeaderCell(props, __renderScope = void 0) {
|
|
190
|
+
const { header } = props;
|
|
191
|
+
props.state;
|
|
192
|
+
const column = header.column;
|
|
193
|
+
const canSort = !header.isPlaceholder && column.getCanSort?.() === true;
|
|
194
|
+
const content = props.render ? props.render(header) : renderTemplate(column.columnDef.header, header.getContext()) ?? column.id;
|
|
195
|
+
const inner = typeof content === "string" || typeof content === "number" || typeof content === "bigint" ? createScopedElement(Text, {}, () => positionalChildren([String(content), canSort ? sortIndicator(column) : ""]), "c:9e661db7") : createScopedElement(Fragment, {}, () => positionalChildren([createScopedValue(() => createElementFromConfig("c:2b569bb4", TemplateContent, { content })), canSort ? createScopedElement(Text, {}, () => sortIndicator(column), "c:79b94a0c") : null]));
|
|
196
|
+
const cls = "vx-table-cell vx-table-th";
|
|
197
|
+
return isRenderCall(__renderScope) ? DataGridHeaderCell$record(canSort, cls, column, header, inner, props) : deferRecord(DataGridHeaderCell$record, canSort, cls, column, header, inner, props);
|
|
198
|
+
}
|
|
199
|
+
function DataGridBodyRow(props, __renderScope$ = void 0) {
|
|
200
|
+
const { row } = props;
|
|
201
|
+
props.state;
|
|
202
|
+
const cells = row.getVisibleCells();
|
|
203
|
+
const inner = createScopedElement(Fragment, {}, () => positionalChildren([cells.map((cell) => createScopedElement(View, {
|
|
204
|
+
key: cell.id,
|
|
205
|
+
className: "vx-table-cell",
|
|
206
|
+
web: { role: "cell" },
|
|
207
|
+
style: columnStyle(cell.column, Boolean(props.fixed), props.state.columnSizing)
|
|
208
|
+
}, () => createScopedValue(() => createElementFromConfig("c:f46b58dc", TemplateContent, { content: props.renderCell ? props.renderCell(cell) : renderTemplate(cell.column.columnDef.cell, cell.getContext()) ?? cell.renderValue?.() })), "c:355196b8"))]));
|
|
209
|
+
const cls = "vx-table-row" + (props.onRowPress ? " vx-table-row--pressable" : "") + (row.getIsSelected?.() ? " vx-table-row--selected" : "");
|
|
210
|
+
const id = props.idPrefix ? `${props.idPrefix}-row-${row.id}` : void 0;
|
|
211
|
+
const content = props.onRowPress ? createScopedValue(() => createScopedElement(Pressable, {
|
|
212
|
+
id: props.selection ? void 0 : id,
|
|
213
|
+
className: "vx-datagrid-row-action",
|
|
214
|
+
onPress: () => props.onRowPress(row),
|
|
215
|
+
accessibilityLabel: props.getRowLabel?.(row) ?? row.id,
|
|
216
|
+
accessibilityHint: props.position.label,
|
|
217
|
+
accessibilityState: { selected: row.getIsSelected?.() || void 0 }
|
|
218
|
+
}, () => inner, "c:738701a6")) : inner;
|
|
219
|
+
return isRenderCall(__renderScope$) ? DataGridBodyRow$record(cls, content, id, props, row) : deferRecord(DataGridBodyRow$record, cls, content, id, props, row);
|
|
220
|
+
}
|
|
221
|
+
function DataGrid(props, __renderScope$2 = void 0) {
|
|
222
|
+
const { table, state } = withSlot(_h$0, useTable, {
|
|
223
|
+
...props.options,
|
|
224
|
+
defaultColumn: props.showColumnFilters ? {
|
|
225
|
+
filterFn: "includesString",
|
|
226
|
+
...props.options?.defaultColumn
|
|
227
|
+
} : props.options?.defaultColumn,
|
|
228
|
+
data: props.data,
|
|
229
|
+
columns: props.columns,
|
|
230
|
+
features: props.features,
|
|
231
|
+
initialState: props.initialState,
|
|
232
|
+
state: props.state,
|
|
233
|
+
getRowId: props.getRowId
|
|
234
|
+
});
|
|
235
|
+
const headerGroups = table.getHeaderGroups();
|
|
236
|
+
const rows = table.getRowModel().rows;
|
|
237
|
+
const rowCount = props.options?.manualPagination && props.options.rowCount == null ? -1 : table.getRowCount();
|
|
238
|
+
const ariaRowCount = rowCount < 0 ? -1 : headerGroups.length + rowCount;
|
|
239
|
+
return isRenderCall(__renderScope$2) ? DataGrid$record(ariaRowCount, headerGroups, props, rowCount, rows, state, table) : deferRecord(DataGrid$record, ariaRowCount, headerGroups, props, rowCount, rows, state, table);
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
export { DataGrid };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { moveColumn } from "./grid-layout.js";
|
|
2
|
+
import { createScopedElement, createScopedValue, hookSlots, positionalChildren, useState } from "octane";
|
|
3
|
+
import { createElementFromConfig, deferRecord, isRenderCall } from "octane/internal/client";
|
|
4
|
+
import { CheckboxInput, HStack, Pressable, Text, TextInput, View } from "@octane-xplat/ui";
|
|
5
|
+
//#region src/GridControls.tsrx
|
|
6
|
+
function Action$record(props) {
|
|
7
|
+
return createScopedElement(Pressable, {
|
|
8
|
+
id: props.id,
|
|
9
|
+
className: "vx-datagrid-action",
|
|
10
|
+
disabled: props.disabled,
|
|
11
|
+
onPress: props.onPress,
|
|
12
|
+
accessibilityLabel: props.label
|
|
13
|
+
}, () => createScopedElement(Text, {}, () => props.label, "c:17360d36"), "c:a39c0448");
|
|
14
|
+
}
|
|
15
|
+
var _h$0 = /* @__PURE__ */ Symbol(/* @__PURE__ */ hookSlots(1));
|
|
16
|
+
function Action(props, __renderScope = void 0) {
|
|
17
|
+
return isRenderCall(__renderScope) ? Action$record(props) : deferRecord(Action$record, props);
|
|
18
|
+
}
|
|
19
|
+
function GridControls(props) {
|
|
20
|
+
const [settingsOpen, setSettingsOpen] = useState(false, _h$0);
|
|
21
|
+
props.state;
|
|
22
|
+
const table = props.table;
|
|
23
|
+
const columns = table.getAllLeafColumns();
|
|
24
|
+
const order = columns.map((column) => column.id);
|
|
25
|
+
const move = (id, index) => table.setColumnOrder(moveColumn(order, id, order[index]));
|
|
26
|
+
return createScopedElement(View, {}, () => positionalChildren([createScopedElement(HStack, { className: "vx-datagrid-controls" }, () => positionalChildren([props.search ? createScopedValue(() => createElementFromConfig("c:60d66d67", TextInput, {
|
|
27
|
+
id: props.id ? `${props.id}-search` : void 0,
|
|
28
|
+
className: "flex-1",
|
|
29
|
+
value: String(props.state.globalFilter ?? ""),
|
|
30
|
+
placeholder: "Filter rows",
|
|
31
|
+
accessibilityLabel: "Filter rows",
|
|
32
|
+
onChange: (value) => {
|
|
33
|
+
table.setGlobalFilter(value);
|
|
34
|
+
table.setPageIndex(0);
|
|
35
|
+
}
|
|
36
|
+
})) : null, props.settings ? createScopedValue(() => createElementFromConfig("c:a0627d7f", Action, {
|
|
37
|
+
id: props.id ? `${props.id}-settings` : void 0,
|
|
38
|
+
label: "Columns",
|
|
39
|
+
onPress: () => setSettingsOpen(!settingsOpen)
|
|
40
|
+
})) : null]), "c:295b44ae"), props.settings && settingsOpen ? createScopedElement(View, { className: "vx-datagrid-settings" }, () => positionalChildren([columns.map((column, index) => createScopedElement(HStack, {
|
|
41
|
+
key: column.id,
|
|
42
|
+
className: "vx-datagrid-controls"
|
|
43
|
+
}, () => positionalChildren([
|
|
44
|
+
createScopedValue(() => createElementFromConfig("c:f756b0c1", CheckboxInput, {
|
|
45
|
+
id: props.id ? `${props.id}-visible-${column.id}` : void 0,
|
|
46
|
+
checked: column.getIsVisible(),
|
|
47
|
+
isDisabled: !column.getCanHide(),
|
|
48
|
+
label: typeof column.columnDef.header === "string" ? column.columnDef.header : column.id,
|
|
49
|
+
accessibilityLabel: `Show ${column.id}`,
|
|
50
|
+
onCheckedChange: (checked) => column.toggleVisibility(checked)
|
|
51
|
+
})),
|
|
52
|
+
createScopedValue(() => createElementFromConfig("c:c0929303", Action, {
|
|
53
|
+
id: props.id ? `${props.id}-earlier-${column.id}` : void 0,
|
|
54
|
+
label: `Move ${column.id} earlier`,
|
|
55
|
+
disabled: index === 0,
|
|
56
|
+
onPress: () => move(column.id, index - 1)
|
|
57
|
+
})),
|
|
58
|
+
createScopedValue(() => createElementFromConfig("c:40890231", Action, {
|
|
59
|
+
id: props.id ? `${props.id}-later-${column.id}` : void 0,
|
|
60
|
+
label: `Move ${column.id} later`,
|
|
61
|
+
disabled: index === columns.length - 1,
|
|
62
|
+
onPress: () => move(column.id, index + 1)
|
|
63
|
+
}))
|
|
64
|
+
]), "c:e5e4c754")), createScopedValue(() => createElementFromConfig("c:b982f7a4", Action, {
|
|
65
|
+
id: props.id ? `${props.id}-reset-columns` : void 0,
|
|
66
|
+
label: "Reset columns",
|
|
67
|
+
onPress: () => {
|
|
68
|
+
table.resetColumnVisibility();
|
|
69
|
+
table.resetColumnOrder();
|
|
70
|
+
table.resetColumnSizing();
|
|
71
|
+
}
|
|
72
|
+
}))]), "c:ec8b36b6") : null]), "c:fb4e7949");
|
|
73
|
+
}
|
|
74
|
+
function GridPagination(props) {
|
|
75
|
+
props.state;
|
|
76
|
+
const table = props.table;
|
|
77
|
+
const { pageIndex, pageSize } = props.state.pagination;
|
|
78
|
+
const count = table.getPageCount();
|
|
79
|
+
const sizes = [...new Set((props.sizes ?? [
|
|
80
|
+
10,
|
|
81
|
+
25,
|
|
82
|
+
50
|
|
83
|
+
]).filter((size) => Number.isInteger(size) && size > 0))];
|
|
84
|
+
return createScopedElement(HStack, {
|
|
85
|
+
className: "vx-datagrid-controls",
|
|
86
|
+
accessibilityLabel: "Pagination"
|
|
87
|
+
}, () => positionalChildren([
|
|
88
|
+
createScopedValue(() => createElementFromConfig("c:5836f3a5", Action, {
|
|
89
|
+
id: props.id ? `${props.id}-previous` : void 0,
|
|
90
|
+
label: "Previous page",
|
|
91
|
+
disabled: !table.getCanPreviousPage(),
|
|
92
|
+
onPress: () => table.previousPage()
|
|
93
|
+
})),
|
|
94
|
+
createScopedValue(() => createScopedElement(Text, { id: props.id ? `${props.id}-page` : void 0 }, () => count < 0 ? `Page ${pageIndex + 1}` : `Page ${count === 0 ? 0 : pageIndex + 1} of ${count}`, "c:2afe0489")),
|
|
95
|
+
createScopedValue(() => createElementFromConfig("c:e9944f84", Action, {
|
|
96
|
+
id: props.id ? `${props.id}-next` : void 0,
|
|
97
|
+
label: "Next page",
|
|
98
|
+
disabled: !table.getCanNextPage(),
|
|
99
|
+
onPress: () => table.nextPage()
|
|
100
|
+
})),
|
|
101
|
+
sizes.map((size) => createElementFromConfig("c:6ba759f0", Action, {
|
|
102
|
+
key: size,
|
|
103
|
+
id: props.id ? `${props.id}-size-${size}` : void 0,
|
|
104
|
+
label: `${size} per page`,
|
|
105
|
+
disabled: pageSize === size,
|
|
106
|
+
onPress: () => {
|
|
107
|
+
table.setPageSize(size);
|
|
108
|
+
table.setPageIndex(0);
|
|
109
|
+
}
|
|
110
|
+
}))
|
|
111
|
+
]), "c:efde622a");
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { GridControls, GridPagination };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/grid-accessibility.ts
|
|
2
|
+
/** Positions describe the final flat row model, before VirtualList windowing.
|
|
3
|
+
* Header rows count toward ARIA indices, but not the spoken data-row ordinal. */
|
|
4
|
+
function gridRowPosition(index, pagination, headerRows, rowCount) {
|
|
5
|
+
const ordinal = pagination.pageIndex * pagination.pageSize + index + 1;
|
|
6
|
+
return {
|
|
7
|
+
ariaIndex: headerRows + ordinal,
|
|
8
|
+
label: rowCount < 0 ? `Row ${ordinal}` : `Row ${ordinal} of ${rowCount}`
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { gridRowPosition };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/grid-layout.ts
|
|
2
|
+
/** Resolve the displayed width without changing flexible legacy columns. */
|
|
3
|
+
function columnStyle(column, fixed, sizing) {
|
|
4
|
+
return fixed || column.columnDef.size != null || sizing[column.id] != null ? {
|
|
5
|
+
width: column.getSize(),
|
|
6
|
+
flexGrow: 0,
|
|
7
|
+
flexShrink: 0
|
|
8
|
+
} : {
|
|
9
|
+
flexGrow: 1,
|
|
10
|
+
flexShrink: 1
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Move a column within the complete order, preserving hidden columns. */
|
|
14
|
+
function moveColumn(order, source, target) {
|
|
15
|
+
const from = order.indexOf(source);
|
|
16
|
+
const to = order.indexOf(target);
|
|
17
|
+
if (from < 0 || to < 0 || from === to) return order;
|
|
18
|
+
const next = [...order];
|
|
19
|
+
next.splice(from, 1);
|
|
20
|
+
next.splice(to, 0, source);
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
/** Find the visible column whose midpoint a horizontal drag has crossed. */
|
|
24
|
+
function dragTarget(columns, id, dx) {
|
|
25
|
+
const from = columns.findIndex((column) => column.id === id);
|
|
26
|
+
if (from < 0 || !Number.isFinite(dx)) return id;
|
|
27
|
+
let distance = columns[from].getSize() / 2;
|
|
28
|
+
let target = id;
|
|
29
|
+
const direction = dx < 0 ? -1 : 1;
|
|
30
|
+
for (let i = from + direction; i >= 0 && i < columns.length; i += direction) {
|
|
31
|
+
distance += columns[i].getSize() / 2;
|
|
32
|
+
if (Math.abs(dx) < distance) break;
|
|
33
|
+
target = columns[i].id;
|
|
34
|
+
distance += columns[i].getSize() / 2;
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
/** Clamp interactive widths to the column's configured bounds. */
|
|
39
|
+
function resizedWidth(column, width) {
|
|
40
|
+
return Math.max(column.columnDef.minSize ?? 20, Math.min(column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER, width));
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { columnStyle, dragTarget, moveColumn, resizedWidth };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { aggregationFns, columnFacetingFeature, columnFilteringFeature, columnGroupingFeature, columnOrderingFeature, columnPinningFeature, columnSizingFeature, columnVisibilityFeature, constructTable, createExpandedRowModel, createFacetedMinMaxValues, createFacetedRowModel, createFacetedUniqueValues, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, filterFns, globalFilteringFeature, rowAggregationFeature, rowExpandingFeature, rowPaginationFeature, rowPinningFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures } from "@tanstack/table-core";
|
|
2
|
+
import { storeReactivityBindings } from "@tanstack/table-core/store-reactivity-bindings";
|
|
3
|
+
//#region src/table.ts
|
|
4
|
+
/** DataGrid's default feature slots: every stock feature whose machinery is
|
|
5
|
+
* DOM-free — sorting, filtering, grouping, expanding, pagination, pinning,
|
|
6
|
+
* selection, column ordering/visibility/sizing, faceting, global filtering,
|
|
7
|
+
* aggregation — plus the row-model factories and builtin fn registries that
|
|
8
|
+
* v9 keeps as separate slots. Omitted: `columnResizingFeature` (its drag
|
|
9
|
+
* handler installs global event listeners — web-only; resize gesture UI
|
|
10
|
+
* belongs to platform leaves) and `cellSelectionFeature`/`cellSpanningFeature`
|
|
11
|
+
* (range-selection UX is web-spreadsheet-shaped, not a v1 xplat idiom). Pass
|
|
12
|
+
* `features` to add them where a platform leaf supports them. */
|
|
13
|
+
var dataGridFeatures = tableFeatures({
|
|
14
|
+
columnFacetingFeature,
|
|
15
|
+
columnFilteringFeature,
|
|
16
|
+
columnGroupingFeature,
|
|
17
|
+
columnOrderingFeature,
|
|
18
|
+
columnPinningFeature,
|
|
19
|
+
columnSizingFeature,
|
|
20
|
+
columnVisibilityFeature,
|
|
21
|
+
globalFilteringFeature,
|
|
22
|
+
rowAggregationFeature,
|
|
23
|
+
rowExpandingFeature,
|
|
24
|
+
rowPaginationFeature,
|
|
25
|
+
rowPinningFeature,
|
|
26
|
+
rowSelectionFeature,
|
|
27
|
+
rowSortingFeature,
|
|
28
|
+
sortedRowModel: createSortedRowModel(),
|
|
29
|
+
filteredRowModel: createFilteredRowModel(),
|
|
30
|
+
groupedRowModel: createGroupedRowModel(),
|
|
31
|
+
expandedRowModel: createExpandedRowModel(),
|
|
32
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
33
|
+
facetedRowModel: createFacetedRowModel(),
|
|
34
|
+
facetedMinMaxValues: createFacetedMinMaxValues(),
|
|
35
|
+
facetedUniqueValues: createFacetedUniqueValues(),
|
|
36
|
+
sortFns,
|
|
37
|
+
filterFns,
|
|
38
|
+
aggregationFns
|
|
39
|
+
});
|
|
40
|
+
/** Construct a table instance bound to TanStack Store reactivity — the
|
|
41
|
+
* vanilla/non-framework binding (`table.optionsStore` subscribable, atoms
|
|
42
|
+
* from `@tanstack/store`, `queueMicrotask` scheduling — all DOM-free). Wrap
|
|
43
|
+
* with `useTable` inside a component; call directly for non-rendering
|
|
44
|
+
* consumers (tests, headless row models). */
|
|
45
|
+
function createTable(options) {
|
|
46
|
+
return constructTable({
|
|
47
|
+
...options,
|
|
48
|
+
features: {
|
|
49
|
+
coreReactivityFeature: storeReactivityBindings(),
|
|
50
|
+
...dataGridFeatures,
|
|
51
|
+
...options.features
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { createTable, dataGridFeatures };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createTable } from "./table.js";
|
|
2
|
+
import { hookSlots, useState, useSyncExternalStore } from "octane";
|
|
3
|
+
import { memoPublish, memoSlot, memoTake1 } from "octane/internal/client";
|
|
4
|
+
//#region src/useTable.tsrx
|
|
5
|
+
var _hs$ = /* @__PURE__ */ hookSlots(3);
|
|
6
|
+
var _h$0 = /* @__PURE__ */ Symbol(_hs$);
|
|
7
|
+
var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
|
|
8
|
+
var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
|
|
9
|
+
function useTable(options) {
|
|
10
|
+
let __hks, __hkd0, __hke;
|
|
11
|
+
const [{ table }] = useState(() => ({ table: createTable(options) }), _h$0);
|
|
12
|
+
table.setOptions((prev) => ({
|
|
13
|
+
...prev,
|
|
14
|
+
...options
|
|
15
|
+
}));
|
|
16
|
+
const subscribe = (__hkd0 = table, __hks = memoSlot(_h$1, "useCallback"), __hke = memoTake1(__hks, __hkd0), __hke === null ? memoPublish(__hks, (notify) => {
|
|
17
|
+
const sub = table.store.subscribe(() => notify());
|
|
18
|
+
return () => sub.unsubscribe();
|
|
19
|
+
}, __hkd0) : __hke.value);
|
|
20
|
+
return {
|
|
21
|
+
table,
|
|
22
|
+
state: useSyncExternalStore(subscribe, () => table.store.get(), _h$2)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { useTable };
|
package/package.json
CHANGED
|
@@ -1,11 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/table",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Headless data grid for Octane xplat — @tanstack/table-core row model (sorting, filtering, pagination, selection, grouping) rendered through shared VirtualList on web, iOS, Android, and macOS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"android",
|
|
7
|
+
"cross-platform",
|
|
8
|
+
"data-grid",
|
|
9
|
+
"datagrid",
|
|
10
|
+
"ios",
|
|
11
|
+
"nativescript",
|
|
12
|
+
"octane",
|
|
13
|
+
"table",
|
|
14
|
+
"tanstack",
|
|
15
|
+
"web",
|
|
16
|
+
"xplat"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/table",
|
|
5
19
|
"license": "MIT",
|
|
6
20
|
"repository": {
|
|
7
21
|
"type": "git",
|
|
8
22
|
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
9
23
|
"directory": "packages/table"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"types",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"macos": {
|
|
34
|
+
"types": "./types/index.macos.d.ts",
|
|
35
|
+
"default": "./src/index.macos.ts"
|
|
36
|
+
},
|
|
37
|
+
"types": "./types/index.d.ts",
|
|
38
|
+
"web": "./dist/web/index.js",
|
|
39
|
+
"native": "./dist/native/index.js",
|
|
40
|
+
"default": "./dist/native/index.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@tanstack/table-core": "9.2.4"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@nativescript-community/octane": "0.2.4",
|
|
51
|
+
"@nativescript/core": "9.1.2",
|
|
52
|
+
"@octane-xplat/ui": "0.10.0",
|
|
53
|
+
"@octanejs/vite-plugin": "0.1.61",
|
|
54
|
+
"octane": "0.6.3",
|
|
55
|
+
"tsrx-typegen": "0.2.2",
|
|
56
|
+
"typescript": "5.9.3",
|
|
57
|
+
"vite": "8.3.0",
|
|
58
|
+
"vitest": "5.0.1"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
62
|
+
"@nativescript/core": ">=9.1.0 <10",
|
|
63
|
+
"@octane-xplat/ui": ">=0.6.0 <1",
|
|
64
|
+
"octane": ">=0.6.3 <1"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@nativescript-community/octane": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@nativescript/core": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "vite build && vite build --mode native && pnpm typegen && node ../../scripts/check-native-dist.mjs",
|
|
76
|
+
"test": "vitest run",
|
|
77
|
+
"test:native": "vitest run --config vitest.native.config.mts",
|
|
78
|
+
"typegen": "tsc -p tsconfig.types.json",
|
|
79
|
+
"test:packed": "node tests/packed-consumer.mjs",
|
|
80
|
+
"pack:check": "tsrx-typegen --pack-check",
|
|
81
|
+
"typecheck": "tsrx-tsc --noEmit -p tsconfig.web.json && tsrx-tsc --noEmit -p tsconfig.native.json"
|
|
10
82
|
}
|
|
11
|
-
}
|
|
83
|
+
}
|