@gradio/dataframe 0.24.3 → 0.24.5

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.
@@ -18,7 +18,7 @@
18
18
  show_copy_button?: boolean;
19
19
  show_search?: "none" | "search" | "filter";
20
20
  fullscreen?: boolean;
21
- on_copy: () => Promise<void>;
21
+ on_copy: () => Promise<boolean>;
22
22
  on_commit_filter: () => void;
23
23
  current_search_query?: string | null;
24
24
  onsearch?: (query: string | null) => void;
@@ -48,8 +48,7 @@
48
48
  }
49
49
 
50
50
  async function handle_copy(): Promise<void> {
51
- await on_copy();
52
- copy_feedback();
51
+ if (await on_copy()) copy_feedback();
53
52
  }
54
53
 
55
54
  $effect(() => {
@@ -3,7 +3,7 @@ type $$ComponentProps = {
3
3
  show_copy_button?: boolean;
4
4
  show_search?: "none" | "search" | "filter";
5
5
  fullscreen?: boolean;
6
- on_copy: () => Promise<void>;
6
+ on_copy: () => Promise<boolean>;
7
7
  on_commit_filter: () => void;
8
8
  current_search_query?: string | null;
9
9
  onsearch?: (query: string | null) => void;
@@ -27,6 +27,7 @@
27
27
  class="selection-button selection-button-{position} {is_first_position
28
28
  ? `move-${direction}`
29
29
  : ''}"
30
+ tabindex="-1"
30
31
  onclick={(e: MouseEvent) => {
31
32
  e.stopPropagation();
32
33
  on_click && on_click();
@@ -4,7 +4,7 @@ export type CellData = {
4
4
  value: CellValue;
5
5
  };
6
6
  export declare function is_cell_in_selection(coords: [number, number], selected_cells: [number, number][]): boolean;
7
- export declare function is_cell_selected(cell: CellCoordinate, selected_cells: CellCoordinate[]): string;
7
+ export declare function is_cell_selected(cell: CellCoordinate, selected_cells: CellCoordinate[], row_above: number | null, row_below: number | null): string;
8
8
  export declare function get_range_selection(start: CellCoordinate, end: CellCoordinate): CellCoordinate[];
9
9
  export declare function handle_selection(current: CellCoordinate, selected_cells: CellCoordinate[], event: {
10
10
  shiftKey: boolean;
@@ -2,12 +2,15 @@ export function is_cell_in_selection(coords, selected_cells) {
2
2
  const [row, col] = coords;
3
3
  return selected_cells.some(([r, c]) => r === row && c === col);
4
4
  }
5
- export function is_cell_selected(cell, selected_cells) {
5
+ // row_above/row_below are the data indices of the rows the caller is rendering
6
+ // on either side of `cell`, or null at an edge of the view. A search or a sort
7
+ // makes them differ from row ± 1, so only the caller knows them.
8
+ export function is_cell_selected(cell, selected_cells, row_above, row_below) {
6
9
  const [row, col] = cell;
7
10
  if (!selected_cells.some(([r, c]) => r === row && c === col))
8
11
  return "";
9
- const up = selected_cells.some(([r, c]) => r === row - 1 && c === col);
10
- const down = selected_cells.some(([r, c]) => r === row + 1 && c === col);
12
+ const up = selected_cells.some(([r, c]) => r === row_above && c === col);
13
+ const down = selected_cells.some(([r, c]) => r === row_below && c === col);
11
14
  const left = selected_cells.some(([r, c]) => r === row && c === col - 1);
12
15
  const right = selected_cells.some(([r, c]) => r === row && c === col + 1);
13
16
  return `cell-selected${up ? " no-top" : ""}${down ? " no-bottom" : ""}${left ? " no-left" : ""}${right ? " no-right" : ""}`;
@@ -1,7 +1,7 @@
1
1
  import type { CellValue, Headers, HeadersWithIDs, TableData } from "../types";
2
2
  export declare function make_cell_id(row: number, col: number): string;
3
3
  export declare function make_header_id(col: number): string;
4
- export declare function copy_table_data(data: TableData, selected_cells: [number, number][] | null): Promise<void>;
4
+ export declare function copy_table_data(data: TableData, selected_cells: [number, number][]): Promise<void>;
5
5
  export declare function guess_delimiter(text: string, possibleDelimiters: string[]): string[];
6
6
  export declare function data_uri_to_blob(data_uri: string): Blob;
7
7
  export declare function handle_file_upload(data_uri: string, update_headers: (headers: Headers) => HeadersWithIDs[], update_values: (values: CellValue[][]) => void): void;
@@ -8,11 +8,12 @@ export function make_header_id(col) {
8
8
  export async function copy_table_data(data, selected_cells) {
9
9
  if (!data || !data.length)
10
10
  return;
11
- const cells_to_copy = selected_cells ||
12
- data.flatMap((row, r) => row.map((_, c) => [r, c]));
13
- const csv = cells_to_copy.reduce((acc, [row, col]) => {
11
+ const csv = selected_cells.reduce((acc, [row, col]) => {
14
12
  acc[row] = acc[row] || {};
15
- const value = String(data[row][col].value);
13
+ // only the headers are padded out to the column count on the way in, so
14
+ // a selection can name a cell a short row does not have
15
+ const cell = data[row]?.[col];
16
+ const value = cell ? String(cell.value) : "";
16
17
  acc[row][col] =
17
18
  value.includes(",") || value.includes('"') || value.includes("\n")
18
19
  ? `"${value.replace(/"/g, '""')}"`
@@ -22,7 +23,9 @@ export async function copy_table_data(data, selected_cells) {
22
23
  const rows = Object.keys(csv).sort((a, b) => +a - +b);
23
24
  if (!rows.length)
24
25
  return;
25
- const cols = Object.keys(csv[rows[0]]).sort((a, b) => +a - +b);
26
+ // every column any selected row contributes: taking them from the first row
27
+ // alone drops the rest of a selection that is not rectangular there
28
+ const cols = Array.from(new Set(rows.flatMap((r) => Object.keys(csv[r])))).sort((a, b) => +a - +b);
26
29
  const text = rows
27
30
  .map((r) => cols.map((c) => csv[r][c] || "").join(","))
28
31
  .join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/dataframe",
3
- "version": "0.24.3",
3
+ "version": "0.24.5",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -15,15 +15,15 @@
15
15
  "dequal": "^2.0.3",
16
16
  "@tanstack/table-core": "^8.21.3",
17
17
  "@tanstack/virtual-core": "^3.13.6",
18
- "@gradio/atoms": "^0.26.1",
18
+ "@gradio/atoms": "^0.26.2",
19
+ "@gradio/checkbox": "^0.8.3",
19
20
  "@gradio/button": "^0.8.2",
20
- "@gradio/checkbox": "^0.8.2",
21
+ "@gradio/client": "^2.7.0",
22
+ "@gradio/markdown-code": "^0.10.1",
21
23
  "@gradio/icons": "^0.16.0",
22
- "@gradio/markdown-code": "^0.10.0",
23
- "@gradio/client": "^2.5.0",
24
+ "@gradio/upload": "^0.18.3",
24
25
  "@gradio/statustracker": "^0.15.3",
25
- "@gradio/upload": "^0.18.2",
26
- "@gradio/utils": "^0.14.0"
26
+ "@gradio/utils": "^0.14.2"
27
27
  },
28
28
  "exports": {
29
29
  ".": {
@@ -42,7 +42,7 @@
42
42
  "svelte": "^5.48.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@gradio/preview": "^0.17.0"
45
+ "@gradio/preview": "^0.17.1"
46
46
  },
47
47
  "repository": {
48
48
  "type": "git",
@@ -23,6 +23,7 @@
23
23
  bind:value
24
24
  label=""
25
25
  interactive={editable}
26
+ tab_index={-1}
26
27
  on_change={handle_change}
27
28
  />
28
29
  </div>
@@ -1,11 +1,18 @@
1
1
  <script lang="ts">
2
- let { on_click }: { on_click: (event: MouseEvent) => void } = $props();
2
+ let {
3
+ on_click,
4
+ tab_index = undefined
5
+ }: {
6
+ on_click: (event: MouseEvent) => void;
7
+ tab_index?: number;
8
+ } = $props();
3
9
  </script>
4
10
 
5
11
  <button
6
12
  aria-label="Open cell menu"
7
13
  class="cell-menu-button"
8
14
  aria-haspopup="menu"
15
+ tabindex={tab_index}
9
16
  onclick={on_click}
10
17
  ontouchstart={(event) => {
11
18
  event.preventDefault();
@@ -11,9 +11,12 @@
11
11
  datatype = "str",
12
12
  row_idx,
13
13
  col_idx,
14
+ aria_col_index = col_idx + 1,
14
15
  col_style = "",
15
16
  cell_style = "",
16
17
  selection_classes = "",
18
+ is_active = false,
19
+ aria_row_index,
17
20
  is_editing = false,
18
21
  is_flash = false,
19
22
  is_static = false,
@@ -32,6 +35,7 @@
32
35
  onmousedown,
33
36
  ondblclick,
34
37
  oncontextmenu,
38
+ onfocus,
35
39
  onblur,
36
40
  on_menu_click,
37
41
  on_select_column,
@@ -42,9 +46,12 @@
42
46
  datatype?: Datatype;
43
47
  row_idx: number;
44
48
  col_idx: number;
49
+ aria_col_index?: number;
45
50
  col_style?: string;
46
51
  cell_style?: string;
47
52
  selection_classes?: string;
53
+ is_active?: boolean;
54
+ aria_row_index: number;
48
55
  is_editing?: boolean;
49
56
  is_flash?: boolean;
50
57
  is_static?: boolean;
@@ -63,6 +70,7 @@
63
70
  onmousedown: (event: MouseEvent) => void;
64
71
  ondblclick: (event: MouseEvent) => void;
65
72
  oncontextmenu: (event: MouseEvent) => void;
73
+ onfocus: () => void;
66
74
  onblur: (detail: {
67
75
  blur_event: FocusEvent;
68
76
  coords: [number, number];
@@ -98,10 +106,15 @@
98
106
  data-row={row_idx}
99
107
  data-col={col_idx}
100
108
  data-testid={`cell-${row_idx}-${col_idx}`}
101
- tabindex={is_static ? -1 : undefined}
109
+ role="gridcell"
110
+ aria-rowindex={aria_row_index}
111
+ aria-colindex={aria_col_index}
112
+ aria-selected={is_selected}
113
+ tabindex={is_active ? 0 : -1}
102
114
  {onmousedown}
103
115
  {ondblclick}
104
116
  {oncontextmenu}
117
+ {onfocus}
105
118
  style="{col_style} {cell_style}"
106
119
  >
107
120
  <div class="cell-wrap" bind:this={wrap_el} style={locked_height}>
@@ -127,7 +140,7 @@
127
140
  expanded={is_solo}
128
141
  />
129
142
  {#if show_menu_button}
130
- <CellMenuButton on_click={on_menu_click} />
143
+ <CellMenuButton on_click={on_menu_click} tab_index={-1} />
131
144
  {/if}
132
145
  </div>
133
146
  </div>
@@ -146,6 +159,17 @@
146
159
  min-width: 0;
147
160
  }
148
161
 
162
+ .body-cell:focus-visible {
163
+ outline: 2px solid var(--color-accent);
164
+ outline-offset: -2px;
165
+ }
166
+
167
+ @media (forced-colors: active) {
168
+ .body-cell:focus-visible {
169
+ outline-color: CanvasText;
170
+ }
171
+ }
172
+
149
173
  .body-cell.static {
150
174
  user-select: text;
151
175
  }
@@ -175,8 +175,7 @@
175
175
  <span
176
176
  class:dragging={is_dragging}
177
177
  onkeydown={handle_keydown}
178
- tabindex="0"
179
- role="button"
178
+ tabindex="-1"
180
179
  class:edit
181
180
  class:expanded={edit}
182
181
  class:header
@@ -16,7 +16,6 @@
16
16
  color: var(--body-text-color);
17
17
  cursor: pointer;
18
18
  transition: all 150ms;
19
- margin-top: var(--size-2);
20
19
  z-index: 10;
21
20
  position: relative;
22
21
  pointer-events: auto;
@@ -12,6 +12,7 @@
12
12
  let {
13
13
  value,
14
14
  col_idx,
15
+ aria_col_index = col_idx + 1,
15
16
  is_editing = false,
16
17
  is_selected = false,
17
18
  is_static = false,
@@ -30,12 +31,14 @@
30
31
  i18n,
31
32
  wrap_text = false,
32
33
  onclick,
34
+ onfocus,
33
35
  on_menu_click,
34
36
  on_end_edit,
35
37
  on_select_all
36
38
  }: {
37
39
  value: string;
38
40
  col_idx: number;
41
+ aria_col_index?: number;
39
42
  is_editing?: boolean;
40
43
  is_selected?: boolean;
41
44
  is_static?: boolean;
@@ -54,8 +57,9 @@
54
57
  i18n: I18nFormatter;
55
58
  wrap_text?: boolean;
56
59
  onclick: (event: MouseEvent, col: number) => void;
60
+ onfocus: (col: number) => void;
57
61
  on_menu_click: (event: MouseEvent, col: number) => void;
58
- on_end_edit: (key: string) => void;
62
+ on_end_edit: (event: KeyboardEvent) => void;
59
63
  on_select_all?: (col: number, checked: boolean) => void;
60
64
  } = $props();
61
65
 
@@ -71,6 +75,11 @@
71
75
  class:filtered={is_filtered}
72
76
  class:first-column={is_first_column}
73
77
  data-heading={col_idx}
78
+ data-testid={`header-${col_idx}`}
79
+ role="columnheader"
80
+ aria-colindex={aria_col_index}
81
+ tabindex={is_selected ? 0 : -1}
82
+ onfocus={() => onfocus(col_idx)}
74
83
  onclick={(e) => onclick(e, col_idx)}
75
84
  onmousedown={(e) => {
76
85
  e.preventDefault();
@@ -94,6 +103,7 @@
94
103
  indeterminate={select_all_state === "indeterminate"}
95
104
  label={`Toggle all: ${value}`}
96
105
  interactive={true}
106
+ tab_index={-1}
97
107
  on_select={() =>
98
108
  on_select_all?.(col_idx, select_all_state !== "checked")}
99
109
  />
@@ -106,7 +116,8 @@
106
116
  edit={is_editing}
107
117
  onkeydown={(event) => {
108
118
  if (["Enter", "Escape", "Tab"].includes(event.key)) {
109
- on_end_edit(event.key);
119
+ event.stopPropagation();
120
+ on_end_edit(event);
110
121
  }
111
122
  }}
112
123
  header
@@ -148,7 +159,10 @@
148
159
  </span>
149
160
  {/if}
150
161
  {#if show_menu_button}
151
- <CellMenuButton on_click={(e) => on_menu_click(e, col_idx)} />
162
+ <CellMenuButton
163
+ on_click={(e) => on_menu_click(e, col_idx)}
164
+ tab_index={-1}
165
+ />
152
166
  {/if}
153
167
  </div>
154
168
  </th>