@ni/nimble-components 35.3.0 → 35.3.2

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.
@@ -16,6 +16,7 @@ export declare class KeyboardNavigationManager<TData extends TableRecord> implem
16
16
  private columnIndex;
17
17
  private focusWithinTable;
18
18
  private isCurrentlyFocusingElement;
19
+ private focusedViaPointer;
19
20
  private readonly tableNotifier;
20
21
  private readonly virtualizerNotifier;
21
22
  private visibleRowNotifiers;
@@ -30,6 +31,7 @@ export declare class KeyboardNavigationManager<TData extends TableRecord> implem
30
31
  onRowBlur(event: FocusEvent): void;
31
32
  onRowActionMenuToggle(event: CustomEvent<TableActionMenuToggleEventDetail>): void;
32
33
  private readonly onTableFocusIn;
34
+ private focusSomethingOtherThanTheTable;
33
35
  private readonly onTableFocusOut;
34
36
  private readonly onCellActionMenuBlur;
35
37
  private readonly onCellViewFocusIn;
@@ -37,6 +39,8 @@ export declare class KeyboardNavigationManager<TData extends TableRecord> implem
37
39
  private readonly onCellBlur;
38
40
  private readonly onCaptureKeyDown;
39
41
  private readonly onKeyDown;
42
+ private readonly onPointerDown;
43
+ private readonly onPointerUpOrCancel;
40
44
  private readonly onViewportKeyDown;
41
45
  private onEnterPressed;
42
46
  private onF2Pressed;
@@ -57,12 +61,13 @@ export declare class KeyboardNavigationManager<TData extends TableRecord> implem
57
61
  private onMoveUp;
58
62
  private onMoveDown;
59
63
  private updateFocusStateFromActiveElement;
64
+ private getActiveElementCellAndRow;
60
65
  private focusElement;
61
66
  private setElementFocusable;
62
67
  private setActionMenuButtonFocused;
63
- private setFocusOnHeader;
64
68
  private setDefaultFocus;
65
69
  private scrollToAndFocusRow;
70
+ private rowIndexIsValid;
66
71
  private focusCurrentRow;
67
72
  private focusRowElement;
68
73
  private focusHeaderElement;
