@vueye-table/vue 3.0.0-alpha.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brahim Boussadjra and vueye-table contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @vueye-table/vue
2
+
3
+ Vue composables for the vueye-table engine. `useDataTable` binds a table to the current effect
4
+ scope and exposes its snapshot as plain reactive properties beside named operations;
5
+ `useDataGrid` adds spreadsheet selection, keyboard navigation, and in-cell editing.
6
+
7
+ ```bash
8
+ pnpm add @vueye-table/vue vue
9
+ ```
10
+
11
+ ```ts
12
+ import { useDataTable } from "@vueye-table/vue";
13
+
14
+ const table = useDataTable({ data: () => props.users, columns });
15
+ table.rows; // the current page, reactive
16
+ table.nextPage();
17
+ ```
18
+
19
+ See the [repository README](https://github.com/boussadjra/vueye-table#readme) for every layer.
@@ -0,0 +1,96 @@
1
+ import { ComputedRef, MaybeRefOrGetter } from "vue";
2
+ import { CellPosition, CellRange, ColumnDef, DataTable, EditResult, GridBounds, GridDirection, GridKey, GridSelection, MoveOptions, TableColumn, TableOptions, TableRow, TableSnapshot, TableStatePatch } from "@vueye-table/core";
3
+ //#region src/use-data-table.d.ts
4
+ /** Options accepted by {@link useDataTable}. Data, columns, and state may be refs or getters. */
5
+ interface UseDataTableOptions<TRow> extends Omit<TableOptions<TRow>, "data" | "columns" | "rowCount"> {
6
+ readonly data: MaybeRefOrGetter<readonly TRow[]>;
7
+ readonly columns: MaybeRefOrGetter<readonly ColumnDef<TRow>[]>;
8
+ readonly rowCount?: MaybeRefOrGetter<number | undefined> | undefined;
9
+ /**
10
+ * State owned by the caller, for example `v-model` props or URL query values. Whenever it
11
+ * changes it is applied with `setState`; the table's own changes still reach `onStateChange`.
12
+ */
13
+ readonly state?: MaybeRefOrGetter<TableStatePatch | undefined> | undefined;
14
+ }
15
+ type TableOperations<TRow> = Omit<DataTable<TRow>, "getSnapshot" | "getState" | "subscribe">;
16
+ /**
17
+ * A reactive view of a table: every snapshot field is a plain reactive property, so templates read
18
+ * `table.rows` and `table.pageCount` directly, and every change goes through a named operation.
19
+ * Watching a field needs a getter: `watch(() => table.page, ...)`.
20
+ */
21
+ type DataTableBinding<TRow> = TableSnapshot<TRow> & TableOperations<TRow> & {
22
+ /** The engine behind the binding. */
23
+ readonly table: DataTable<TRow>;
24
+ /** The current snapshot, replaced on every change. */
25
+ readonly snapshot: TableSnapshot<TRow>;
26
+ };
27
+ /**
28
+ * A binding for any row type, for components that accept every table. Row types make the binding
29
+ * invariant, so `DataTableBinding<unknown>` would refuse `DataTableBinding<Person>`.
30
+ */
31
+ type AnyDataTableBinding = DataTableBinding<any>;
32
+ /**
33
+ * Create a table bound to the current effect scope. The table follows its reactive data,
34
+ * columns, row count, and state, and stops listening when the scope ends.
35
+ */
36
+ declare function useDataTable<TRow>(options: UseDataTableOptions<TRow>): DataTableBinding<TRow>;
37
+ //#endregion
38
+ //#region src/use-data-grid.d.ts
39
+ /** The cell being edited and the text typed so far. */
40
+ interface GridEditor {
41
+ readonly position: CellPosition;
42
+ readonly draft: string;
43
+ }
44
+ /**
45
+ * Spreadsheet interaction over a table: a selected range, keyboard navigation, in-cell editing,
46
+ * copy, paste, and clearing. Positions refer to the current page and the visible columns.
47
+ */
48
+ interface DataGridBinding<TRow> {
49
+ readonly selection: GridSelection | undefined;
50
+ readonly range: CellRange | undefined;
51
+ readonly editor: GridEditor | undefined;
52
+ readonly bounds: ComputedRef<GridBounds>;
53
+ /** The latest edit outcome, to show a rejected value. */
54
+ readonly lastResult: EditResult<TRow> | undefined;
55
+ cellAt(position: CellPosition): {
56
+ row: TableRow<TRow>;
57
+ column: TableColumn<TRow>;
58
+ } | undefined;
59
+ isFocused(position: CellPosition): boolean;
60
+ isSelected(position: CellPosition): boolean;
61
+ isEditing(position: CellPosition): boolean;
62
+ focusCell(position: CellPosition, options?: {
63
+ readonly extend?: boolean | undefined;
64
+ }): void;
65
+ move(direction: GridDirection, options?: MoveOptions): void;
66
+ selectAll(): void;
67
+ /** Start editing the focused cell, with its current text or with `initial`. */
68
+ startEdit(initial?: string): boolean;
69
+ updateDraft(draft: string): void;
70
+ /** Write the draft through the table, then move the focus if asked. */
71
+ commitEdit(then?: GridDirection): EditResult<TRow> | undefined;
72
+ cancelEdit(): void;
73
+ /** The selected range as tab-separated text. */
74
+ copy(): string;
75
+ /** Paste tab-separated text at the top-left of the selection. */
76
+ paste(text: string): EditResult<TRow> | undefined;
77
+ clear(): EditResult<TRow> | undefined;
78
+ /** Handle a key press. Returns `true` when the grid used it, so the caller can prevent the default. */
79
+ handleKey(event: GridKey): boolean;
80
+ }
81
+ declare function useDataGrid<TRow>(table: DataTableBinding<TRow>): DataGridBinding<TRow>;
82
+ //#endregion
83
+ //#region src/injection.d.ts
84
+ /** Make a table available to every component below the current one. */
85
+ declare function provideDataTable<TRow>(table: DataTableBinding<TRow>): void;
86
+ /**
87
+ * Read the table provided by an ancestor. Throws when there is none, naming the component that
88
+ * provides one, rather than rendering an empty table.
89
+ */
90
+ declare function injectDataTable<TRow = unknown>(consumer?: string): DataTableBinding<TRow>;
91
+ declare function provideDataGrid<TRow>(grid: DataGridBinding<TRow>): void;
92
+ /** Read the grid provided by an ancestor, or `undefined` outside a grid. */
93
+ declare function injectDataGrid<TRow = unknown>(): DataGridBinding<TRow> | undefined;
94
+ //#endregion
95
+ export { type AnyDataTableBinding, type DataGridBinding, type DataTableBinding, type GridEditor, type UseDataTableOptions, injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useDataTable };
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/use-data-table.ts","../src/use-data-grid.ts","../src/injection.ts"],"mappings":";;;;UAmBiB,oBAAoB,cAAc,KACjD,aAAa;WAGJ,MAAM,0BAA0B;WAChC,SAAS,0BAA0B,UAAU;WAC7C,WAAW;;;;;WAKX,QAAQ,iBAAiB;;KAG/B,gBAAgB,QAAQ,KAAK,UAAU;;;;;;KAOhC,iBAAiB,QAAQ,cAAc,QACjD,gBAAgB;;WAEL,OAAO,UAAU;;WAEjB,UAAU,cAAc;;;;;;KAOzB,sBAAsB;;;;;iBA4ClB,aAAa,MAAM,SAAS,oBAAoB,QAAQ,iBAAiB;;;;UCzExE;WACN,UAAU;WACV;;;;;;UAOM,gBAAgB;WACtB,WAAW;WACX,OAAO;WACP,QAAQ;WACR,QAAQ,YAAY;;WAEpB,YAAY,WAAW;EAChC,OAAO,UAAU;IAAiB,KAAK,SAAS;IAAO,QAAQ,YAAY;;EAC3E,UAAU,UAAU;EACpB,WAAW,UAAU;EACrB,UAAU,UAAU;EACpB,UAAU,UAAU,cAAc;aAAqB;;EACvD,KAAK,WAAW,eAAe,UAAU;EACzC;;EAEA,UAAU;EACV,YAAY;;EAEZ,WAAW,OAAO,gBAAgB,WAAW;EAC7C;;EAEA;;EAEA,MAAM,eAAe,WAAW;EAChC,SAAS,WAAW;;EAEpB,UAAU,OAAO;;iBAGH,YAAY,MAAM,OAAO,iBAAiB,QAAQ,gBAAgB;;;;iBCpDlE,iBAAiB,MAAM,OAAO,iBAAiB;;;;;iBAQ/C,gBAAgB,gBAC9B,oBACC,iBAAiB;iBAUJ,gBAAgB,MAAM,MAAM,gBAAgB;;iBAK5C,eAAe,mBAAmB,gBAAgB"}
package/dist/index.js ADDED
@@ -0,0 +1,277 @@
1
+ import { computed, getCurrentScope, inject, onScopeDispose, provide, shallowRef, toRaw, toValue, watch } from "vue";
2
+ import { clampPosition, createTable, gridCommand, moveSelection, rangeContains, selectCell, selectionRange } from "@vueye-table/core";
3
+
4
+ //#region src/injection.ts
5
+ const tableKey = Symbol("vueye-table");
6
+ const gridKey = Symbol("vueye-table-grid");
7
+ /** Make a table available to every component below the current one. */
8
+ function provideDataTable(table) {
9
+ provide(tableKey, table);
10
+ }
11
+ /**
12
+ * Read the table provided by an ancestor. Throws when there is none, naming the component that
13
+ * provides one, rather than rendering an empty table.
14
+ */
15
+ function injectDataTable(consumer = "This component") {
16
+ const table = inject(tableKey, void 0);
17
+ if (!table) throw new Error(`${consumer} needs a table. Render it inside <DataTableRoot :table="table">, or call provideDataTable(useDataTable(...)) in an ancestor.`);
18
+ return table;
19
+ }
20
+ function provideDataGrid(grid) {
21
+ provide(gridKey, grid);
22
+ }
23
+ /** Read the grid provided by an ancestor, or `undefined` outside a grid. */
24
+ function injectDataGrid() {
25
+ return inject(gridKey, void 0);
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/use-data-grid.ts
30
+ function useDataGrid(table) {
31
+ const selection = shallowRef();
32
+ const editor = shallowRef();
33
+ const lastResult = shallowRef();
34
+ const bounds = computed(() => ({
35
+ rows: table.rows.length,
36
+ columns: table.columns.length
37
+ }));
38
+ const range = computed(() => selection.value ? selectionRange(selection.value) : void 0);
39
+ const same = (left, right) => left !== void 0 && left.row === right.row && left.column === right.column;
40
+ watch(bounds, (next) => {
41
+ const current = selection.value;
42
+ if (current && (next.rows === 0 || next.columns === 0)) selection.value = void 0;
43
+ else if (current) {
44
+ const anchor = clampPosition(current.anchor, next);
45
+ const focus = clampPosition(current.focus, next);
46
+ if (!same(current.anchor, anchor) || !same(current.focus, focus)) selection.value = {
47
+ anchor,
48
+ focus
49
+ };
50
+ }
51
+ const position = editor.value?.position;
52
+ if (position && (position.row >= next.rows || position.column >= next.columns)) editor.value = void 0;
53
+ });
54
+ const cellAt = (position) => {
55
+ const row = table.rows[position.row];
56
+ const column = table.columns[position.column];
57
+ return row && column ? {
58
+ row,
59
+ column
60
+ } : void 0;
61
+ };
62
+ const ensureSelection = () => {
63
+ if (bounds.value.rows === 0 || bounds.value.columns === 0) return;
64
+ selection.value ??= selectCell({
65
+ row: 0,
66
+ column: 0
67
+ });
68
+ return selection.value;
69
+ };
70
+ const grid = {
71
+ get selection() {
72
+ return selection.value;
73
+ },
74
+ get range() {
75
+ return range.value;
76
+ },
77
+ get editor() {
78
+ return editor.value;
79
+ },
80
+ get lastResult() {
81
+ return lastResult.value;
82
+ },
83
+ bounds,
84
+ cellAt,
85
+ isFocused: (position) => same(selection.value?.focus, position),
86
+ isSelected: (position) => range.value ? rangeContains(range.value, position) : false,
87
+ isEditing: (position) => same(editor.value?.position, position),
88
+ focusCell(position, { extend = false } = {}) {
89
+ const target = clampPosition(position, bounds.value);
90
+ if (editor.value && !same(editor.value.position, target)) grid.commitEdit();
91
+ selection.value = extend && selection.value ? {
92
+ anchor: selection.value.anchor,
93
+ focus: target
94
+ } : selectCell(target);
95
+ },
96
+ move(direction, options) {
97
+ const current = ensureSelection();
98
+ if (current) selection.value = moveSelection(current, direction, bounds.value, options);
99
+ },
100
+ selectAll() {
101
+ if (bounds.value.rows > 0 && bounds.value.columns > 0) selection.value = {
102
+ anchor: {
103
+ row: 0,
104
+ column: 0
105
+ },
106
+ focus: {
107
+ row: bounds.value.rows - 1,
108
+ column: bounds.value.columns - 1
109
+ }
110
+ };
111
+ },
112
+ startEdit(initial) {
113
+ const focus = ensureSelection()?.focus;
114
+ const cell = focus ? cellAt(focus) : void 0;
115
+ if (!focus || !cell || !cell.column.isEditable(cell.row.original)) return false;
116
+ editor.value = {
117
+ position: focus,
118
+ draft: initial ?? cell.row.getDisplay(cell.column.id)
119
+ };
120
+ return true;
121
+ },
122
+ updateDraft(draft) {
123
+ if (editor.value) editor.value = {
124
+ position: editor.value.position,
125
+ draft
126
+ };
127
+ },
128
+ commitEdit(then) {
129
+ const current = editor.value;
130
+ if (!current) return;
131
+ const cell = cellAt(current.position);
132
+ editor.value = void 0;
133
+ if (!cell) return;
134
+ const result = table.edit({
135
+ rowKey: cell.row.key,
136
+ column: cell.column.id,
137
+ input: current.draft
138
+ });
139
+ lastResult.value = result;
140
+ if (then) grid.move(then);
141
+ return result;
142
+ },
143
+ cancelEdit() {
144
+ editor.value = void 0;
145
+ },
146
+ copy() {
147
+ return range.value ? table.copy(range.value) : "";
148
+ },
149
+ paste(text) {
150
+ if (!range.value) return;
151
+ const result = table.paste({
152
+ row: range.value.top,
153
+ column: range.value.left
154
+ }, text);
155
+ lastResult.value = result;
156
+ return result;
157
+ },
158
+ clear() {
159
+ if (!range.value) return;
160
+ const result = table.clear(range.value);
161
+ lastResult.value = result;
162
+ return result;
163
+ },
164
+ handleKey(event) {
165
+ if (editor.value) return false;
166
+ const command = gridCommand(event);
167
+ switch (command?.type) {
168
+ case "move":
169
+ grid.move(command.direction, command.options);
170
+ return true;
171
+ case "edit": return grid.startEdit(command.initial);
172
+ case "clear":
173
+ grid.clear();
174
+ return true;
175
+ case "selectAll":
176
+ grid.selectAll();
177
+ return true;
178
+ case "undo": return table.undo();
179
+ case "redo": return table.redo();
180
+ case "cancel":
181
+ if (selection.value) selection.value = selectCell(selection.value.focus);
182
+ return true;
183
+ default: return false;
184
+ }
185
+ }
186
+ };
187
+ return grid;
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/use-data-table.ts
192
+ const OPERATIONS = [
193
+ "setState",
194
+ "setData",
195
+ "setColumns",
196
+ "setRowCount",
197
+ "toggleSort",
198
+ "sort",
199
+ "clearSorting",
200
+ "search",
201
+ "filter",
202
+ "clearFilters",
203
+ "goToPage",
204
+ "nextPage",
205
+ "previousPage",
206
+ "setPageSize",
207
+ "select",
208
+ "deselect",
209
+ "toggleRow",
210
+ "toggleAll",
211
+ "clearSelection",
212
+ "getSelectedRows",
213
+ "toggleColumn",
214
+ "moveColumn",
215
+ "edit",
216
+ "undo",
217
+ "redo",
218
+ "copy",
219
+ "paste",
220
+ "clear",
221
+ "exportRows",
222
+ "reset",
223
+ "destroy"
224
+ ];
225
+ function definedEntries(patch) {
226
+ return Object.fromEntries(Object.entries(patch ?? {}).filter(([, value]) => value !== void 0));
227
+ }
228
+ /**
229
+ * Create a table bound to the current effect scope. The table follows its reactive data,
230
+ * columns, row count, and state, and stops listening when the scope ends.
231
+ */
232
+ function useDataTable(options) {
233
+ const table = createTable({
234
+ ...options,
235
+ data: toRaw(toValue(options.data)),
236
+ columns: toValue(options.columns),
237
+ rowCount: toValue(options.rowCount),
238
+ initialState: {
239
+ ...options.initialState,
240
+ ...definedEntries(toValue(options.state))
241
+ }
242
+ });
243
+ const snapshot = shallowRef(table.getSnapshot());
244
+ const unsubscribe = table.subscribe((next) => {
245
+ snapshot.value = next;
246
+ });
247
+ const stops = [
248
+ watch(() => toValue(options.data), (data) => table.setData(toRaw(data))),
249
+ watch(() => toValue(options.columns), (columns) => table.setColumns(columns)),
250
+ watch(() => toValue(options.rowCount), (rowCount) => {
251
+ if (rowCount !== void 0) table.setRowCount(rowCount);
252
+ }),
253
+ watch(() => toValue(options.state), (state) => {
254
+ if (state) table.setState(state);
255
+ }, { deep: true })
256
+ ];
257
+ if (getCurrentScope()) onScopeDispose(() => {
258
+ for (const stop of stops) stop();
259
+ unsubscribe();
260
+ table.destroy();
261
+ });
262
+ const binding = { table };
263
+ Object.defineProperty(binding, "snapshot", {
264
+ enumerable: true,
265
+ get: () => snapshot.value
266
+ });
267
+ for (const key of Object.keys(snapshot.value)) Object.defineProperty(binding, key, {
268
+ enumerable: true,
269
+ get: () => snapshot.value[key]
270
+ });
271
+ for (const name of OPERATIONS) binding[name] = table[name];
272
+ return Object.freeze(binding);
273
+ }
274
+
275
+ //#endregion
276
+ export { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useDataTable };
277
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/injection.ts","../src/use-data-grid.ts","../src/use-data-table.ts"],"sourcesContent":["import { inject, provide, type InjectionKey } from \"vue\";\n\nimport type { DataGridBinding } from \"./use-data-grid\";\nimport type { DataTableBinding } from \"./use-data-table\";\n\nconst tableKey: InjectionKey<DataTableBinding<unknown>> = Symbol(\"vueye-table\");\nconst gridKey: InjectionKey<DataGridBinding<unknown>> = Symbol(\"vueye-table-grid\");\n\n/** Make a table available to every component below the current one. */\nexport function provideDataTable<TRow>(table: DataTableBinding<TRow>): void {\n provide(tableKey, table as DataTableBinding<unknown>);\n}\n\n/**\n * Read the table provided by an ancestor. Throws when there is none, naming the component that\n * provides one, rather than rendering an empty table.\n */\nexport function injectDataTable<TRow = unknown>(\n consumer = \"This component\",\n): DataTableBinding<TRow> {\n const table = inject(tableKey, undefined);\n if (!table) {\n throw new Error(\n `${consumer} needs a table. Render it inside <DataTableRoot :table=\"table\">, or call provideDataTable(useDataTable(...)) in an ancestor.`,\n );\n }\n return table as DataTableBinding<TRow>;\n}\n\nexport function provideDataGrid<TRow>(grid: DataGridBinding<TRow>): void {\n provide(gridKey, grid as DataGridBinding<unknown>);\n}\n\n/** Read the grid provided by an ancestor, or `undefined` outside a grid. */\nexport function injectDataGrid<TRow = unknown>(): DataGridBinding<TRow> | undefined {\n return inject(gridKey, undefined) as DataGridBinding<TRow> | undefined;\n}\n","import {\n clampPosition,\n gridCommand,\n moveSelection,\n rangeContains,\n selectCell,\n selectionRange,\n type CellPosition,\n type CellRange,\n type EditResult,\n type GridBounds,\n type GridDirection,\n type GridKey,\n type GridSelection,\n type MoveOptions,\n type TableColumn,\n type TableRow,\n} from \"@vueye-table/core\";\nimport { computed, shallowRef, watch, type ComputedRef } from \"vue\";\n\nimport type { DataTableBinding } from \"./use-data-table\";\n\n/** The cell being edited and the text typed so far. */\nexport interface GridEditor {\n readonly position: CellPosition;\n readonly draft: string;\n}\n\n/**\n * Spreadsheet interaction over a table: a selected range, keyboard navigation, in-cell editing,\n * copy, paste, and clearing. Positions refer to the current page and the visible columns.\n */\nexport interface DataGridBinding<TRow> {\n readonly selection: GridSelection | undefined;\n readonly range: CellRange | undefined;\n readonly editor: GridEditor | undefined;\n readonly bounds: ComputedRef<GridBounds>;\n /** The latest edit outcome, to show a rejected value. */\n readonly lastResult: EditResult<TRow> | undefined;\n cellAt(position: CellPosition): { row: TableRow<TRow>; column: TableColumn<TRow> } | undefined;\n isFocused(position: CellPosition): boolean;\n isSelected(position: CellPosition): boolean;\n isEditing(position: CellPosition): boolean;\n focusCell(position: CellPosition, options?: { readonly extend?: boolean | undefined }): void;\n move(direction: GridDirection, options?: MoveOptions): void;\n selectAll(): void;\n /** Start editing the focused cell, with its current text or with `initial`. */\n startEdit(initial?: string): boolean;\n updateDraft(draft: string): void;\n /** Write the draft through the table, then move the focus if asked. */\n commitEdit(then?: GridDirection): EditResult<TRow> | undefined;\n cancelEdit(): void;\n /** The selected range as tab-separated text. */\n copy(): string;\n /** Paste tab-separated text at the top-left of the selection. */\n paste(text: string): EditResult<TRow> | undefined;\n clear(): EditResult<TRow> | undefined;\n /** Handle a key press. Returns `true` when the grid used it, so the caller can prevent the default. */\n handleKey(event: GridKey): boolean;\n}\n\nexport function useDataGrid<TRow>(table: DataTableBinding<TRow>): DataGridBinding<TRow> {\n const selection = shallowRef<GridSelection | undefined>();\n const editor = shallowRef<GridEditor | undefined>();\n const lastResult = shallowRef<EditResult<TRow> | undefined>();\n const bounds = computed<GridBounds>(() => ({\n rows: table.rows.length,\n columns: table.columns.length,\n }));\n const range = computed(() => (selection.value ? selectionRange(selection.value) : undefined));\n\n const same = (left: CellPosition | undefined, right: CellPosition): boolean =>\n left !== undefined && left.row === right.row && left.column === right.column;\n\n // Searching, filtering, paging, or hiding a column can shrink the grid under the selection.\n // Keep the selection on cells that exist, and drop an edit whose cell is gone.\n watch(bounds, (next) => {\n const current = selection.value;\n if (current && (next.rows === 0 || next.columns === 0)) {\n selection.value = undefined;\n } else if (current) {\n const anchor = clampPosition(current.anchor, next);\n const focus = clampPosition(current.focus, next);\n if (!same(current.anchor, anchor) || !same(current.focus, focus)) {\n selection.value = { anchor, focus };\n }\n }\n const position = editor.value?.position;\n if (position && (position.row >= next.rows || position.column >= next.columns)) {\n editor.value = undefined;\n }\n });\n\n const cellAt: DataGridBinding<TRow>[\"cellAt\"] = (position) => {\n const row = table.rows[position.row];\n const column = table.columns[position.column];\n return row && column ? { row, column } : undefined;\n };\n\n const ensureSelection = (): GridSelection | undefined => {\n if (bounds.value.rows === 0 || bounds.value.columns === 0) {\n return undefined;\n }\n selection.value ??= selectCell({ row: 0, column: 0 });\n return selection.value;\n };\n\n const grid: DataGridBinding<TRow> = {\n get selection() {\n return selection.value;\n },\n get range() {\n return range.value;\n },\n get editor() {\n return editor.value;\n },\n get lastResult() {\n return lastResult.value;\n },\n bounds,\n cellAt,\n isFocused: (position) => same(selection.value?.focus, position),\n isSelected: (position) => (range.value ? rangeContains(range.value, position) : false),\n isEditing: (position) => same(editor.value?.position, position),\n focusCell(position, { extend = false } = {}) {\n const target = clampPosition(position, bounds.value);\n if (editor.value && !same(editor.value.position, target)) {\n grid.commitEdit();\n }\n selection.value =\n extend && selection.value\n ? { anchor: selection.value.anchor, focus: target }\n : selectCell(target);\n },\n move(direction, options) {\n const current = ensureSelection();\n if (current) {\n selection.value = moveSelection(current, direction, bounds.value, options);\n }\n },\n selectAll() {\n if (bounds.value.rows > 0 && bounds.value.columns > 0) {\n selection.value = {\n anchor: { row: 0, column: 0 },\n focus: { row: bounds.value.rows - 1, column: bounds.value.columns - 1 },\n };\n }\n },\n startEdit(initial) {\n const focus = ensureSelection()?.focus;\n const cell = focus ? cellAt(focus) : undefined;\n if (!focus || !cell || !cell.column.isEditable(cell.row.original)) {\n return false;\n }\n editor.value = { position: focus, draft: initial ?? cell.row.getDisplay(cell.column.id) };\n return true;\n },\n updateDraft(draft) {\n if (editor.value) {\n editor.value = { position: editor.value.position, draft };\n }\n },\n commitEdit(then) {\n const current = editor.value;\n if (!current) {\n return undefined;\n }\n const cell = cellAt(current.position);\n editor.value = undefined;\n if (!cell) {\n return undefined;\n }\n const result = table.edit({\n rowKey: cell.row.key,\n column: cell.column.id,\n input: current.draft,\n });\n lastResult.value = result;\n if (then) {\n grid.move(then);\n }\n return result;\n },\n cancelEdit() {\n editor.value = undefined;\n },\n copy() {\n return range.value ? table.copy(range.value) : \"\";\n },\n paste(text) {\n if (!range.value) {\n return undefined;\n }\n const result = table.paste({ row: range.value.top, column: range.value.left }, text);\n lastResult.value = result;\n return result;\n },\n clear() {\n if (!range.value) {\n return undefined;\n }\n const result = table.clear(range.value);\n lastResult.value = result;\n return result;\n },\n handleKey(event) {\n if (editor.value) {\n return false;\n }\n const command = gridCommand(event);\n switch (command?.type) {\n case \"move\":\n grid.move(command.direction, command.options);\n return true;\n case \"edit\":\n return grid.startEdit(command.initial);\n case \"clear\":\n grid.clear();\n return true;\n case \"selectAll\":\n grid.selectAll();\n return true;\n case \"undo\":\n return table.undo();\n case \"redo\":\n return table.redo();\n case \"cancel\":\n if (selection.value) {\n selection.value = selectCell(selection.value.focus);\n }\n return true;\n default:\n return false;\n }\n },\n };\n return grid;\n}\n","import {\n createTable,\n type ColumnDef,\n type DataTable,\n type TableOptions,\n type TableSnapshot,\n type TableStatePatch,\n} from \"@vueye-table/core\";\nimport {\n getCurrentScope,\n onScopeDispose,\n shallowRef,\n toRaw,\n toValue,\n watch,\n type MaybeRefOrGetter,\n} from \"vue\";\n\n/** Options accepted by {@link useDataTable}. Data, columns, and state may be refs or getters. */\nexport interface UseDataTableOptions<TRow> extends Omit<\n TableOptions<TRow>,\n \"data\" | \"columns\" | \"rowCount\"\n> {\n readonly data: MaybeRefOrGetter<readonly TRow[]>;\n readonly columns: MaybeRefOrGetter<readonly ColumnDef<TRow>[]>;\n readonly rowCount?: MaybeRefOrGetter<number | undefined> | undefined;\n /**\n * State owned by the caller, for example `v-model` props or URL query values. Whenever it\n * changes it is applied with `setState`; the table's own changes still reach `onStateChange`.\n */\n readonly state?: MaybeRefOrGetter<TableStatePatch | undefined> | undefined;\n}\n\ntype TableOperations<TRow> = Omit<DataTable<TRow>, \"getSnapshot\" | \"getState\" | \"subscribe\">;\n\n/**\n * A reactive view of a table: every snapshot field is a plain reactive property, so templates read\n * `table.rows` and `table.pageCount` directly, and every change goes through a named operation.\n * Watching a field needs a getter: `watch(() => table.page, ...)`.\n */\nexport type DataTableBinding<TRow> = TableSnapshot<TRow> &\n TableOperations<TRow> & {\n /** The engine behind the binding. */\n readonly table: DataTable<TRow>;\n /** The current snapshot, replaced on every change. */\n readonly snapshot: TableSnapshot<TRow>;\n };\n\n/**\n * A binding for any row type, for components that accept every table. Row types make the binding\n * invariant, so `DataTableBinding<unknown>` would refuse `DataTableBinding<Person>`.\n */\nexport type AnyDataTableBinding = DataTableBinding<any>;\n\nconst OPERATIONS = [\n \"setState\",\n \"setData\",\n \"setColumns\",\n \"setRowCount\",\n \"toggleSort\",\n \"sort\",\n \"clearSorting\",\n \"search\",\n \"filter\",\n \"clearFilters\",\n \"goToPage\",\n \"nextPage\",\n \"previousPage\",\n \"setPageSize\",\n \"select\",\n \"deselect\",\n \"toggleRow\",\n \"toggleAll\",\n \"clearSelection\",\n \"getSelectedRows\",\n \"toggleColumn\",\n \"moveColumn\",\n \"edit\",\n \"undo\",\n \"redo\",\n \"copy\",\n \"paste\",\n \"clear\",\n \"exportRows\",\n \"reset\",\n \"destroy\",\n] as const satisfies readonly (keyof TableOperations<unknown>)[];\n\nfunction definedEntries(patch: TableStatePatch | undefined): TableStatePatch {\n return Object.fromEntries(Object.entries(patch ?? {}).filter(([, value]) => value !== undefined));\n}\n\n/**\n * Create a table bound to the current effect scope. The table follows its reactive data,\n * columns, row count, and state, and stops listening when the scope ends.\n */\nexport function useDataTable<TRow>(options: UseDataTableOptions<TRow>): DataTableBinding<TRow> {\n const table = createTable<TRow>({\n ...options,\n data: toRaw(toValue(options.data)),\n columns: toValue(options.columns),\n rowCount: toValue(options.rowCount),\n initialState: { ...options.initialState, ...definedEntries(toValue(options.state)) },\n });\n const snapshot = shallowRef(table.getSnapshot());\n const unsubscribe = table.subscribe((next) => {\n snapshot.value = next;\n });\n\n const stops = [\n watch(\n () => toValue(options.data),\n // Reactive arrays arrive as proxies; the engine works on, and compares, the raw array.\n (data) => table.setData(toRaw(data)),\n ),\n watch(\n () => toValue(options.columns),\n (columns) => table.setColumns(columns),\n ),\n watch(\n () => toValue(options.rowCount),\n (rowCount) => {\n if (rowCount !== undefined) {\n table.setRowCount(rowCount);\n }\n },\n ),\n watch(\n () => toValue(options.state),\n (state) => {\n if (state) {\n table.setState(state);\n }\n },\n { deep: true },\n ),\n ];\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n for (const stop of stops) {\n stop();\n }\n unsubscribe();\n table.destroy();\n });\n }\n\n const binding = { table } as Record<string, unknown>;\n Object.defineProperty(binding, \"snapshot\", { enumerable: true, get: () => snapshot.value });\n for (const key of Object.keys(snapshot.value)) {\n Object.defineProperty(binding, key, {\n enumerable: true,\n get: () => snapshot.value[key as keyof TableSnapshot<TRow>],\n });\n }\n for (const name of OPERATIONS) {\n binding[name] = table[name];\n }\n return Object.freeze(binding) as unknown as DataTableBinding<TRow>;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@vueye-table/vue",
3
+ "version": "3.0.0-alpha.1",
4
+ "description": "Vue composables that bind the vueye-table engine to reactive state.",
5
+ "keywords": [
6
+ "composables",
7
+ "data-table",
8
+ "datagrid",
9
+ "spreadsheet",
10
+ "vue"
11
+ ],
12
+ "homepage": "https://github.com/boussadjra/vueye-table#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/boussadjra/vueye-table/issues"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/boussadjra/vueye-table.git",
20
+ "directory": "packages/vue"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "dependencies": {
41
+ "@vueye-table/core": "3.0.0-alpha.1"
42
+ },
43
+ "devDependencies": {
44
+ "vue": "3.5.43",
45
+ "@vueye-table/typescript-config": "0.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "vue": ">=3.5.0 <4"
49
+ },
50
+ "engines": {
51
+ "node": ">=22.12.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "dev": "tsdown --watch",
56
+ "typecheck": "tsc -p tsconfig.json",
57
+ "package:check": "publint --strict && attw --pack . --profile esm-only",
58
+ "clean": "node ../../scripts/clean-package.mjs"
59
+ }
60
+ }