@gradio/dataframe 0.24.2 → 0.24.4

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.
@@ -255,6 +255,13 @@
255
255
  let editing: CellCoordinate | false = $state(false);
256
256
  let header_edit: number | false = $state(false);
257
257
  let selected_header: number | false = $state(false);
258
+ let keyboard_header: number | false = $derived(
259
+ selected_header !== false &&
260
+ selected_header >= 0 &&
261
+ selected_header < resolved_headers.length
262
+ ? selected_header
263
+ : false
264
+ );
258
265
  let active_cell_menu: {
259
266
  row: number;
260
267
  col: number;
@@ -270,10 +277,140 @@
270
277
 
271
278
  let parent: HTMLDivElement;
272
279
 
280
+ let keyboard_active = $derived.by((): CellCoordinate | false => {
281
+ if (selected) {
282
+ const selected_row = selected[0];
283
+ const is_visible = rows.some(
284
+ (row) => row.original._index === selected_row
285
+ );
286
+ if (is_visible) return selected;
287
+ }
288
+ if (rows.length > 0 && resolved_headers.length > 0) {
289
+ return [rows[0].original._index, 0];
290
+ }
291
+ return false;
292
+ });
293
+
294
+ function visible_row_position(row_index: number): number {
295
+ return rows.findIndex((row) => row.original._index === row_index);
296
+ }
297
+
298
+ function is_active_cell(row: number, col: number): boolean {
299
+ return !!(
300
+ keyboard_active &&
301
+ keyboard_active[0] === row &&
302
+ keyboard_active[1] === col &&
303
+ keyboard_header === false
304
+ );
305
+ }
306
+
307
+ async function focus_cell(coord: CellCoordinate): Promise<void> {
308
+ await tick();
309
+ parent
310
+ ?.querySelector<HTMLElement>(
311
+ `[data-testid="cell-${coord[0]}-${coord[1]}"]`
312
+ )
313
+ ?.focus();
314
+ }
315
+
316
+ async function focus_header(col: number): Promise<void> {
317
+ await tick();
318
+ parent
319
+ ?.querySelector<HTMLElement>(`[data-testid="header-${col}"]`)
320
+ ?.focus();
321
+ }
322
+
323
+ function set_active_cell(coord: CellCoordinate, move_focus = true): void {
324
+ selected = coord;
325
+ selected_cells = [coord];
326
+ selected_header = false;
327
+ header_edit = false;
328
+ const row_position = visible_row_position(coord[0]);
329
+ if (row_position >= 0) {
330
+ virtualizer.instance.scrollToIndex(row_position, { align: "auto" });
331
+ }
332
+ if (move_focus) focus_cell(coord);
333
+ }
334
+
335
+ function handle_cell_focus(row: number, col: number): void {
336
+ if (editing) return;
337
+ if (selected && selected[0] === row && selected[1] === col) return;
338
+ set_active_cell([row, col], false);
339
+ }
340
+
341
+ function handle_header_focus(col: number): void {
342
+ if (header_edit !== false) return;
343
+ selected = false;
344
+ selected_cells = [];
345
+ selected_header = col;
346
+ }
347
+
348
+ function dispatch_select(row: number, col: number): void {
349
+ onselect?.({
350
+ index: [row, col],
351
+ value: values?.[row]?.[col],
352
+ row_value: values?.[row] ?? [],
353
+ col_value: values?.map((value_row) => value_row[col]) ?? []
354
+ } as any);
355
+ }
356
+
357
+ function adjacent_visible_cell(
358
+ coord: CellCoordinate,
359
+ backwards: boolean
360
+ ): CellCoordinate | false {
361
+ const [row, col] = coord;
362
+ const row_position = visible_row_position(row);
363
+ if (row_position === -1) return false;
364
+
365
+ if (backwards) {
366
+ if (col > 0) return [row, col - 1];
367
+ if (row_position > 0) {
368
+ return [
369
+ rows[row_position - 1].original._index,
370
+ resolved_headers.length - 1
371
+ ];
372
+ }
373
+ return false;
374
+ }
375
+
376
+ if (col < resolved_headers.length - 1) return [row, col + 1];
377
+ if (row_position < rows.length - 1) {
378
+ return [rows[row_position + 1].original._index, 0];
379
+ }
380
+ return false;
381
+ }
382
+
383
+ function focus_outside_grid(backwards: boolean): void {
384
+ tick().then(() => {
385
+ const candidates = Array.from(
386
+ document.querySelectorAll<HTMLElement>(
387
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
388
+ )
389
+ ).filter(
390
+ (element) =>
391
+ !parent.contains(element) && element.getClientRects().length > 0
392
+ );
393
+ const position = backwards
394
+ ? Node.DOCUMENT_POSITION_PRECEDING
395
+ : Node.DOCUMENT_POSITION_FOLLOWING;
396
+ const ordered = backwards ? candidates.reverse() : candidates;
397
+ ordered
398
+ .find((element) => parent.compareDocumentPosition(element) & position)
399
+ ?.focus();
400
+ });
401
+ }
402
+
273
403
  function get_dtype(col: number): Datatype {
274
404
  return Array.isArray(datatype) ? (datatype[col] ?? "str") : datatype;
275
405
  }
276
406
 
407
+ function is_static_column(col: number): boolean {
408
+ return (
409
+ static_columns.includes(col) ||
410
+ static_columns.includes(resolved_headers[col])
411
+ );
412
+ }
413
+
277
414
  type SizingEntry = { val: string; col_idx: number; dtype: Datatype };
278
415
 
279
416
  // heading multipliers for markdown/html block elements that render
@@ -418,16 +555,8 @@
418
555
  // click selects, does NOT enter edit mode (double-click or typing does)
419
556
  editing = false;
420
557
 
421
- onselect?.({
422
- index: coord,
423
- value: values?.[row]?.[col],
424
- row_value: values?.[row] ?? [],
425
- col_value: values?.map((r) => r[col]) ?? []
426
- } as any);
427
-
428
- if (!col_is_static) {
429
- tick().then(() => parent?.focus());
430
- }
558
+ dispatch_select(row, col);
559
+ focus_cell(coord);
431
560
  }