@@ -26,25 +26,32 @@ export class KeyboardNavigationManager {
26
26
  this.columnIndex = -1;
27
27
  this.focusWithinTable = false;
28
28
  this.isCurrentlyFocusingElement = false;
29
+ this.focusedViaPointer = false;
29
30
  this.visibleRowNotifiers = [];
30
31
  this.onTableFocusIn = (event) => {
31
32
  this.focusWithinTable = true;
32
- this.updateFocusStateFromActiveElement(false);
33
+ this.updateFocusStateFromActiveElement();
33
34
  // Sets initial focus on the appropriate table content
34
35
  const actionMenuOpen = this.table.openActionMenuRecordId !== undefined;
35
- if ((event.target === this.table
36
- || this.focusType === TableFocusType.none)
37
- && !actionMenuOpen) {
38
- let focusHeader = true;
39
- if (this.hasRowOrCellFocusType()
40
- && this.scrollToAndFocusRow(this.rowIndex)) {
41
- focusHeader = false;
36
+ if (!actionMenuOpen) {
37
+ if (this.focusType === TableFocusType.none) {
38
+ this.focusSomethingOtherThanTheTable();
42
39
  }
43
- if (focusHeader && !this.setFocusOnHeader()) {
44
- // nothing to focus
45
- this.table.blur();
40
+ else if (event.target === this.table) {
41
+ // restore focus to last focused element
42
+ if (this.hasRowOrCellFocusType() && this.rowIndexIsValid(this.rowIndex)) {
43
+ this.scrollToAndFocusRow(this.rowIndex);
44
+ }
45
+ else if (this.hasHeaderFocusType()) {
46
+ this.focusHeaderElement();
47
+ }
48
+ else {
49
+ // should only get here if focusType was row/cell, but rowIndex was invalid
50
+ this.focusSomethingOtherThanTheTable();
51
+ }
46
52
  }
47
53
  }
54
+ this.focusedViaPointer = false;
48
55
  };
49
56
  this.onTableFocusOut = () => {
50
57
  this.focusWithinTable = false;
@@ -59,12 +66,18 @@ export class KeyboardNavigationManager {
59
66
  };
60
67
  this.onCellViewFocusIn = (event) => {
61
68
  event.stopPropagation();
62
- this.updateFocusStateFromActiveElement(false);
69
+ this.updateFocusStateFromActiveElement();
63
70
  };
64
71
  this.onCellFocusIn = (event) => {
65
72
  event.stopPropagation();
66
73
  const cell = event.detail;
67
- this.updateFocusStateFromActiveElement(true);
74
+ const row = this.updateFocusStateFromActiveElement();
75
+ if (row) {
76
+ if (this.hasRowOrCellFocusType()
77
+ && this.rowIndex !== row.resolvedRowIndex) {
78
+ this.setRowFocusState(row.resolvedRowIndex);
79
+ }
80
+ }
68
81
  // Currently, clicking a non-interactive cell only updates the focus state to that row, it
69
82
  // doesn't focus the cell. If we revisit this, we most likely need to set the cells to tabindex=-1
70
83
  // upfront too, so their focusing behavior is consistent whether they've been previously keyboard
@@ -137,6 +150,12 @@ export class KeyboardNavigationManager {
137
150
  }
138
151
  }
139
152
  };
153
+ this.onPointerDown = () => {
154
+ this.focusedViaPointer = true;
155
+ };
156
+ this.onPointerUpOrCancel = () => {
157
+ this.focusedViaPointer = false;
158
+ };
140
159
  this.onViewportKeyDown = (event) => {
141
160
  let handleEvent = !this.inNavigationMode
142
161
  && (event.key === keyArrowUp || event.key === keyArrowDown);
@@ -174,6 +193,9 @@ export class KeyboardNavigationManager {
174
193
  connect() {
175
194
  this.table.addEventListener('keydown', this.onCaptureKeyDown, { capture: true });
176
195
  this.table.addEventListener('keydown', this.onKeyDown);
196
+ this.table.addEventListener('pointerdown', this.onPointerDown);
197
+ this.table.addEventListener('pointerup', this.onPointerUpOrCancel);
198
+ this.table.addEventListener('pointercancel', this.onPointerUpOrCancel);
177
199
  this.table.addEventListener('focusin', this.onTableFocusIn);
178
200
  this.table.addEventListener('focusout', this.onTableFocusOut);
179
201
  this.table.viewport.addEventListener('keydown', this.onViewportKeyDown);
@@ -185,6 +207,9 @@ export class KeyboardNavigationManager {
185
207
  disconnect() {
186
208
  this.table.removeEventListener('keydown', this.onCaptureKeyDown, { capture: true });
187
209
  this.table.removeEventListener('keydown', this.onKeyDown);
210
+ this.table.removeEventListener('pointerdown', this.onPointerDown);
211
+ this.table.removeEventListener('pointerup', this.onPointerUpOrCancel);
212
+ this.table.removeEventListener('pointercancel', this.onPointerUpOrCancel);
188
213
  this.table.removeEventListener('focusin', this.onTableFocusIn);
189
214
  this.table.removeEventListener('focusout', this.onTableFocusOut);
190
215
  this.table.viewport.removeEventListener('keydown', this.onViewportKeyDown);
@@ -276,6 +301,13 @@ export class KeyboardNavigationManager {
276
301
  this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
277
302
  }
278
303
  }
304
+ focusSomethingOtherThanTheTable() {
305
+ this.setDefaultFocus();
306
+ if (this.focusType === TableFocusType.none) {
307
+ // nothing within the table to focus
308
+ this.table.blur();
309
+ }
310
+ }
279
311
  onEnterPressed(ctrlKey) {
280
312
  let row;
281
313
  let rowElements;
@@ -451,7 +483,7 @@ export class KeyboardNavigationManager {
451
483
  this.headerActionIndex = nextFocusState.headerActionIndex ?? this.headerActionIndex;
452
484
  this.cellContentIndex = nextFocusState.cellContentIndex ?? this.cellContentIndex;
453
485
  if (this.hasRowOrCellFocusType()) {
454
- this.focusCurrentRow(false);
486
+ this.focusCurrentRow(!this.focusedViaPointer);
455
487
  }
456
488
  else {
457
489
  this.focusHeaderElement();
@@ -627,35 +659,39 @@ export class KeyboardNavigationManager {
627
659
  }
628
660
  return false;
629
661
  }
630
- updateFocusStateFromActiveElement(setRowFocus) {
662
+ updateFocusStateFromActiveElement() {
631
663
  // If the user is interacting with the table with non-keyboard methods (like mouse), we need to
632
664
  // update our focus state based on the current active/focused element
633
- const activeElement = this.getActiveElement();
634
- if (activeElement) {
635
- const row = this.getContainingRow(activeElement);
636
- if (row) {
637
- if (!(row instanceof TableGroupRow)) {
638
- const cell = this.getContainingCell(activeElement);
639
- if (cell) {
640
- const columnIndex = this.table.visibleColumns.indexOf(cell.column);
641
- if (cell.actionMenuButton === activeElement) {
642
- this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
643
- return;
644
- }
645
- const contentIndex = cell.cellView.tabbableChildren.indexOf(activeElement);
646
- if (contentIndex > -1) {
647
- this.setCellContentFocusState(contentIndex, row.resolvedRowIndex, columnIndex, false);
648
- return;
649
- }
650
- }
651
- }
652
- if (setRowFocus
653
- && this.hasRowOrCellFocusType()
654
- && this.rowIndex !== row.resolvedRowIndex) {
655
- this.setRowFocusState(row.resolvedRowIndex);
656
- }
665
+ const { activeElement, row, cell } = this.getActiveElementCellAndRow();
666
+ if (!cell) {
667
+ return row;
668
+ }
669
+ const columnIndex = this.table.visibleColumns.indexOf(cell.column);
670
+ if (cell.actionMenuButton === activeElement) {
671
+ this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
672
+ }
673
+ else {
674
+ const contentIndex = cell.cellView.tabbableChildren.indexOf(activeElement);
675
+ if (contentIndex > -1) {
676
+ this.setCellContentFocusState(contentIndex, row.resolvedRowIndex, columnIndex, false);
657
677
  }
658
678
  }
679
+ return row;
680
+ }
681
+ getActiveElementCellAndRow() {
682
+ const activeElement = this.getActiveElement();
683
+ if (!activeElement) {
684
+ return {};
685
+ }
686
+ const row = this.getContainingRow(activeElement);
687
+ if (!row) {
688
+ return { activeElement };
689
+ }
690
+ if (row instanceof TableGroupRow) {
691
+ return { activeElement, row };
692
+ }
693
+ const cell = this.getContainingCell(activeElement);
694
+ return { activeElement, row, cell };
659
695
  }
660
696
  focusElement(element, focusOptions) {
661
697
  const previousActiveElement = this.getActiveElement();
@@ -686,13 +722,6 @@ export class KeyboardNavigationManager {
686
722
  menuButton.classList.remove('cell-action-menu-focused');
687
723
  }
688
724
  }
689
- setFocusOnHeader() {
690
- if (this.hasHeaderFocusType()) {
691
- return this.focusHeaderElement();
692
- }
693
- this.setDefaultFocus();
694
- return this.focusType !== TableFocusType.none;
695
- }
696
725
  setDefaultFocus() {
697
726
  const headerElements = this.getTableHeaderFocusableElements();
698
727
  if (!this.trySetHeaderActionFocus(headerElements, 0)
@@ -702,22 +731,19 @@ export class KeyboardNavigationManager {
702
731
  }
703
732
  }
704
733
  scrollToAndFocusRow(totalRowIndex, scrollOptions) {
705
- if (totalRowIndex >= 0 && totalRowIndex < this.table.tableData.length) {
706
- switch (this.focusType) {
707
- case TableFocusType.none:
708
- case TableFocusType.headerActions:
709
- case TableFocusType.columnHeader:
710
- this.setRowFocusState(totalRowIndex);
711
- break;
712
- default:
713
- break;
714
- }
715
- this.rowIndex = totalRowIndex;
716
- this.virtualizer.scrollToIndex(totalRowIndex, scrollOptions);
717
- this.focusCurrentRow(true);
718
- return true;
734
+ if (!this.rowIndexIsValid(totalRowIndex)) {
735
+ return false;
719
736
  }
720
- return false;
737
+ if (!this.hasRowOrCellFocusType()) {
738
+ this.setRowFocusState();
739
+ }
740
+ this.rowIndex = totalRowIndex;
741
+ this.virtualizer.scrollToIndex(totalRowIndex, scrollOptions);
742
+ this.focusCurrentRow(!this.focusedViaPointer);
743
+ return true;
744
+ }
745
+ rowIndexIsValid(rowIndex) {
746
+ return rowIndex >= 0 && rowIndex < this.table.tableData.length;
721
747
  }
722
748
  focusCurrentRow(allowScroll) {
723
749
  const visibleRowIndex = this.getCurrentRowVisibleIndex();
@@ -791,7 +817,7 @@ export class KeyboardNavigationManager {
791
817
  break;
792
818
  }
793
819
  if (focusableElement) {
794
- this.focusElement(focusableElement);
820
+ this.focusElement(focusableElement, { preventScroll: this.focusedViaPointer });
795
821
  return true;
796
822
  }
797
823
  return false;
@@ -912,7 +938,7 @@ export class KeyboardNavigationManager {
912
938
  trySetRowSelectionCheckboxFocus(rowElements) {
913
939
  if (rowElements?.selectionCheckbox) {
914
940
  this.focusType = TableFocusType.rowSelectionCheckbox;
915
- this.focusCurrentRow(true);
941
+ this.focusCurrentRow(!this.focusedViaPointer);
916
942
  return true;
917
943
  }
918
944
  return false;
@@ -1001,7 +1027,7 @@ export class KeyboardNavigationManager {
1001
1027
  this.rowIndex = rowIndex;
1002
1028
  this.columnIndex = columnIndex;
1003
1029
  if (focusElement) {
1004
- this.focusCurrentRow(true);
1030
+ this.focusCurrentRow(!this.focusedViaPointer);
1005
1031
  }
1006
1032
  }
1007
1033
  isResolvedRowType(row) {