@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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +427 -3
  3. package/dist/native/ColumnDrag.tsrx.js +237 -0
  4. package/dist/native/DataGrid.tsrx.js +957 -0
  5. package/dist/native/GridControls.tsrx.js +424 -0
  6. package/dist/native/grid-accessibility.js +12 -0
  7. package/dist/native/grid-layout.js +43 -0
  8. package/dist/native/index.js +4 -0
  9. package/dist/native/octane-client-build.json +8 -0
  10. package/dist/native/table.js +56 -0
  11. package/dist/native/useTable.tsrx.js +24 -0
  12. package/dist/web/ColumnDrag.tsrx.js +85 -0
  13. package/dist/web/DataGrid.tsrx.js +242 -0
  14. package/dist/web/GridControls.tsrx.js +114 -0
  15. package/dist/web/grid-accessibility.js +12 -0
  16. package/dist/web/grid-layout.js +43 -0
  17. package/dist/web/index.js +4 -0
  18. package/dist/web/octane-client-build.json +8 -0
  19. package/dist/web/table.js +56 -0
  20. package/dist/web/useTable.tsrx.js +26 -0
  21. package/package.json +75 -3
  22. package/src/ColumnDrag.tsrx +123 -0
  23. package/src/DataGrid.mobile.test.tsrx +55 -0
  24. package/src/DataGrid.tsrx +350 -0
  25. package/src/DataGrid.web.test.tsrx +277 -0
  26. package/src/GridControls.tsrx +157 -0
  27. package/src/grid-accessibility.ts +14 -0
  28. package/src/grid-layout.test.ts +32 -0
  29. package/src/grid-layout.ts +52 -0
  30. package/src/index.macos.ts +8 -0
  31. package/src/index.ts +7 -0
  32. package/src/index.web.ts +8 -0
  33. package/src/props.ts +122 -0
  34. package/src/table.test.ts +114 -0
  35. package/src/table.ts +91 -0
  36. package/src/useTable.tsrx +34 -0
  37. package/types/index.d.ts +20 -0
  38. package/types/index.macos.d.ts +17 -0
  39. package/types/props.d.ts +96 -0
  40. 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,4 @@
1
+ import { createTable, dataGridFeatures } from "./table.js";
2
+ import { useTable } from "./useTable.tsrx.js";
3
+ import { DataGrid } from "./DataGrid.tsrx.js";
4
+ export { DataGrid, createTable, dataGridFeatures, useTable };
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "buildId": "68b032a6-82e9-429a-a3f0-e40fb7c4e27d",
4
+ "mode": "production",
5
+ "capabilities": {
6
+ "independentHydration": false
7
+ }
8
+ }
@@ -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.1",
4
- "description": "Placeholder stub for @octane-xplat/table — no supported API yet.",
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
+ }