432
561
 
433
562
  function handle_cell_dblclick(
@@ -475,7 +604,6 @@
475
604
  if (event.target instanceof HTMLAnchorElement) return;
476
605
  event.preventDefault();
477
606
  event.stopPropagation();
478
- if (!editable) return;
479
607
 
480
608
  editing = false;
481
609
  selected = false;
@@ -483,14 +611,19 @@
483
611
  active_cell_menu = null;
484
612
  active_header_menu = null;
485
613
  selected_header = col;
486
- header_edit = editable ? col : false;
487
- parent?.focus();
614
+ header_edit = editable && !is_static_column(col) ? col : false;
615
+ if (header_edit === false) focus_header(col);
488
616
  }
489
617
 
490
- function end_header_edit(key: string): void {
491
- if (["Escape", "Enter", "Tab"].includes(key)) {
618
+ function end_header_edit(event: KeyboardEvent): void {
619
+ if (["Escape", "Enter", "Tab"].includes(event.key)) {
620
+ event.preventDefault();
492
621
  header_edit = false;
493
- parent?.focus();
622
+ if (event.key === "Tab") {
623
+ focus_outside_grid(event.shiftKey);
624
+ } else if (selected_header !== false) {
625
+ focus_header(selected_header);
626
+ }
494
627
  }
495
628
  }
496
629
 
@@ -565,8 +698,7 @@
565
698
  }
566
699
  values = new_values;
567
700
  push_change(new_values);
568
- selected = [index ?? new_values.length - 1, 0];
569
- parent?.focus();
701
+ set_active_cell([index ?? new_values.length - 1, 0]);
570
702
  }
571
703
 
