@gradio/dataframe 0.22.0 → 0.23.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/CHANGELOG.md +24 -0
- package/Dataframe.test.ts +777 -31
- package/Index.svelte +107 -106
- package/dist/Index.svelte +107 -106
- package/dist/Index.svelte.d.ts +0 -1
- package/dist/shared/BooleanCell.svelte +12 -6
- package/dist/shared/BooleanCell.svelte.d.ts +4 -18
- package/dist/shared/CellMenu.svelte +74 -49
- package/dist/shared/CellMenu.svelte.d.ts +5 -19
- package/dist/shared/CellMenuButton.svelte +3 -3
- package/dist/shared/CellMenuButton.svelte.d.ts +4 -18
- package/dist/shared/CellMenuIcons.svelte +1 -1
- package/dist/shared/CellMenuIcons.svelte.d.ts +4 -18
- package/dist/shared/DataCell.svelte +189 -0
- package/dist/shared/DataCell.svelte.d.ts +44 -0
- package/dist/shared/EditableCell.svelte +88 -64
- package/dist/shared/EditableCell.svelte.d.ts +8 -26
- package/dist/shared/EmptyRowButton.svelte +2 -2
- package/dist/shared/EmptyRowButton.svelte.d.ts +4 -18
- package/dist/shared/FilterMenu.svelte +26 -18
- package/dist/shared/FilterMenu.svelte.d.ts +5 -21
- package/dist/shared/HeaderCell.svelte +218 -0
- package/dist/shared/HeaderCell.svelte.d.ts +30 -0
- package/dist/shared/RowNumber.svelte +4 -2
- package/dist/shared/RowNumber.svelte.d.ts +4 -18
- package/dist/shared/Table.svelte +1037 -949
- package/dist/shared/Table.svelte.d.ts +12 -30
- package/dist/shared/Toolbar.svelte +32 -22
- package/dist/shared/Toolbar.svelte.d.ts +6 -21
- package/dist/shared/column_measurement.svelte.d.ts +20 -0
- package/dist/shared/column_measurement.svelte.js +79 -0
- package/dist/shared/icons/FilterIcon.svelte +14 -6
- package/dist/shared/icons/FilterIcon.svelte.d.ts +14 -6
- package/dist/shared/icons/Padlock.svelte +6 -2
- package/dist/shared/icons/Padlock.svelte.d.ts +4 -25
- package/dist/shared/icons/SelectionButtons.svelte +23 -8
- package/dist/shared/icons/SelectionButtons.svelte.d.ts +4 -18
- package/dist/shared/icons/SortButtonDown.svelte +15 -4
- package/dist/shared/icons/SortButtonDown.svelte.d.ts +4 -25
- package/dist/shared/icons/SortButtonUp.svelte +15 -5
- package/dist/shared/icons/SortButtonUp.svelte.d.ts +4 -25
- package/dist/shared/icons/SortIcon.svelte +13 -7
- package/dist/shared/icons/SortIcon.svelte.d.ts +7 -21
- package/dist/shared/tanstack/index.d.ts +3 -0
- package/dist/shared/tanstack/index.js +3 -0
- package/dist/shared/tanstack/table.svelte.d.ts +26 -0
- package/dist/shared/tanstack/table.svelte.js +102 -0
- package/dist/shared/tanstack/virtual.svelte.d.ts +14 -0
- package/dist/shared/tanstack/virtual.svelte.js +76 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/utils/filter.d.ts +5 -0
- package/dist/shared/utils/filter.js +62 -0
- package/dist/shared/utils/selection_utils.js +1 -1
- package/dist/shared/utils/table_utils.d.ts +1 -14
- package/dist/shared/utils/table_utils.js +0 -61
- package/dist/standalone/Index.svelte +71 -46
- package/dist/standalone/Index.svelte.d.ts +4 -23
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +12 -11
- package/shared/BooleanCell.svelte +12 -6
- package/shared/CellMenu.svelte +74 -49
- package/shared/CellMenuButton.svelte +3 -3
- package/shared/CellMenuIcons.svelte +1 -1
- package/shared/DataCell.svelte +189 -0
- package/shared/EditableCell.svelte +88 -64
- package/shared/EmptyRowButton.svelte +2 -2
- package/shared/FilterMenu.svelte +26 -18
- package/shared/HeaderCell.svelte +218 -0
- package/shared/RowNumber.svelte +4 -2
- package/shared/Table.svelte +1037 -949
- package/shared/Toolbar.svelte +32 -22
- package/shared/column_measurement.svelte.ts +103 -0
- package/shared/icons/FilterIcon.svelte +14 -6
- package/shared/icons/Padlock.svelte +6 -2
- package/shared/icons/SelectionButtons.svelte +23 -8
- package/shared/icons/SortButtonDown.svelte +15 -4
- package/shared/icons/SortButtonUp.svelte +15 -5
- package/shared/icons/SortIcon.svelte +13 -7
- package/shared/tanstack/index.ts +23 -0
- package/shared/tanstack/table.svelte.ts +117 -0
- package/shared/tanstack/virtual.svelte.ts +105 -0
- package/shared/types.ts +3 -0
- package/shared/utils/filter.ts +66 -0
- package/shared/utils/selection_utils.ts +1 -1
- package/shared/utils/table_utils.ts +1 -100
- package/standalone/Index.svelte +71 -46
- package/test/filter.test.ts +350 -0
- package/test/selection_utils.test.ts +416 -0
- package/test/table_utils.test.ts +45 -112
- package/types.ts +34 -0
- package/dist/shared/TableCell.svelte +0 -330
- package/dist/shared/TableCell.svelte.d.ts +0 -66
- package/dist/shared/TableHeader.svelte +0 -344
- package/dist/shared/TableHeader.svelte.d.ts +0 -61
- package/dist/shared/VirtualTable.svelte +0 -379
- package/dist/shared/VirtualTable.svelte.d.ts +0 -42
- package/dist/shared/context/dataframe_context.d.ts +0 -180
- package/dist/shared/context/dataframe_context.js +0 -412
- package/dist/shared/icons/SortArrowDown.svelte +0 -25
- package/dist/shared/icons/SortArrowDown.svelte.d.ts +0 -20
- package/dist/shared/icons/SortArrowUp.svelte +0 -25
- package/dist/shared/icons/SortArrowUp.svelte.d.ts +0 -20
- package/dist/shared/utils/data_processing.d.ts +0 -14
- package/dist/shared/utils/data_processing.js +0 -47
- package/dist/shared/utils/drag_utils.d.ts +0 -15
- package/dist/shared/utils/drag_utils.js +0 -67
- package/dist/shared/utils/filter_utils.d.ts +0 -29
- package/dist/shared/utils/filter_utils.js +0 -123
- package/dist/shared/utils/keyboard_utils.d.ts +0 -3
- package/dist/shared/utils/keyboard_utils.js +0 -236
- package/dist/shared/utils/menu_utils.d.ts +0 -42
- package/dist/shared/utils/menu_utils.js +0 -58
- package/dist/shared/utils/sort_utils.d.ts +0 -26
- package/dist/shared/utils/sort_utils.js +0 -59
- package/shared/TableCell.svelte +0 -330
- package/shared/TableHeader.svelte +0 -344
- package/shared/VirtualTable.svelte +0 -379
- package/shared/context/dataframe_context.ts +0 -701
- package/shared/icons/SortArrowDown.svelte +0 -25
- package/shared/icons/SortArrowUp.svelte +0 -25
- package/shared/utils/data_processing.ts +0 -77
- package/shared/utils/drag_utils.ts +0 -105
- package/shared/utils/filter_utils.ts +0 -208
- package/shared/utils/keyboard_utils.ts +0 -309
- package/shared/utils/menu_utils.ts +0 -115
- package/shared/utils/sort_utils.ts +0 -91
- package/test/sort_utils.test.ts +0 -323
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
is_cell_in_selection,
|
|
4
|
+
is_cell_selected,
|
|
5
|
+
get_range_selection,
|
|
6
|
+
handle_selection,
|
|
7
|
+
handle_delete_key,
|
|
8
|
+
should_show_cell_menu,
|
|
9
|
+
get_next_cell_coordinates,
|
|
10
|
+
move_cursor,
|
|
11
|
+
get_current_indices,
|
|
12
|
+
select_column,
|
|
13
|
+
select_row
|
|
14
|
+
} from "../shared/utils/selection_utils";
|
|
15
|
+
import type { CellCoordinate } from "../shared/types";
|
|
16
|
+
|
|
17
|
+
// helper: create a grid of CellData for move_cursor / get_next_cell_coordinates
|
|
18
|
+
function make_grid(rows: number, cols: number) {
|
|
19
|
+
return Array.from({ length: rows }, (_, r) =>
|
|
20
|
+
Array.from({ length: cols }, (_, c) => ({
|
|
21
|
+
id: `cell-${r}-${c}`,
|
|
22
|
+
value: `${r},${c}`
|
|
23
|
+
}))
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// helper: create a minimal KeyboardEvent-like object
|
|
28
|
+
function make_key_event(
|
|
29
|
+
key: string,
|
|
30
|
+
opts: { metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {}
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
key,
|
|
34
|
+
metaKey: opts.metaKey ?? false,
|
|
35
|
+
ctrlKey: opts.ctrlKey ?? false,
|
|
36
|
+
shiftKey: opts.shiftKey ?? false
|
|
37
|
+
} as KeyboardEvent;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("is_cell_in_selection", () => {
|
|
41
|
+
test("returns true when cell is in selection", () => {
|
|
42
|
+
expect(
|
|
43
|
+
is_cell_in_selection(
|
|
44
|
+
[1, 2],
|
|
45
|
+
[
|
|
46
|
+
[0, 0],
|
|
47
|
+
[1, 2],
|
|
48
|
+
[3, 4]
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("returns false when cell is not in selection", () => {
|
|
55
|
+
expect(
|
|
56
|
+
is_cell_in_selection(
|
|
57
|
+
[1, 2],
|
|
58
|
+
[
|
|
59
|
+
[0, 0],
|
|
60
|
+
[3, 4]
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("handles empty selection", () => {
|
|
67
|
+
expect(is_cell_in_selection([0, 0], [])).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("is_cell_selected", () => {
|
|
72
|
+
test("returns empty string when cell not selected", () => {
|
|
73
|
+
expect(is_cell_selected([0, 0], [[1, 1]])).toBe("");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("returns 'cell-selected' for isolated cell", () => {
|
|
77
|
+
expect(is_cell_selected([1, 1], [[1, 1]])).toBe("cell-selected");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("adds 'no-top' when neighbor above is selected", () => {
|
|
81
|
+
const result = is_cell_selected(
|
|
82
|
+
[1, 0],
|
|
83
|
+
[
|
|
84
|
+
[0, 0],
|
|
85
|
+
[1, 0]
|
|
86
|
+
]
|
|
87
|
+
);
|
|
88
|
+
expect(result).toContain("cell-selected");
|
|
89
|
+
expect(result).toContain("no-top");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("adds 'no-bottom' when neighbor below is selected", () => {
|
|
93
|
+
const result = is_cell_selected(
|
|
94
|
+
[0, 0],
|
|
95
|
+
[
|
|
96
|
+
[0, 0],
|
|
97
|
+
[1, 0]
|
|
98
|
+
]
|
|
99
|
+
);
|
|
100
|
+
expect(result).toContain("cell-selected");
|
|
101
|
+
expect(result).toContain("no-bottom");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("adds 'no-left' when neighbor left is selected", () => {
|
|
105
|
+
const result = is_cell_selected(
|
|
106
|
+
[0, 1],
|
|
107
|
+
[
|
|
108
|
+
[0, 0],
|
|
109
|
+
[0, 1]
|
|
110
|
+
]
|
|
111
|
+
);
|
|
112
|
+
expect(result).toContain("cell-selected");
|
|
113
|
+
expect(result).toContain("no-left");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("adds 'no-right' when neighbor right is selected", () => {
|
|
117
|
+
const result = is_cell_selected(
|
|
118
|
+
[0, 0],
|
|
119
|
+
[
|
|
120
|
+
[0, 0],
|
|
121
|
+
[0, 1]
|
|
122
|
+
]
|
|
123
|
+
);
|
|
124
|
+
expect(result).toContain("cell-selected");
|
|
125
|
+
expect(result).toContain("no-right");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("combines multiple neighbor flags for block selection", () => {
|
|
129
|
+
// 2x2 block: center-ish cell [1,1] in a block [[0,0],[0,1],[1,0],[1,1]]
|
|
130
|
+
const selection: CellCoordinate[] = [
|
|
131
|
+
[0, 0],
|
|
132
|
+
[0, 1],
|
|
133
|
+
[1, 0],
|
|
134
|
+
[1, 1]
|
|
135
|
+
];
|
|
136
|
+
const result = is_cell_selected([1, 1], selection);
|
|
137
|
+
expect(result).toContain("cell-selected");
|
|
138
|
+
expect(result).toContain("no-top");
|
|
139
|
+
expect(result).toContain("no-left");
|
|
140
|
+
// no neighbor below or right
|
|
141
|
+
expect(result).not.toContain("no-bottom");
|
|
142
|
+
expect(result).not.toContain("no-right");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("get_range_selection", () => {
|
|
147
|
+
test("returns rectangular range with anchor first", () => {
|
|
148
|
+
const result = get_range_selection([0, 0], [2, 2]);
|
|
149
|
+
// anchor [0,0] should be first
|
|
150
|
+
expect(result[0]).toEqual([0, 0]);
|
|
151
|
+
// should contain all 9 cells in the 3x3 range
|
|
152
|
+
expect(result).toHaveLength(9);
|
|
153
|
+
expect(result).toContainEqual([1, 1]);
|
|
154
|
+
expect(result).toContainEqual([2, 2]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("handles reversed direction (end before start)", () => {
|
|
158
|
+
const result = get_range_selection([2, 2], [0, 0]);
|
|
159
|
+
expect(result[0]).toEqual([2, 2]);
|
|
160
|
+
expect(result).toHaveLength(9);
|
|
161
|
+
expect(result).toContainEqual([0, 0]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("single cell range returns just the anchor", () => {
|
|
165
|
+
const result = get_range_selection([1, 1], [1, 1]);
|
|
166
|
+
expect(result).toEqual([[1, 1]]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("handle_selection", () => {
|
|
171
|
+
test("plain click returns single cell", () => {
|
|
172
|
+
const result = handle_selection([2, 3], [[0, 0]], {
|
|
173
|
+
shiftKey: false,
|
|
174
|
+
metaKey: false,
|
|
175
|
+
ctrlKey: false
|
|
176
|
+
});
|
|
177
|
+
expect(result).toEqual([[2, 3]]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("shift+click returns range from last selected", () => {
|
|
181
|
+
const result = handle_selection([2, 2], [[0, 0]], {
|
|
182
|
+
shiftKey: true,
|
|
183
|
+
metaKey: false,
|
|
184
|
+
ctrlKey: false
|
|
185
|
+
});
|
|
186
|
+
// range from [0,0] to [2,2] = 9 cells
|
|
187
|
+
expect(result).toHaveLength(9);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("cmd/ctrl+click adds cell to selection", () => {
|
|
191
|
+
const result = handle_selection([1, 1], [[0, 0]], {
|
|
192
|
+
shiftKey: false,
|
|
193
|
+
metaKey: true,
|
|
194
|
+
ctrlKey: false
|
|
195
|
+
});
|
|
196
|
+
expect(result).toEqual([
|
|
197
|
+
[0, 0],
|
|
198
|
+
[1, 1]
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("cmd/ctrl+click removes already-selected cell", () => {
|
|
203
|
+
const result = handle_selection(
|
|
204
|
+
[0, 0],
|
|
205
|
+
[
|
|
206
|
+
[0, 0],
|
|
207
|
+
[1, 1]
|
|
208
|
+
],
|
|
209
|
+
{
|
|
210
|
+
shiftKey: false,
|
|
211
|
+
metaKey: true,
|
|
212
|
+
ctrlKey: false
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
expect(result).toEqual([[1, 1]]);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("handle_delete_key", () => {
|
|
220
|
+
test("clears values of selected cells", () => {
|
|
221
|
+
const data = [
|
|
222
|
+
[
|
|
223
|
+
{ id: "0-0", value: "a" },
|
|
224
|
+
{ id: "0-1", value: "b" }
|
|
225
|
+
],
|
|
226
|
+
[
|
|
227
|
+
{ id: "1-0", value: "c" },
|
|
228
|
+
{ id: "1-1", value: "d" }
|
|
229
|
+
]
|
|
230
|
+
];
|
|
231
|
+
const result = handle_delete_key(data, [
|
|
232
|
+
[0, 0],
|
|
233
|
+
[1, 1]
|
|
234
|
+
]);
|
|
235
|
+
expect(result[0][0].value).toBe("");
|
|
236
|
+
expect(result[1][1].value).toBe("");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("leaves unselected cells unchanged", () => {
|
|
240
|
+
const data = [
|
|
241
|
+
[
|
|
242
|
+
{ id: "0-0", value: "a" },
|
|
243
|
+
{ id: "0-1", value: "b" }
|
|
244
|
+
]
|
|
245
|
+
];
|
|
246
|
+
const result = handle_delete_key(data, [[0, 0]]);
|
|
247
|
+
expect(result[0][1].value).toBe("b");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("handles out-of-bounds gracefully", () => {
|
|
251
|
+
const data = [
|
|
252
|
+
[
|
|
253
|
+
{ id: "0-0", value: "a" },
|
|
254
|
+
{ id: "0-1", value: "b" }
|
|
255
|
+
]
|
|
256
|
+
];
|
|
257
|
+
// should not throw
|
|
258
|
+
const result = handle_delete_key(data, [[5, 5]]);
|
|
259
|
+
expect(result[0][0].value).toBe("a");
|
|
260
|
+
expect(result[0][1].value).toBe("b");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("should_show_cell_menu", () => {
|
|
265
|
+
test("true when editable, single cell selected, and coords match", () => {
|
|
266
|
+
expect(should_show_cell_menu([1, 2], [[1, 2]], true)).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("false when not editable", () => {
|
|
270
|
+
expect(should_show_cell_menu([1, 2], [[1, 2]], false)).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("false when multiple cells selected", () => {
|
|
274
|
+
expect(
|
|
275
|
+
should_show_cell_menu(
|
|
276
|
+
[1, 2],
|
|
277
|
+
[
|
|
278
|
+
[1, 2],
|
|
279
|
+
[1, 3]
|
|
280
|
+
],
|
|
281
|
+
true
|
|
282
|
+
)
|
|
283
|
+
).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("false when coords don't match", () => {
|
|
287
|
+
expect(should_show_cell_menu([0, 0], [[1, 2]], true)).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("get_next_cell_coordinates", () => {
|
|
292
|
+
const grid = make_grid(3, 3);
|
|
293
|
+
|
|
294
|
+
test("moves right to next column", () => {
|
|
295
|
+
expect(get_next_cell_coordinates([0, 0], grid, false)).toEqual([0, 1]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("wraps to next row at end of row", () => {
|
|
299
|
+
expect(get_next_cell_coordinates([0, 2], grid, false)).toEqual([1, 0]);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("shift moves left to previous column", () => {
|
|
303
|
+
expect(get_next_cell_coordinates([0, 1], grid, true)).toEqual([0, 0]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("shift wraps to previous row at start of row", () => {
|
|
307
|
+
expect(get_next_cell_coordinates([1, 0], grid, true)).toEqual([0, 2]);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("returns false at grid boundary", () => {
|
|
311
|
+
// forward past last cell
|
|
312
|
+
expect(get_next_cell_coordinates([2, 2], grid, false)).toBe(false);
|
|
313
|
+
// backward past first cell
|
|
314
|
+
expect(get_next_cell_coordinates([0, 0], grid, true)).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("move_cursor", () => {
|
|
319
|
+
const grid = make_grid(4, 4);
|
|
320
|
+
|
|
321
|
+
test("ArrowRight moves one column right", () => {
|
|
322
|
+
expect(move_cursor(make_key_event("ArrowRight"), [1, 1], grid)).toEqual([
|
|
323
|
+
1, 2
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("ArrowLeft moves one column left", () => {
|
|
328
|
+
expect(move_cursor(make_key_event("ArrowLeft"), [1, 1], grid)).toEqual([
|
|
329
|
+
1, 0
|
|
330
|
+
]);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("ArrowDown moves one row down", () => {
|
|
334
|
+
expect(move_cursor(make_key_event("ArrowDown"), [1, 1], grid)).toEqual([
|
|
335
|
+
2, 1
|
|
336
|
+
]);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("ArrowUp moves one row up", () => {
|
|
340
|
+
expect(move_cursor(make_key_event("ArrowUp"), [1, 1], grid)).toEqual([
|
|
341
|
+
0, 1
|
|
342
|
+
]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("Cmd+ArrowRight jumps to last column", () => {
|
|
346
|
+
expect(
|
|
347
|
+
move_cursor(make_key_event("ArrowRight", { metaKey: true }), [1, 0], grid)
|
|
348
|
+
).toEqual([1, 3]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("Cmd+ArrowLeft jumps to first column", () => {
|
|
352
|
+
expect(
|
|
353
|
+
move_cursor(make_key_event("ArrowLeft", { metaKey: true }), [1, 3], grid)
|
|
354
|
+
).toEqual([1, 0]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("Cmd+ArrowDown jumps to last row", () => {
|
|
358
|
+
expect(
|
|
359
|
+
move_cursor(make_key_event("ArrowDown", { metaKey: true }), [0, 1], grid)
|
|
360
|
+
).toEqual([3, 1]);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("Cmd+ArrowUp jumps to first row", () => {
|
|
364
|
+
expect(
|
|
365
|
+
move_cursor(make_key_event("ArrowUp", { metaKey: true }), [3, 1], grid)
|
|
366
|
+
).toEqual([0, 1]);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("returns false at grid boundary", () => {
|
|
370
|
+
expect(move_cursor(make_key_event("ArrowUp"), [0, 0], grid)).toBe(false);
|
|
371
|
+
expect(move_cursor(make_key_event("ArrowLeft"), [0, 0], grid)).toBe(false);
|
|
372
|
+
expect(move_cursor(make_key_event("ArrowDown"), [3, 3], grid)).toBe(false);
|
|
373
|
+
expect(move_cursor(make_key_event("ArrowRight"), [3, 3], grid)).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe("get_current_indices", () => {
|
|
378
|
+
const grid = make_grid(3, 3);
|
|
379
|
+
|
|
380
|
+
test("finds cell by ID", () => {
|
|
381
|
+
expect(get_current_indices("cell-1-2", grid)).toEqual([1, 2]);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("returns [-1, -1] when ID not found", () => {
|
|
385
|
+
expect(get_current_indices("nonexistent", grid)).toEqual([-1, -1]);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("select_column", () => {
|
|
390
|
+
test("selects all rows for given column", () => {
|
|
391
|
+
const data = [
|
|
392
|
+
[1, 2, 3],
|
|
393
|
+
[4, 5, 6],
|
|
394
|
+
[7, 8, 9]
|
|
395
|
+
];
|
|
396
|
+
expect(select_column(data, 1)).toEqual([
|
|
397
|
+
[0, 1],
|
|
398
|
+
[1, 1],
|
|
399
|
+
[2, 1]
|
|
400
|
+
]);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("select_row", () => {
|
|
405
|
+
test("selects all columns for given row", () => {
|
|
406
|
+
const data = [
|
|
407
|
+
[1, 2, 3],
|
|
408
|
+
[4, 5, 6]
|
|
409
|
+
];
|
|
410
|
+
expect(select_row(data, 0)).toEqual([
|
|
411
|
+
[0, 0],
|
|
412
|
+
[0, 1],
|
|
413
|
+
[0, 2]
|
|
414
|
+
]);
|
|
415
|
+
});
|
|
416
|
+
});
|
package/test/table_utils.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
make_cell_id,
|
|
4
|
+
make_header_id,
|
|
5
|
+
guess_delimiter,
|
|
6
|
+
data_uri_to_blob
|
|
7
|
+
} from "../shared/utils/table_utils";
|
|
4
8
|
import { cast_value_to_type } from "../shared/utils/utils";
|
|
5
9
|
|
|
6
|
-
function make_id(): string {
|
|
7
|
-
return Math.random().toString(36).substring(2, 15);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
10
|
describe("table_utils", () => {
|
|
11
11
|
describe("id generation", () => {
|
|
12
12
|
test("generates unique cell ids", () => {
|
|
@@ -27,112 +27,6 @@ describe("table_utils", () => {
|
|
|
27
27
|
expect(id_set.size).toBe(10);
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
|
-
|
|
31
|
-
describe("process_data", () => {
|
|
32
|
-
const element_refs: Record<string, { cell: null; input: null }> = {};
|
|
33
|
-
const data_binding: Record<string, { value: string | number; id: string }> =
|
|
34
|
-
{};
|
|
35
|
-
|
|
36
|
-
test("processes data with row numbers", () => {
|
|
37
|
-
const test_result = process_data(
|
|
38
|
-
[
|
|
39
|
-
["1", "2"],
|
|
40
|
-
["3", "4"]
|
|
41
|
-
],
|
|
42
|
-
element_refs,
|
|
43
|
-
data_binding,
|
|
44
|
-
make_id
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
expect(test_result[0].map((item) => item.value)).toEqual(["1", "2"]);
|
|
48
|
-
expect(test_result).toHaveLength(2);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("processes data without row numbers", () => {
|
|
52
|
-
const test_result = process_data(
|
|
53
|
-
[
|
|
54
|
-
["1", "2"],
|
|
55
|
-
["3", "4"]
|
|
56
|
-
],
|
|
57
|
-
element_refs,
|
|
58
|
-
data_binding,
|
|
59
|
-
make_id
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
expect(test_result.length).toBe(2);
|
|
63
|
-
expect(test_result[0].length).toBe(2);
|
|
64
|
-
expect(test_result[0][0].value).toBe("1");
|
|
65
|
-
expect(test_result[0][1].value).toBe("2");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("handles empty data", () => {
|
|
69
|
-
const test_result = process_data([], element_refs, data_binding, make_id);
|
|
70
|
-
|
|
71
|
-
expect(test_result.length).toBe(0);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("make_headers", () => {
|
|
76
|
-
test("creates headers with ids when headers are provided", () => {
|
|
77
|
-
const els = {};
|
|
78
|
-
const headers = ["Name", "Age", "City"];
|
|
79
|
-
const col_count: [number, "fixed" | "dynamic"] = [3, "fixed"];
|
|
80
|
-
|
|
81
|
-
const result = make_headers(headers, col_count, els, make_id);
|
|
82
|
-
|
|
83
|
-
expect(result.length).toBe(3);
|
|
84
|
-
expect(result[0].value).toBe("Name");
|
|
85
|
-
expect(result[1].value).toBe("Age");
|
|
86
|
-
expect(result[2].value).toBe("City");
|
|
87
|
-
expect(result[0].id).toBeDefined();
|
|
88
|
-
expect(result[1].id).toBeDefined();
|
|
89
|
-
expect(result[2].id).toBeDefined();
|
|
90
|
-
expect(Object.keys(els).length).toBe(3);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("fills missing headers when col_count is fixed", () => {
|
|
94
|
-
const els = {};
|
|
95
|
-
const headers = ["Name", "Age"];
|
|
96
|
-
const col_count: [number, "fixed" | "dynamic"] = [4, "fixed"];
|
|
97
|
-
|
|
98
|
-
const result = make_headers(headers, col_count, els, make_id);
|
|
99
|
-
|
|
100
|
-
expect(result.length).toBe(4);
|
|
101
|
-
expect(result[0].value).toBe("Name");
|
|
102
|
-
expect(result[1].value).toBe("Age");
|
|
103
|
-
expect(result[2].value).toBe("2");
|
|
104
|
-
expect(result[3].value).toBe("3");
|
|
105
|
-
expect(Object.keys(els).length).toBe(4);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("creates default headers when no headers are provided", () => {
|
|
109
|
-
const els = {};
|
|
110
|
-
const headers = [];
|
|
111
|
-
const col_count: [number, "fixed" | "dynamic"] = [3, "fixed"];
|
|
112
|
-
|
|
113
|
-
const result = make_headers(headers, col_count, els, make_id);
|
|
114
|
-
|
|
115
|
-
expect(result.length).toBe(3);
|
|
116
|
-
expect(result[0].value).toBe("0");
|
|
117
|
-
expect(result[1].value).toBe("1");
|
|
118
|
-
expect(result[2].value).toBe("2");
|
|
119
|
-
expect(Object.keys(els).length).toBe(3);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("handles null values in headers", () => {
|
|
123
|
-
const els = {};
|
|
124
|
-
const headers = ["Name", null, "City"] as any;
|
|
125
|
-
const col_count: [number, "fixed" | "dynamic"] = [3, "fixed"];
|
|
126
|
-
|
|
127
|
-
const result = make_headers(headers, col_count, els, make_id);
|
|
128
|
-
|
|
129
|
-
expect(result.length).toBe(3);
|
|
130
|
-
expect(result[0].value).toBe("Name");
|
|
131
|
-
expect(result[1].value).toBe("");
|
|
132
|
-
expect(result[2].value).toBe("City");
|
|
133
|
-
expect(Object.keys(els).length).toBe(3);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
30
|
});
|
|
137
31
|
|
|
138
32
|
describe("cast_value_to_type", () => {
|
|
@@ -174,3 +68,42 @@ describe("cast_value_to_type", () => {
|
|
|
174
68
|
expect(cast_value_to_type(undefined, "str")).toBe(undefined);
|
|
175
69
|
});
|
|
176
70
|
});
|
|
71
|
+
|
|
72
|
+
describe("guess_delimiter", () => {
|
|
73
|
+
test("detects comma delimiter", () => {
|
|
74
|
+
const csv = "a,b,c\n1,2,3\n4,5,6";
|
|
75
|
+
expect(guess_delimiter(csv, [",", "\t"])).toContain(",");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("detects tab delimiter", () => {
|
|
79
|
+
const tsv = "a\tb\tc\n1\t2\t3\n4\t5\t6";
|
|
80
|
+
expect(guess_delimiter(tsv, [",", "\t"])).toContain("\t");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("returns empty array when no consistent delimiter", () => {
|
|
84
|
+
const text = "abc\ndef\nghi";
|
|
85
|
+
expect(guess_delimiter(text, [",", "\t"])).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("handles single-line input", () => {
|
|
89
|
+
// single line with commas — cache set once, always matches
|
|
90
|
+
const text = "a,b,c";
|
|
91
|
+
expect(guess_delimiter(text, [",", "\t"])).toContain(",");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("data_uri_to_blob", () => {
|
|
96
|
+
test("converts data URI to Blob with correct MIME type", () => {
|
|
97
|
+
const data_uri = "data:text/plain;base64,SGVsbG8=";
|
|
98
|
+
const blob = data_uri_to_blob(data_uri);
|
|
99
|
+
expect(blob.type).toBe("text/plain");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("converts data URI to Blob with correct content", async () => {
|
|
103
|
+
// "Hello" in base64 is "SGVsbG8="
|
|
104
|
+
const data_uri = "data:text/plain;base64,SGVsbG8=";
|
|
105
|
+
const blob = data_uri_to_blob(data_uri);
|
|
106
|
+
const text = await blob.text();
|
|
107
|
+
expect(text).toBe("Hello");
|
|
108
|
+
});
|
|
109
|
+
});
|
package/types.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SelectData } from "@gradio/utils";
|
|
2
|
+
import type { LoadingStatus } from "@gradio/statustracker";
|
|
3
|
+
import type { DataframeValue, Datatype, EditData } from "./shared/utils/utils";
|
|
4
|
+
|
|
5
|
+
export interface DataframeEvents {
|
|
6
|
+
change: DataframeValue;
|
|
7
|
+
input: never;
|
|
8
|
+
select: SelectData;
|
|
9
|
+
edit: EditData;
|
|
10
|
+
clear_status: LoadingStatus;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DataframeProps {
|
|
14
|
+
value: DataframeValue;
|
|
15
|
+
col_count: [number, "fixed" | "dynamic"];
|
|
16
|
+
row_count: [number, "fixed" | "dynamic"];
|
|
17
|
+
wrap: boolean;
|
|
18
|
+
datatype: Datatype | Datatype[];
|
|
19
|
+
line_breaks: boolean;
|
|
20
|
+
column_widths: string[];
|
|
21
|
+
latex_delimiters: {
|
|
22
|
+
left: string;
|
|
23
|
+
right: string;
|
|
24
|
+
display: boolean;
|
|
25
|
+
}[];
|
|
26
|
+
max_height: number;
|
|
27
|
+
buttons: string[] | null;
|
|
28
|
+
max_chars: number | undefined;
|
|
29
|
+
show_row_numbers: boolean;
|
|
30
|
+
show_search: "none" | "search" | "filter";
|
|
31
|
+
pinned_columns: number;
|
|
32
|
+
static_columns: (string | number)[];
|
|
33
|
+
fullscreen: boolean;
|
|
34
|
+
}
|