@gradio/dataframe 0.17.4 → 0.17.6

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 (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/Dataframe.stories.svelte +29 -0
  3. package/dist/shared/CellMenu.svelte.d.ts +1 -1
  4. package/dist/shared/EditableCell.svelte +8 -17
  5. package/dist/shared/EditableCell.svelte.d.ts +5 -3
  6. package/dist/shared/Table.svelte +111 -138
  7. package/dist/shared/TableCell.svelte +4 -6
  8. package/dist/shared/TableCell.svelte.d.ts +5 -1
  9. package/dist/shared/TableHeader.svelte +4 -3
  10. package/dist/shared/TableHeader.svelte.d.ts +1 -2
  11. package/dist/shared/context/dataframe_context.d.ts +147 -0
  12. package/dist/shared/context/dataframe_context.js +335 -0
  13. package/dist/shared/selection_utils.d.ts +1 -2
  14. package/dist/shared/selection_utils.js +0 -13
  15. package/dist/shared/utils/drag_utils.js +1 -0
  16. package/dist/shared/utils/keyboard_utils.d.ts +3 -2
  17. package/dist/shared/utils/keyboard_utils.js +107 -68
  18. package/package.json +6 -6
  19. package/shared/CellMenu.svelte +1 -1
  20. package/shared/EditableCell.svelte +9 -20
  21. package/shared/Table.svelte +147 -165
  22. package/shared/TableCell.svelte +9 -6
  23. package/shared/TableHeader.svelte +5 -8
  24. package/shared/context/dataframe_context.ts +576 -0
  25. package/shared/selection_utils.ts +1 -23
  26. package/shared/utils/drag_utils.ts +1 -0
  27. package/shared/utils/keyboard_utils.ts +142 -80
  28. package/{shared/utils → test}/sort_utils.test.ts +5 -1
  29. package/{shared/utils → test}/table_utils.test.ts +2 -2
  30. package/dist/shared/context/keyboard_context.d.ts +0 -37
  31. package/dist/shared/context/keyboard_context.js +0 -12
  32. package/dist/shared/context/selection_context.d.ts +0 -32
  33. package/dist/shared/context/selection_context.js +0 -107
  34. package/dist/shared/context/table_context.d.ts +0 -141
  35. package/dist/shared/context/table_context.js +0 -375
  36. package/shared/context/keyboard_context.ts +0 -65
  37. package/shared/context/selection_context.ts +0 -168
  38. package/shared/context/table_context.ts +0 -625
@@ -1,625 +0,0 @@
1
- import { getContext, setContext } from "svelte";
2
- import { writable, get } from "svelte/store";
3
- import type { Writable } from "svelte/store";
4
- import { sort_table_data } from "../utils/table_utils";
5
- import { dequal } from "dequal/lite";
6
- import type { DataframeValue } from "../utils";
7
-
8
- export const DATAFRAME_KEY = Symbol("dataframe");
9
-
10
- export type SortDirection = "asc" | "desc";
11
-
12
- export type DataFrameState = {
13
- config: {
14
- show_fullscreen_button: boolean;
15
- show_copy_button: boolean;
16
- show_search: "none" | "search" | "filter";
17
- show_row_numbers: boolean;
18
- editable: boolean;
19
- pinned_columns: number;
20
- show_label: boolean;
21
- line_breaks: boolean;
22
- wrap: boolean;
23
- max_height: number;
24
- column_widths: string[];
25
- max_chars: number | undefined;
26
- };
27
- current_search_query: string | null;
28
- sort_state: {
29
- sort_columns: { col: number; direction: SortDirection }[];
30
- row_order: number[];
31
- };
32
- ui_state: {
33
- active_cell_menu: { row: number; col: number; x: number; y: number } | null;
34
- active_header_menu: { col: number; x: number; y: number } | null;
35
- selected_cells: [number, number][];
36
- selected: [number, number] | false;
37
- editing: [number, number] | false;
38
- header_edit: number | false;
39
- selected_header: number | false;
40
- active_button: {
41
- type: "header" | "cell";
42
- row?: number;
43
- col: number;
44
- } | null;
45
- };
46
- };
47
-
48
- export interface DataFrameContext {
49
- state: Writable<DataFrameState>;
50
- actions: {
51
- handle_search: (search_query: string | null) => void;
52
- handle_sort: (col: number, direction: SortDirection) => void;
53
- get_sort_status: (
54
- name: string,
55
- headers: string[]
56
- ) => "none" | "asc" | "desc";
57
- sort_data: (
58
- data: any[][],
59
- display_value: string[][] | null,
60
- styling: string[][] | null
61
- ) => void;
62
- update_row_order: (data: any[][]) => void;
63
- filter_data: (data: any[][]) => any[][];
64
- add_row: (data: any[][], make_id: () => string, index?: number) => any[][];
65
- add_col: (
66
- data: any[][],
67
- headers: string[],
68
- make_id: () => string,
69
- index?: number
70
- ) => { data: any[][]; headers: string[] };
71
- add_row_at: (
72
- data: any[][],
73
- index: number,
74
- position: "above" | "below",
75
- make_id: () => string
76
- ) => any[][];
77
- add_col_at: (
78
- data: any[][],
79
- headers: string[],
80
- index: number,
81
- position: "left" | "right",
82
- make_id: () => string
83
- ) => { data: any[][]; headers: string[] };
84
- delete_row: (data: any[][], index: number) => any[][];
85
- delete_col: (
86
- data: any[][],
87
- headers: string[],
88
- index: number
89
- ) => { data: any[][]; headers: string[] };
90
- delete_row_at: (data: any[][], index: number) => any[][];
91
- delete_col_at: (
92
- data: any[][],
93
- headers: string[],
94
- index: number
95
- ) => { data: any[][]; headers: string[] };
96
- set_active_cell_menu: (
97
- menu: { row: number; col: number; x: number; y: number } | null
98
- ) => void;
99
- set_active_header_menu: (
100
- menu: { col: number; x: number; y: number } | null
101
- ) => void;
102
- set_selected_cells: (cells: [number, number][]) => void;
103
- set_selected: (selected: [number, number] | false) => void;
104
- set_editing: (editing: [number, number] | false) => void;
105
- clear_ui_state: () => void;
106
- set_header_edit: (header_index: number | false) => void;
107
- set_selected_header: (header_index: number | false) => void;
108
- handle_header_click: (col: number, editable: boolean) => void;
109
- end_header_edit: (key: string) => void;
110
- trigger_change: (
111
- data: any[][],
112
- headers: any[],
113
- previous_data: string[][],
114
- previous_headers: string[],
115
- value_is_output: boolean,
116
- dispatch: {
117
- (e: "change", detail: DataframeValue): void;
118
- (e: "input", detail?: undefined): void;
119
- (e: "select", detail: any): void;
120
- (e: "search", detail: string | null): void;
121
- }
122
- ) => Promise<void>;
123
- get_selected_cells: () => [number, number][];
124
- get_active_cell_menu: () => {
125
- row: number;
126
- col: number;
127
- x: number;
128
- y: number;
129
- } | null;
130
- get_active_button: () => {
131
- type: "header" | "cell";
132
- row?: number;
133
- col: number;
134
- } | null;
135
- set_active_button: (
136
- button: { type: "header" | "cell"; row?: number; col: number } | null
137
- ) => void;
138
- reset_sort_state: () => void;
139
- };
140
- }
141
-
142
- export function create_actions(
143
- state: Writable<DataFrameState>
144
- ): DataFrameContext["actions"] {
145
- const add_row = (
146
- data: any[][],
147
- make_id: () => string,
148
- index?: number
149
- ): any[][] => {
150
- if (!data || data.length === 0) {
151
- return [[{ value: "", id: make_id() }]];
152
- }
153
-
154
- const new_row = Array(data[0].length)
155
- .fill(null)
156
- .map(() => ({
157
- value: "",
158
- id: make_id()
159
- }));
160
-
161
- const new_data = [...data];
162
- if (typeof index === "number" && index >= 0 && index <= data.length) {
163
- new_data.splice(index, 0, new_row);
164
- } else {
165
- new_data.push(new_row);
166
- }
167
-
168
- return new_data;
169
- };
170
-
171
- const add_col = (
172
- data: any[][],
173
- headers: string[],
174
- make_id: () => string,
175
- index?: number
176
- ): { data: any[][]; headers: string[] } => {
177
- if (!data || data.length === 0) {
178
- return {
179
- data: [[{ value: "", id: make_id() }]],
180
- headers: ["Header 1"]
181
- };
182
- }
183
-
184
- const new_headers = [...headers];
185
- const new_data = data.map((row) => [...row]);
186
-
187
- if (
188
- typeof index === "number" &&
189
- index >= 0 &&
190
- index <= (data[0]?.length || 0)
191
- ) {
192
- new_headers.splice(index, 0, `Header ${headers.length + 1}`);
193
- new_data.forEach((row) => {
194
- const id = make_id();
195
- row.splice(index, 0, { value: "", id });
196
- });
197
- } else {
198
- new_headers.push(`Header ${headers.length + 1}`);
199
- new_data.forEach((row) => {
200
- const id = make_id();
201
- row.push({ value: "", id });
202
- });
203
- }
204
-
205
- return { data: new_data, headers: new_headers };
206
- };
207
-
208
- const reset_sort_state = (): void => {
209
- state.update((s) => ({
210
- ...s,
211
- sort_state: {
212
- sort_columns: [],
213
- row_order: []
214
- }
215
- }));
216
- };
217
-
218
- return {
219
- handle_search: (search_query: string | null) => {
220
- state.update((s) => ({ ...s, current_search_query: search_query }));
221
- },
222
- handle_sort: (col: number, direction: SortDirection) => {
223
- state.update((s) => {
224
- const sort_columns = [...s.sort_state.sort_columns];
225
- const existing_index = sort_columns.findIndex(
226
- (item) => item.col === col
227
- );
228
-
229
- if (existing_index !== -1) {
230
- const existing_item = sort_columns[existing_index];
231
-
232
- if (existing_item.direction === direction) {
233
- sort_columns.splice(existing_index, 1);
234
- } else {
235
- sort_columns[existing_index] = { col, direction };
236
- }
237
- } else {
238
- if (sort_columns.length >= 3) {
239
- sort_columns.shift();
240
- }
241
- sort_columns.push({ col, direction });
242
- }
243
-
244
- return {
245
- ...s,
246
- sort_state: {
247
- ...s.sort_state,
248
- sort_columns
249
- }
250
- };
251
- });
252
- },
253
- get_sort_status: (
254
- name: string,
255
- headers: string[]
256
- ): "none" | "asc" | "desc" => {
257
- const current_state = get(state);
258
- const sort_item = current_state.sort_state.sort_columns.find(
259
- (item) => headers[item.col] === name
260
- );
261
-
262
- if (!sort_item) return "none";
263
- return sort_item.direction;
264
- },
265
- sort_data: (
266
- data: any[][],
267
- display_value: string[][] | null,
268
- styling: string[][] | null
269
- ) => {
270
- const current_state = get(state);
271
- if (current_state.sort_state.sort_columns.length > 0) {
272
- sort_table_data(
273
- data,
274
- display_value,
275
- styling,
276
- current_state.sort_state.sort_columns
277
- );
278
- }
279
- },
280
- update_row_order: (data: any[][]) => {
281
- state.update((s) => {
282
- const current_sort_state = { ...s.sort_state };
283
- if (current_sort_state.sort_columns.length > 0 && data[0]) {
284
- const indices = [...Array(data.length)].map((_, i) => i);
285
- indices.sort((a, b) => {
286
- const row_a = data[a];
287
- const row_b = data[b];
288
-
289
- for (const {
290
- col: sort_index,
291
- direction
292
- } of current_sort_state.sort_columns) {
293
- if (
294
- !row_a ||
295
- !row_b ||
296
- sort_index < 0 ||
297
- sort_index >= row_a.length ||
298
- sort_index >= row_b.length
299
- ) {
300
- continue;
301
- }
302
-
303
- const val_a = row_a[sort_index].value;
304
- const val_b = row_b[sort_index].value;
305
- const comp = val_a < val_b ? -1 : val_a > val_b ? 1 : 0;
306
-
307
- if (comp !== 0) {
308
- return direction === "asc" ? comp : -comp;
309
- }
310
- }
311
-
312
- return 0;
313
- });
314
- current_sort_state.row_order = indices;
315
- } else {
316
- current_sort_state.row_order = [...Array(data.length)].map(
317
- (_, i) => i
318
- );
319
- }
320
- return { ...s, sort_state: current_sort_state };
321
- });
322
- },
323
- filter_data: (data: any[][]) => {
324
- const current_state = get(state);
325
-
326
- if (!current_state.current_search_query) {
327
- return data;
328
- }
329
-
330
- const search_query = current_state.current_search_query.toLowerCase();
331
-
332
- const filtered = data.filter((row) => {
333
- return row.some((cell) => {
334
- if (!cell) {
335
- return false;
336
- }
337
-
338
- const cell_value = cell.value;
339
-
340
- if (cell_value === null || cell_value === undefined) {
341
- return false;
342
- }
343
-
344
- const string_value = String(cell_value).toLowerCase();
345
- return string_value.includes(search_query);
346
- });
347
- });
348
-
349
- return filtered;
350
- },
351
- add_row,
352
- add_col,
353
- add_row_at: (
354
- data: any[][],
355
- index: number,
356
- position: "above" | "below",
357
- make_id: () => string
358
- ) => {
359
- const row_index = position === "above" ? index : index + 1;
360
- return add_row(data, make_id, row_index);
361
- },
362
- add_col_at: (
363
- data: any[][],
364
- headers: string[],
365
- index: number,
366
- position: "left" | "right",
367
- make_id: () => string
368
- ) => {
369
- const col_index = position === "left" ? index : index + 1;
370
- return add_col(data, headers, make_id, col_index);
371
- },
372
- delete_row: (data: any[][], index: number) => {
373
- if (data.length <= 1) {
374
- return data;
375
- }
376
-
377
- const new_data = [...data];
378
- new_data.splice(index, 1);
379
-
380
- return new_data;
381
- },
382
- delete_col: (
383
- data: any[][],
384
- headers: string[],
385
- index: number
386
- ): { data: any[][]; headers: string[] } => {
387
- if (headers.length <= 1) {
388
- return { data, headers };
389
- }
390
-
391
- const new_headers = [...headers];
392
- new_headers.splice(index, 1);
393
-
394
- const new_data = data.map((row) => {
395
- const new_row = [...row];
396
- new_row.splice(index, 1);
397
- return new_row;
398
- });
399
-
400
- return { data: new_data, headers: new_headers };
401
- },
402
- delete_row_at: (data: any[][], index: number) => {
403
- if (data.length <= 1) return data;
404
- data.splice(index, 1);
405
- return data;
406
- },
407
- delete_col_at: (data: any[][], headers: string[], index: number) => {
408
- if (headers.length <= 1) {
409
- return { data, headers };
410
- }
411
-
412
- const new_headers = [...headers];
413
- new_headers.splice(index, 1);
414
-
415
- const new_data = data.map((row) => {
416
- const new_row = [...row];
417
- new_row.splice(index, 1);
418
- return new_row;
419
- });
420
-
421
- return { data: new_data, headers: new_headers };
422
- },
423
- set_active_cell_menu: (menu) => {
424
- state.update((s) => ({
425
- ...s,
426
- ui_state: { ...s.ui_state, active_cell_menu: menu }
427
- }));
428
- },
429
- set_active_header_menu: (menu) => {
430
- state.update((s) => ({
431
- ...s,
432
- ui_state: { ...s.ui_state, active_header_menu: menu }
433
- }));
434
- },
435
- set_selected_cells: (cells) => {
436
- state.update((s) => ({
437
- ...s,
438
- ui_state: { ...s.ui_state, selected_cells: cells }
439
- }));
440
- },
441
- set_selected: (selected) => {
442
- state.update((s) => ({
443
- ...s,
444
- ui_state: { ...s.ui_state, selected: selected }
445
- }));
446
- },
447
- set_editing: (editing) => {
448
- state.update((s) => ({
449
- ...s,
450
- ui_state: { ...s.ui_state, editing: editing }
451
- }));
452
- },
453
- clear_ui_state: () => {
454
- state.update((s) => ({
455
- ...s,
456
- ui_state: {
457
- active_cell_menu: null,
458
- active_header_menu: null,
459
- selected_cells: [],
460
- selected: false,
461
- editing: false,
462
- header_edit: false,
463
- selected_header: false,
464
- active_button: null
465
- }
466
- }));
467
- },
468
- set_header_edit: (header_index: number | false) => {
469
- state.update((s) => ({
470
- ...s,
471
- ui_state: {
472
- ...s.ui_state,
473
- selected_cells: [],
474
- selected_header: header_index,
475
- header_edit: header_index
476
- }
477
- }));
478
- },
479
- set_selected_header: (header_index: number | false) => {
480
- state.update((s) => ({
481
- ...s,
482
- ui_state: {
483
- ...s.ui_state,
484
- selected_header: header_index,
485
- selected: false,
486
- selected_cells: []
487
- }
488
- }));
489
- },
490
- handle_header_click: (col: number, editable: boolean) => {
491
- state.update((s) => ({
492
- ...s,
493
- ui_state: {
494
- ...s.ui_state,
495
- active_cell_menu: null,
496
- active_header_menu: null,
497
- selected: false,
498
- selected_cells: [],
499
- selected_header: col,
500
- header_edit: editable ? col : false
501
- }
502
- }));
503
- },
504
- end_header_edit: (key: string) => {
505
- if (key === "Escape" || key === "Enter" || key === "Tab") {
506
- state.update((s) => ({
507
- ...s,
508
- ui_state: {
509
- ...s.ui_state,
510
- selected: false,
511
- header_edit: false
512
- }
513
- }));
514
- }
515
- },
516
- trigger_change: async (
517
- data: any[][],
518
- headers: any[],
519
- previous_data: string[][],
520
- previous_headers: string[],
521
- value_is_output: boolean,
522
- dispatch: {
523
- (e: "change", detail: DataframeValue): void;
524
- (e: "input", detail?: undefined): void;
525
- (e: "select", detail: any): void;
526
- (e: "search", detail: string | null): void;
527
- }
528
- ): Promise<void> => {
529
- const current_state = get(state);
530
- if (current_state.current_search_query) return;
531
-
532
- const current_headers = headers.map((h) => h.value);
533
- const current_data = data.map((row) =>
534
- row.map((cell) => String(cell.value))
535
- );
536
-
537
- if (
538
- !dequal(current_data, previous_data) ||
539
- !dequal(current_headers, previous_headers)
540
- ) {
541
- if (!dequal(current_headers, previous_headers)) {
542
- reset_sort_state();
543
- }
544
-
545
- dispatch("change", {
546
- data: data.map((row) => row.map((cell) => cell.value)),
547
- headers: headers.map((h) => h.value),
548
- metadata: null
549
- });
550
- if (!value_is_output) {
551
- dispatch("input");
552
- }
553
- }
554
- },
555
- get_selected_cells: () => {
556
- const current_state = get(state);
557
- return current_state.ui_state.selected_cells;
558
- },
559
- get_active_cell_menu: () => {
560
- const current_state = get(state);
561
- return current_state.ui_state.active_cell_menu;
562
- },
563
- get_active_button: () => {
564
- const current_state = get(state);
565
- return current_state.ui_state.active_button;
566
- },
567
- set_active_button: (button) => {
568
- state.update((s) => ({
569
- ...s,
570
- ui_state: { ...s.ui_state, active_button: button }
571
- }));
572
- },
573
- reset_sort_state
574
- };
575
- }
576
-
577
- export function create_dataframe_context(config: {
578
- show_fullscreen_button: boolean;
579
- show_copy_button: boolean;
580
- show_search: "none" | "search" | "filter";
581
- show_row_numbers: boolean;
582
- editable: boolean;
583
- pinned_columns: number;
584
- show_label: boolean;
585
- line_breaks: boolean;
586
- wrap: boolean;
587
- max_height: number;
588
- column_widths: string[];
589
- max_chars: number | undefined;
590
- }): DataFrameContext {
591
- const instance_id = Symbol(
592
- `dataframe_${Math.random().toString(36).substring(2)}`
593
- );
594
- const state = writable<DataFrameState>({
595
- config,
596
- current_search_query: null,
597
- sort_state: {
598
- sort_columns: [],
599
- row_order: []
600
- },
601
- ui_state: {
602
- active_cell_menu: null,
603
- active_header_menu: null,
604
- selected_cells: [],
605
- selected: false,
606
- editing: false,
607
- header_edit: false,
608
- selected_header: false,
609
- active_button: null
610
- }
611
- });
612
-
613
- const actions = create_actions(state);
614
- const context: DataFrameContext = { state, actions };
615
- setContext(instance_id, context);
616
- setContext(DATAFRAME_KEY, { instance_id, context });
617
- return context;
618
- }
619
-
620
- export function get_dataframe_context(): DataFrameContext {
621
- const ctx = getContext<{ instance_id: symbol; context: DataFrameContext }>(
622
- DATAFRAME_KEY
623
- );
624
- return ctx ? ctx.context : getContext<DataFrameContext>(DATAFRAME_KEY);
625
- }