572
704
  function add_col(index?: number): void {
@@ -583,20 +715,33 @@
583
715
  values = new_values;
584
716
  headers = new_headers;
585
717
  push_change(new_values, new_headers);
586
- parent?.focus();
718
+ const new_col = index ?? new_headers.length - 1;
719
+ if (selected) {
720
+ set_active_cell([selected[0], new_col]);
721
+ } else {
722
+ selected_header = new_col;
723
+ header_edit = false;
724
+ focus_header(new_col);
725
+ }
587
726
  }
588
727
 
589
728
  function delete_row_at(index: number): void {
590
729
  if (values.length <= 1) return;
730
+ const target_col = selected ? selected[1] : 0;
591
731
  values = [...values.slice(0, index), ...values.slice(index + 1)];
592
732
  push_change(values);
593
733
  active_cell_menu = null;
594
734
  active_header_menu = null;
735
+ set_active_cell([
736
+ Math.min(index, values.length - 1),
737
+ Math.min(target_col, resolved_headers.length - 1)
738
+ ]);
595
739
  }
596
740
 
597
741
  function delete_col_at(index: number): void {
598
742
  if (col_count[1] !== "dynamic") return;
599
743
  if ((values[0]?.length ?? 0) <= 1) return;
744
+ const target_row = selected ? selected[0] : (rows[0]?.original._index ?? 0);
600
745
  values = values.map((row) => [
601
746
  ...row.slice(0, index),
602
747
  ...row.slice(index + 1)
@@ -608,9 +753,8 @@
608
753
  push_change(values, headers as string[]);
609
754
  active_cell_menu = null;
610
755
  active_header_menu = null;
611
- selected = false;
612
- selected_cells = [];
613
756
  editing = false;
757
+ set_active_cell([target_row, Math.min(index, headers.length - 1)]);
614
758
  }
615
759
 
616
760
  function add_row_at(index: number, position: "above" | "below"): void {
@@ -682,137 +826,206 @@
682
826
  }
683
827
 
684
828
  function handle_keydown(e: KeyboardEvent): void {
685
- if (!selected && selected_header === false) return;
829
+ if (e.target instanceof HTMLAnchorElement) return;
830
+ if (!selected && keyboard_header === false) return;
686
831
 
687
- const num_rows = rows.length;
688
832
  const num_cols = resolved_headers.length;
689
833
 
690
- if (selected) {
691
- const [row, col] = selected;
692
-
693
- if (
694
- editing &&
695
- (e.key === "ArrowUp" ||
696
- e.key === "ArrowDown" ||
697
- e.key === "ArrowLeft" ||
698
- e.key === "ArrowRight")
699
- ) {
700
- return;
701
- }
702
-
834
+ if (keyboard_header !== false) {
835
+ if (header_edit !== false) return;
836
+ const col = keyboard_header;
703
837
  switch (e.key) {
704
- case "ArrowUp":
838
+ case "ArrowDown": {
705
839
  e.preventDefault();
706
- if (row > 0) {
707
- selected = [row - 1, col];
708
- selected_cells = [selected];
709
- virtualizer.instance.scrollToIndex(row - 1, { align: "auto" });
710
- }
711
- break;
712
- case "ArrowDown":
713
- e.preventDefault();
714
- if (row < num_rows - 1) {
715
- selected = [row + 1, col];
716
- selected_cells = [selected];
717
- virtualizer.instance.scrollToIndex(row + 1, { align: "auto" });
718
- }
840
+ const first_row = rows[0]?.original._index;
841
+ if (first_row !== undefined) set_active_cell([first_row, col]);
719
842
  break;
843
+ }
720
844
  case "ArrowLeft":
721
845
  e.preventDefault();
722
846
  if (col > 0) {
723
- selected = [row, col - 1];
724
- selected_cells = [selected];
847
+ selected_header = col - 1;
848
+ focus_header(col - 1);
725
849
  }
726
850
  break;
727
851
  case "ArrowRight":
728
852
  e.preventDefault();
729
853
  if (col < num_cols - 1) {
730
- selected = [row, col + 1];
731
- selected_cells = [selected];
854
+ selected_header = col + 1;
855
+ focus_header(col + 1);
732
856
  }
733
857
  break;
734
- case "Tab": {
858
+ case "Enter":
859
+ case "F2":
735
860
  e.preventDefault();
736
- const was_editing = !!editing;
737
- if (e.shiftKey) {
738
- if (col > 0) selected = [row, col - 1];
739
- else if (row > 0) selected = [row - 1, num_cols - 1];
740
- } else {
741
- if (col < num_cols - 1) selected = [row, col + 1];
742
- else if (row < num_rows - 1) selected = [row + 1, 0];
743
- }
744
- selected_cells = [selected];
745
- if (was_editing) {
746
- const tab_col = (selected as CellCoordinate)[1];
747
- const tab_static =
748
- static_columns.includes(tab_col) ||
749
- static_columns.includes(resolved_headers[tab_col]);
750
- editing = editable && !tab_static ? selected : false;
751
- } else {
752
- editing = false;
753
- }
754
- if (!editing) tick().then(() => parent?.focus());
861
+ if (editable && !is_static_column(col)) header_edit = col;
755
862
  break;
756
- }
757
- case "Enter":
758
- if (editing && e.shiftKey) {
759
- // shift+enter inserts newline in textarea — don't intercept
760
- return;
761
- }
863
+ case " ":
864
+ case "Spacebar":
762
865
  e.preventDefault();
763
- if (editing) {
764
- editing = false;
765
- if (row < num_rows - 1) {
766
- selected = [row + 1, col];
767
- selected_cells = [selected];
768
- }
769
- tick().then(() => parent?.focus());
770
- } else if (editable) {
771
- const enter_static =
772
- static_columns.includes(col) ||
773
- static_columns.includes(resolved_headers[col]);
774
- if (!enter_static) {
775
- editing = [row, col];
776
- }
866
+ if (editable && !is_static_column(col) && get_dtype(col) === "bool") {
867
+ handle_select_all(col, get_select_all_state(col) !== "checked");
777
868
  }
778
869
  break;
779
- case "Escape":
780
- editing = false;
781
- tick().then(() => parent?.focus());
870
+ }
871
+ return;
872
+ }
873
+
874
+ if (!selected) return;
875
+ const [row, col] = selected;
876
+ const row_position = visible_row_position(row);
877
+ if (row_position === -1) return;
878
+
879
+ if (
880
+ editing &&
881
+ (e.key === "ArrowUp" ||
882
+ e.key === "ArrowDown" ||
883
+ e.key === "ArrowLeft" ||
884
+ e.key === "ArrowRight" ||
885
+ e.key === "Home" ||
886
+ e.key === "End")
887
+ ) {
888
+ return;
889
+ }
890
+
891
+ switch (e.key) {
892
+ case "ArrowUp": {
893
+ e.preventDefault();
894
+ if (row_position > 0) {
895
+ set_active_cell([rows[row_position - 1].original._index, col]);
896
+ } else {
897
+ selected = false;
898
+ selected_cells = [];
899
+ selected_header = col;
900
+ focus_header(col);
901
+ }
902
+ break;
903
+ }
904
+ case "ArrowDown":
905
+ e.preventDefault();
906
+ if (row_position < rows.length - 1) {
907
+ set_active_cell([rows[row_position + 1].original._index, col]);
908
+ }
909
+ break;
910
+ case "ArrowLeft":
911
+ e.preventDefault();
912
+ if (col > 0) set_active_cell([row, col - 1]);
913
+ break;
914
+ case "ArrowRight":
915
+ e.preventDefault();
916
+ if (col < num_cols - 1) set_active_cell([row, col + 1]);
917
+ break;
918
+ case "Home":
919
+ e.preventDefault();
920
+ set_active_cell(
921
+ e.ctrlKey || e.metaKey ? [rows[0].original._index, 0] : [row, 0]
922
+ );
923
+ break;
924
+ case "End":
925
+ e.preventDefault();
926
+ set_active_cell(
927
+ e.ctrlKey || e.metaKey
928
+ ? [rows[rows.length - 1].original._index, num_cols - 1]
929
+ : [row, num_cols - 1]
930
+ );
931
+ break;
932
+ case "Tab": {
933
+ if (!editing) return;
934
+ e.preventDefault();
935
+ const next_cell = adjacent_visible_cell([row, col], e.shiftKey);
936
+ editing = false;
937
+ if (!next_cell) {
938
+ focus_outside_grid(e.shiftKey);
782
939
  break;
783
- case "Delete":
784
- case "Backspace":
785
- if (!editing && editable) {
786
- e.preventDefault();
787
- const new_values = values.map((r) => [...r]);
788
- selected_cells.forEach(([r, c]) => {
789
- if (!static_columns.includes(c)) {
790
- new_values[r][c] = "";
791
- }
792
- });
940
+ }
941
+ set_active_cell(next_cell, false);
942
+ const next_col = next_cell[1];
943
+ const next_is_static = is_static_column(next_col);
944
+ if (editable && !next_is_static && get_dtype(next_col) !== "bool") {
945
+ editing = next_cell;
946
+ } else {
947
+ focus_cell(next_cell);
948
+ }
949
+ break;
950
+ }
951
+ case "Enter":
952
+ if (editing && e.shiftKey) return;
953
+ e.preventDefault();
954
+ if (editing) {
955
+ editing = false;
956
+ focus_cell([row, col]);
957
+ } else {
958
+ dispatch_select(row, col);
959
+ const enter_static = is_static_column(col);
960
+ if (editable && !enter_static && get_dtype(col) !== "bool") {
961
+ editing = [row, col];
962
+ }
963
+ }
964
+ break;
965
+ case "F2":
966
+ e.preventDefault();
967
+ if (editing) {
968
+ editing = false;
969
+ focus_cell([row, col]);
970
+ } else if (
971
+ editable &&
972
+ !is_static_column(col) &&
973
+ get_dtype(col) !== "bool"
974
+ ) {
975
+ editing = [row, col];
976
+ }
977
+ break;
978
+ case " ":
979
+ case "Spacebar":
980
+ if (!editing) {
981
+ e.preventDefault();
982
+ const col_is_static = is_static_column(col);
983
+ if (editable && !col_is_static && get_dtype(col) === "bool") {
984
+ const new_values = values.map((value_row) => [...value_row]);
985
+ new_values[row][col] = !new_values[row][col];
793
986
  values = new_values;
794
987
  push_change(new_values);
795
988
  }
796
- break;
797
- default:
798
- // start editing on printable character
799
- if (
800
- editable &&
801
- !editing &&
802
- e.key.length === 1 &&
803
- !e.ctrlKey &&
804
- !e.metaKey &&
805
- !static_columns.includes(col)
806
- ) {
807
- editing = [row, col];
808
- }
809
- break;
810
- }
811
-
812
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
813
- handle_copy();
814
- }
989
+ dispatch_select(row, col);
990
+ }
991
+ break;
992
+ case "Escape":
993
+ if (editing) {
994
+ e.preventDefault();
995
+ editing = false;
996
+ focus_cell([row, col]);
997
+ }
998
+ break;
999
+ case "Delete":
1000
+ case "Backspace":
1001
+ if (!editing && editable) {
1002
+ e.preventDefault();
1003
+ const new_values = values.map((value_row) => [...value_row]);
1004
+ selected_cells.forEach(([selected_row, selected_col]) => {
1005
+ if (!is_static_column(selected_col)) {
1006
+ new_values[selected_row][selected_col] = "";
1007
+ }
1008
+ });
1009
+ values = new_values;
1010
+ push_change(new_values);
1011
+ }
1012
+ break;
1013
+ default:
1014
+ if (
1015
+ editable &&
1016
+ !editing &&
1017
+ e.key.length === 1 &&
1018
+ !e.ctrlKey &&
1019
+ !e.metaKey &&
1020
+ !is_static_column(col) &&
1021
+ get_dtype(col) !== "bool"
1022
+ ) {
1023
+ editing = [row, col];
1024
+ }
1025
+ break;
815
1026
  }
1027
+
1028
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") handle_copy();
816
1029
  }
817
1030
 
818
1031
  function handle_scroll(): void {
@@ -888,10 +1101,12 @@
888
1101
  let disable_scroll = $derived(
889
1102
  active_cell_menu !== null || active_header_menu !== null
890
1103
  );
891
- let selected_index = $derived(selected !== false ? selected[0] : false);
1104
+ let selected_index = $derived(
1105
+ selected !== false ? visible_row_position(selected[0]) : false
1106
+ );
892
1107
 
893
1108
  $effect(() => {
894
- if (typeof selected_index === "number") {
1109
+ if (typeof selected_index === "number" && selected_index >= 0) {
895
1110
  virtualizer.instance.scrollToIndex(selected_index, { align: "auto" });
896
1111
  }
897
1112
  });
@@ -947,7 +1162,11 @@
947
1162
  class:menu-open={active_cell_menu || active_header_menu}
948
1163
  onkeydown={handle_keydown}
949
1164
  role="grid"
950
- tabindex="0"
1165
+ aria-label={label || "Dataframe"}
1166
+ aria-rowcount={rows.length + 1}
1167
+ aria-colcount={resolved_headers.length + Number(show_row_numbers)}
1168
+ aria-readonly={!editable}
1169
+ tabindex={rows.length === 0 ? 0 : -1}
951
1170
  style="--df-max-col-width: {viewport_width}px;"
952
1171
  >
953
1172
  <Upload
@@ -961,6 +1180,8 @@
961
1180
  onload={on_file_upload}
962
1181
  bind:dragging
963
1182
  aria_label={i18n("dataframe.drop_to_upload")}
1183
+ tab_index={-1}
1184
+ container_element="div"
964
1185
  >
965
1186
  <div
966
1187
  class="virtual-table-viewport"
@@ -968,18 +1189,19 @@
968
1189
  bind:this={scroll_container}
969
1190
  bind:clientWidth={viewport_width}
970
1191
  onscroll={handle_scroll}
1192
+ role="none"
971
1193
  style="max-height: {max_height}px;"
972
- role="grid"
973
1194
  >
974
- {#if label && label.length !== 0}
975
- <span class="sr-only">{label}</span>
976
- {/if}
977
1195
  <!-- header row: uses table layout to auto-size columns by content -->
978
- <table class="header-table" bind:this={header_table_el}>
979
- <thead>
980
- <tr bind:this={header_row_el}>
1196
+ <table class="header-table" bind:this={header_table_el} role="none">
1197
+ <thead role="rowgroup">
1198
+ <tr bind:this={header_row_el} role="row" aria-rowindex="1">
981
1199
  {#if show_row_numbers}
982
- <th class="row-number-header">&nbsp;</th>
1200
+ <th
1201
+ class="row-number-header"
1202
+ role="columnheader"
1203
+ aria-colindex="1">&nbsp;</th
1204
+ >
983
1205
  {/if}
984
1206
  {#each header_groups as headerGroup (headerGroup.id)}
985
1207
  {#each headerGroup.headers as header (header.id)}
@@ -988,8 +1210,9 @@
988
1210
  <HeaderCell
989
1211
  value={String(header.column.columnDef.header ?? "")}
990
1212
  {col_idx}
1213
+ aria_col_index={col_idx + (show_row_numbers ? 2 : 1)}
991
1214
  is_editing={header_edit === col_idx}
992
- is_selected={selected_header === col_idx}
1215
+ is_selected={keyboard_header === col_idx}
993
1216
  is_static={!!(header.column.columnDef.meta as any)
994
1217
  ?.isStatic}
995
1218
  is_bool={get_dtype(col_idx) === "bool"}
@@ -1007,6 +1230,7 @@
1007
1230
  {i18n}
1008
1231
  wrap_text={wrap}
1009
1232
  onclick={handle_header_click}
1233
+ onfocus={handle_header_focus}
1010
1234
  on_menu_click={toggle_header_menu}
1011
1235
  on_end_edit={end_header_edit}
1012
1236
  on_select_all={handle_select_all}
@@ -1049,6 +1273,7 @@
1049
1273
  <!-- table body: absolutely positioned rows (standard tanstack virtual pattern) -->
1050
1274
  <div
1051
1275
  class="virtual-body"
1276
+ role="none"
1052
1277
  style="height: {total_size}px; position: relative; flex-shrink: 0; width: {measurement.total_header_width
1053
1278
  ? `${measurement.total_header_width}px`
1054
1279
  : '100%'};"
@@ -1061,6 +1286,8 @@
1061
1286
  class="virtual-row"
1062
1287
  class:row-odd={virtual_row.index % 2 !== 0}
1063
1288
  data-index={virtual_row.index}
1289
+ role="row"
1290
+ aria-rowindex={virtual_row.index + 2}
1064
1291
  style="position: absolute; top: 0; left: 0; width: 100%; transform: translateY({virtual_row.start}px);{selected_cells.some(
1065
1292
  ([r]) => r === row_idx
1066
1293
  )
@@ -1073,6 +1300,8 @@
1073
1300
  class="row-number-cell"
1074
1301
  data-row={row_idx}
1075
1302
  data-col="row-number"
1303
+ role="rowheader"
1304
+ aria-colindex="1"
1076
1305
  style="flex: 0 0 {measurement.row_num_width}px; width: {measurement.row_num_width}px;"
1077
1306
  >
1078
1307
  {row_idx + 1}
@@ -1091,12 +1320,15 @@
1091
1320
  datatype={get_dtype(col_idx)}
1092
1321
  {row_idx}
1093
1322
  {col_idx}
1323
+ aria_col_index={col_idx + (show_row_numbers ? 2 : 1)}
1094
1324
  col_style={measurement.get_col_style(ci)}
1095
1325
  cell_style={get_styling(row_idx, col_idx)}
1096
1326
  selection_classes={is_cell_selected(
1097
1327
  [row_idx, col_idx],
1098
1328
  selected_cells
1099
1329
  )}
1330
+ is_active={is_active_cell(row_idx, col_idx)}
1331
+ aria_row_index={virtual_row.index + 2}
1100
1332
  is_editing={!!(
1101
1333
  editing &&
1102
1334
  editing[0] === row_idx &&
@@ -1136,11 +1368,12 @@
1136
1368
  e.preventDefault();
1137
1369
  toggle_cell_menu(e, row_idx, col_idx);
1138
1370
  }}
1371
+ onfocus={() => handle_cell_focus(row_idx, col_idx)}
1139
1372
  onblur={handle_blur}
1140
1373
  on_menu_click={(e) => toggle_cell_menu(e, row_idx, col_idx)}
1141
1374
  on_select_column={(c) => {
1142
1375
  selected_cells = rows.map(
1143
- (_, r) => [r, c] as CellCoordinate
1376
+ (row) => [row.original._index, c] as CellCoordinate
1144
1377
  );
1145
1378
  selected = selected_cells[0];
1146
1379
  }}
@@ -1298,7 +1531,7 @@
1298
1531
  user-select: none;
1299
1532
  }
1300
1533
 
1301
- .table-wrap > :global(button) {
1534
+ .table-wrap > :global(.upload-container) {
1302
1535
  border: 1px solid var(--border-color-primary);
1303
1536
  border-radius: var(--table-radius);
1304
1537
  overflow: hidden;
@@ -1338,6 +1571,7 @@
1338
1571
  /* Header table: auto-sizes columns by content, sticky */
1339
1572
  .header-table {
1340
1573
  width: 100%;
1574
+ padding: 0;
1341
1575
  color: var(--body-text-color);
1342
1576
  font-size: var(--input-text-size);
1343
1577
  line-height: var(--line-md);