@ni/nimble-components 29.6.0 → 29.7.0

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 (58) hide show
  1. package/dist/all-components-bundle.js +1302 -41
  2. package/dist/all-components-bundle.js.map +1 -1
  3. package/dist/all-components-bundle.min.js +4053 -3810
  4. package/dist/all-components-bundle.min.js.map +1 -1
  5. package/dist/esm/table/components/cell/index.d.ts +9 -0
  6. package/dist/esm/table/components/cell/index.js +20 -0
  7. package/dist/esm/table/components/cell/index.js.map +1 -1
  8. package/dist/esm/table/components/cell/styles.js +17 -1
  9. package/dist/esm/table/components/cell/styles.js.map +1 -1
  10. package/dist/esm/table/components/cell/template.js +10 -2
  11. package/dist/esm/table/components/cell/template.js.map +1 -1
  12. package/dist/esm/table/components/group-row/index.d.ts +11 -3
  13. package/dist/esm/table/components/group-row/index.js +13 -1
  14. package/dist/esm/table/components/group-row/index.js.map +1 -1
  15. package/dist/esm/table/components/group-row/styles.js +7 -1
  16. package/dist/esm/table/components/group-row/styles.js.map +1 -1
  17. package/dist/esm/table/components/group-row/template.js +1 -1
  18. package/dist/esm/table/components/group-row/template.js.map +1 -1
  19. package/dist/esm/table/components/header/styles.js +7 -1
  20. package/dist/esm/table/components/header/styles.js.map +1 -1
  21. package/dist/esm/table/components/row/index.d.ts +16 -4
  22. package/dist/esm/table/components/row/index.js +34 -6
  23. package/dist/esm/table/components/row/index.js.map +1 -1
  24. package/dist/esm/table/components/row/styles.js +39 -1
  25. package/dist/esm/table/components/row/styles.js.map +1 -1
  26. package/dist/esm/table/components/row/template.js +4 -2
  27. package/dist/esm/table/components/row/template.js.map +1 -1
  28. package/dist/esm/table/index.d.ts +22 -2
  29. package/dist/esm/table/index.js +58 -1
  30. package/dist/esm/table/index.js.map +1 -1
  31. package/dist/esm/table/models/keyboard-navigation-manager.d.ts +96 -0
  32. package/dist/esm/table/models/keyboard-navigation-manager.js +1015 -0
  33. package/dist/esm/table/models/keyboard-navigation-manager.js.map +1 -0
  34. package/dist/esm/table/models/table-update-tracker.d.ts +2 -1
  35. package/dist/esm/table/models/table-update-tracker.js +20 -3
  36. package/dist/esm/table/models/table-update-tracker.js.map +1 -1
  37. package/dist/esm/table/models/virtualizer.d.ts +6 -2
  38. package/dist/esm/table/models/virtualizer.js +16 -22
  39. package/dist/esm/table/models/virtualizer.js.map +1 -1
  40. package/dist/esm/table/styles.js +21 -0
  41. package/dist/esm/table/styles.js.map +1 -1
  42. package/dist/esm/table/template.js +21 -3
  43. package/dist/esm/table/template.js.map +1 -1
  44. package/dist/esm/table/testing/table.pageobject.d.ts +7 -2
  45. package/dist/esm/table/testing/table.pageobject.js +16 -9
  46. package/dist/esm/table/testing/table.pageobject.js.map +1 -1
  47. package/dist/esm/table/types.d.ts +38 -0
  48. package/dist/esm/table/types.js +14 -0
  49. package/dist/esm/table/types.js.map +1 -1
  50. package/dist/esm/table-column/anchor/cell-view/index.d.ts +3 -0
  51. package/dist/esm/table-column/anchor/cell-view/index.js +13 -0
  52. package/dist/esm/table-column/anchor/cell-view/index.js.map +1 -1
  53. package/dist/esm/table-column/anchor/cell-view/template.js +4 -2
  54. package/dist/esm/table-column/anchor/cell-view/template.js.map +1 -1
  55. package/dist/esm/table-column/base/cell-view/index.d.ts +5 -0
  56. package/dist/esm/table-column/base/cell-view/index.js +7 -0
  57. package/dist/esm/table-column/base/cell-view/index.js.map +1 -1
  58. package/package.json +1 -1
@@ -16280,7 +16280,7 @@
16280
16280
 
16281
16281
  /**
16282
16282
  * Do not edit directly
16283
- * Generated on Thu, 20 Jun 2024 19:54:16 GMT
16283
+ * Generated on Tue, 25 Jun 2024 18:25:46 GMT
16284
16284
  */
16285
16285
 
16286
16286
  const Information100DarkUi = "#a46eff";
@@ -16699,6 +16699,9 @@
16699
16699
  const keyEnter = "Enter";
16700
16700
  const keyEscape = "Escape";
16701
16701
  const keyHome = "Home";
16702
+ const keyFunction2 = "F2";
16703
+ const keyPageDown = "PageDown";
16704
+ const keyPageUp = "PageUp";
16702
16705
  const keyShift = "Shift";
16703
16706
  const keySpace = " ";
16704
16707
  const keyTab = "Tab";
@@ -64433,6 +64436,20 @@ img.ProseMirror-separator {
64433
64436
  selected: 'selected',
64434
64437
  partiallySelected: 'partially-selected'
64435
64438
  };
64439
+ /**
64440
+ * @internal
64441
+ * Table keyboard focus types
64442
+ */
64443
+ const TableFocusType = {
64444
+ none: 'none',
64445
+ columnHeader: 'columnHeader',
64446
+ headerActions: 'headerActions',
64447
+ row: 'row',
64448
+ rowSelectionCheckbox: 'rowSelectionCheckbox',
64449
+ cell: 'cell',
64450
+ cellActionMenu: 'cellActionMenu',
64451
+ cellContent: 'cellContent'
64452
+ };
64436
64453
 
64437
64454
  /**
64438
64455
  * The possible operations to use when sorting a table column.
@@ -64514,6 +64531,13 @@ img.ProseMirror-separator {
64514
64531
  this.delegatedEvents = [];
64515
64532
  this.delegatedEventHandler = () => { };
64516
64533
  }
64534
+ /**
64535
+ * Gets the child elements in this cell view that should be able to be reached via Tab/ Shift-Tab,
64536
+ * if any.
64537
+ */
64538
+ get tabbableChildren() {
64539
+ return [];
64540
+ }
64517
64541
  /**
64518
64542
  * Called if an element inside this cell view has focus, and this row/cell is being recycled.
64519
64543
  * Expected implementation is to commit changes as needed, and blur the focusable element (or close
@@ -64897,6 +64921,15 @@ img.ProseMirror-separator {
64897
64921
  --ni-private-column-divider-padding: 3px;
64898
64922
  }
64899
64923
 
64924
+ :host(${focusVisible}) {
64925
+ ${
64926
+ /* The table can briefly be focused in some keyboard nav cases (e.g. regaining focus and we
64927
+ need to scroll to the previously focused row first). Ensure that we don't get the browser-default
64928
+ focus outline in that case.
64929
+ ) */ ''}
64930
+ outline: none;
64931
+ }
64932
+
64900
64933
  .disable-select {
64901
64934
  ${userSelectNone}
64902
64935
  }
@@ -65027,6 +65060,10 @@ img.ProseMirror-separator {
65027
65060
  position: relative;
65028
65061
  }
65029
65062
 
65063
+ .table-viewport${focusVisible} {
65064
+ outline: none;
65065
+ }
65066
+
65030
65067
  .table-scroll {
65031
65068
  pointer-events: none;
65032
65069
  position: absolute;
@@ -65053,10 +65090,17 @@ img.ProseMirror-separator {
65053
65090
 
65054
65091
  .group-row {
65055
65092
  position: relative;
65093
+ --ni-private-table-cell-focus-offset-multiplier: 0;
65056
65094
  }
65057
65095
 
65058
65096
  .row {
65059
65097
  position: relative;
65098
+ --ni-private-table-cell-focus-offset-multiplier: 0;
65099
+ }
65100
+
65101
+ .collapse-all-visible .row,
65102
+ .collapse-all-visible .group-row {
65103
+ --ni-private-table-cell-focus-offset-multiplier: 1;
65060
65104
  }
65061
65105
 
65062
65106
  .accessibly-hidden {
@@ -65087,6 +65131,11 @@ img.ProseMirror-separator {
65087
65131
  cursor: default;
65088
65132
  }
65089
65133
 
65134
+ :host(${focusVisible}) {
65135
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65136
+ outline-offset: calc(-2 * ${borderWidth});
65137
+ }
65138
+
65090
65139
  .sort-indicator,
65091
65140
  .grouped-indicator {
65092
65141
  flex: 0 0 auto;
@@ -65240,6 +65289,11 @@ img.ProseMirror-separator {
65240
65289
  background-color: ${fillHoverSelectedColor};
65241
65290
  }
65242
65291
 
65292
+ :host(${focusVisible}) {
65293
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65294
+ outline-offset: calc(-2 * ${borderWidth});
65295
+ }
65296
+
65243
65297
  .expand-collapse-button {
65244
65298
  flex: 0 0 auto;
65245
65299
  margin-left: max(
@@ -65314,6 +65368,34 @@ img.ProseMirror-separator {
65314
65368
  --ni-private-table-cell-action-menu-display: block;
65315
65369
  }
65316
65370
 
65371
+ nimble-table-cell${focusVisible} {
65372
+ --ni-private-table-cell-action-menu-display: block;
65373
+ }
65374
+
65375
+ nimble-table-cell:first-of-type${focusVisible} {
65376
+ margin-left: calc(
65377
+ -1 * (${controlHeight} - ${smallPadding}) * var(--ni-private-table-cell-focus-offset-multiplier)
65378
+ );
65379
+ padding-left: calc(
65380
+ (${controlHeight} - ${mediumPadding}) *
65381
+ var(--ni-private-table-cell-focus-offset-multiplier) +
65382
+ ${mediumPadding}
65383
+ );
65384
+ }
65385
+
65386
+ nimble-table-cell:first-of-type${focusVisible}::before {
65387
+ content: '';
65388
+ display: block;
65389
+ width: calc(
65390
+ (
65391
+ ${controlHeight} *
65392
+ var(--ni-private-table-cell-nesting-level) +
65393
+ ${smallPadding}
65394
+ ) * var(--ni-private-table-cell-focus-offset-multiplier)
65395
+ );
65396
+ height: ${controlHeight};
65397
+ }
65398
+
65317
65399
  :host(:hover) nimble-table-cell {
65318
65400
  --ni-private-table-cell-action-menu-display: block;
65319
65401
  }
@@ -65321,6 +65403,10 @@ img.ProseMirror-separator {
65321
65403
  :host([selected]) nimble-table-cell {
65322
65404
  --ni-private-table-cell-action-menu-display: block;
65323
65405
  }
65406
+
65407
+ :host(${focusVisible}) nimble-table-cell {
65408
+ --ni-private-table-cell-action-menu-display: block;
65409
+ }
65324
65410
  `.withBehaviors(themeBehavior(Theme.color, css `
65325
65411
  :host([selectable]:not([selected]):hover)::before {
65326
65412
  background-color: ${hexToRgbaCssColor(White, 0.05)};
@@ -65351,6 +65437,15 @@ img.ProseMirror-separator {
65351
65437
  --ni-private-table-cell-action-menu-display: block;
65352
65438
  }
65353
65439
 
65440
+ :host(${focusVisible}) {
65441
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65442
+ outline-offset: -2px;
65443
+ }
65444
+
65445
+ .cell-view-container {
65446
+ display: contents;
65447
+ }
65448
+
65354
65449
  .cell-view {
65355
65450
  overflow: hidden;
65356
65451
  }
@@ -65363,19 +65458,33 @@ img.ProseMirror-separator {
65363
65458
  height: ${controlSlimHeight};
65364
65459
  align-self: center;
65365
65460
  }
65461
+
65462
+ ${
65463
+ /* This CSS class is applied dynamically by KeyboardNavigationManager */ ''}
65464
+ .action-menu.cell-action-menu-focused {
65465
+ display: block;
65466
+ }
65366
65467
  `;
65367
65468
 
65368
65469
  // prettier-ignore
65369
65470
  const template$h = html `
65370
- <template role="cell" style="--ni-private-table-cell-nesting-level: ${x => x.nestingLevel}">
65371
- ${x => x.cellViewTemplate}
65471
+ <template role="cell" style="--ni-private-table-cell-nesting-level: ${x => x.nestingLevel}"
65472
+ @focusin="${x => x.onCellFocusIn()}"
65473
+ @blur="${x => x.onCellBlur()}"
65474
+ >
65475
+ <div ${ref('cellViewContainer')} class="cell-view-container" @focusin="${x => x.onCellViewFocusIn()}">
65476
+ ${x => x.cellViewTemplate}
65477
+ </div>
65372
65478
  ${when(x => x.hasActionMenu, html `
65373
65479
  <${menuButtonTag} ${ref('actionMenuButton')}
65374
65480
  content-hidden
65375
65481
  appearance="${ButtonAppearance.ghost}"
65482
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
65483
+ tabindex="-1"
65376
65484
  @beforetoggle="${(x, c) => x.onActionMenuBeforeToggle(c.event)}"
65377
65485
  @toggle="${(x, c) => x.onActionMenuToggle(c.event)}"
65378
65486
  @click="${(_, c) => c.event.stopPropagation()}"
65487
+ @blur="${x => x.onActionMenuBlur()}"
65379
65488
  class="action-menu"
65380
65489
  title="${x => (x.actionMenuLabel ? x.actionMenuLabel : tableCellActionMenuLabel.getValueFor(x))}"
65381
65490
  >
@@ -65398,6 +65507,11 @@ img.ProseMirror-separator {
65398
65507
  this.menuOpen = false;
65399
65508
  this.nestingLevel = 0;
65400
65509
  }
65510
+ /** @internal */
65511
+ get cellView() {
65512
+ return this.cellViewContainer
65513
+ .firstElementChild;
65514
+ }
65401
65515
  onActionMenuBeforeToggle(event) {
65402
65516
  this.$emit('cell-action-menu-beforetoggle', event.detail);
65403
65517
  }
@@ -65405,6 +65519,18 @@ img.ProseMirror-separator {
65405
65519
  this.menuOpen = event.detail.newState;
65406
65520
  this.$emit('cell-action-menu-toggle', event.detail);
65407
65521
  }
65522
+ onActionMenuBlur() {
65523
+ this.$emit('cell-action-menu-blur', this);
65524
+ }
65525
+ onCellViewFocusIn() {
65526
+ this.$emit('cell-view-focus-in', this);
65527
+ }
65528
+ onCellFocusIn() {
65529
+ this.$emit('cell-focus-in', this);
65530
+ }
65531
+ onCellBlur() {
65532
+ this.$emit('cell-blur', this);
65533
+ }
65408
65534
  }
65409
65535
  __decorate$1([
65410
65536
  observable
@@ -65430,6 +65556,9 @@ img.ProseMirror-separator {
65430
65556
  __decorate$1([
65431
65557
  observable
65432
65558
  ], TableCell.prototype, "cellViewTemplate", void 0);
65559
+ __decorate$1([
65560
+ observable
65561
+ ], TableCell.prototype, "cellViewContainer", void 0);
65433
65562
  __decorate$1([
65434
65563
  observable
65435
65564
  ], TableCell.prototype, "nestingLevel", void 0);
@@ -65451,11 +65580,13 @@ img.ProseMirror-separator {
65451
65580
  >
65452
65581
  ${when(x => !x.rowOperationGridCellHidden, html `
65453
65582
  <span role="gridcell" class="row-operations-container">
65454
- ${when(x => x.selectable && !x.hideSelection, html `
65583
+ ${when(x => x.showSelectionCheckbox, html `
65455
65584
  <${checkboxTag}
65456
65585
  ${ref('selectionCheckbox')}
65457
65586
  class="selection-checkbox"
65458
- @change="${(x, c) => x.onSelectionChange(c.event)}"
65587
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
65588
+ tabindex="-1"
65589
+ @change="${(x, c) => x.onSelectionCheckboxChange(c.event)}"
65459
65590
  @click="${(_, c) => c.event.stopPropagation()}"
65460
65591
  title="${x => tableRowSelectLabel.getValueFor(x)}"
65461
65592
  aria-label="${x => tableRowSelectLabel.getValueFor(x)}"
@@ -65576,6 +65707,10 @@ img.ProseMirror-separator {
65576
65707
  get isNestedParent() {
65577
65708
  return this.isParentRow && this.nestingLevel > 0;
65578
65709
  }
65710
+ /** @internal */
65711
+ get showSelectionCheckbox() {
65712
+ return this.selectable && !this.hideSelection;
65713
+ }
65579
65714
  get ariaSelected() {
65580
65715
  if (this.selectable) {
65581
65716
  return this.selected ? 'true' : 'false';
@@ -65583,16 +65718,20 @@ img.ProseMirror-separator {
65583
65718
  return null;
65584
65719
  }
65585
65720
  /** @internal */
65586
- onSelectionChange(event) {
65721
+ onSelectionCheckboxChange(event) {
65587
65722
  if (this.ignoreSelectionChangeEvents) {
65588
65723
  return;
65589
65724
  }
65590
65725
  const checkbox = event.target;
65591
65726
  const checked = checkbox.checked;
65592
- this.selected = checked;
65727
+ this.onSelectionChange(!checked, checked);
65728
+ }
65729
+ /** @internal */
65730
+ onSelectionChange(oldState, newState) {
65731
+ this.selected = newState;
65593
65732
  const detail = {
65594
- oldState: !checked,
65595
- newState: checked
65733
+ oldState,
65734
+ newState
65596
65735
  };
65597
65736
  this.$emit('row-selection-toggle', detail);
65598
65737
  }
@@ -65623,6 +65762,20 @@ img.ProseMirror-separator {
65623
65762
  this.updateCellStates();
65624
65763
  }
65625
65764
  }
65765
+ /** @internal */
65766
+ getFocusableElements() {
65767
+ return {
65768
+ selectionCheckbox: this.showSelectionCheckbox
65769
+ ? this.selectionCheckbox
65770
+ : undefined,
65771
+ cells: Array.from(this.cellContainer.querySelectorAll(tableCellTag)).map(cell => ({
65772
+ cell,
65773
+ actionMenuButton: cell.hasActionMenu
65774
+ ? cell.actionMenuButton
65775
+ : undefined
65776
+ }))
65777
+ };
65778
+ }
65626
65779
  onRowExpandToggle(event) {
65627
65780
  const expandEventDetail = {
65628
65781
  oldState: this.expanded,
@@ -65630,7 +65783,7 @@ img.ProseMirror-separator {
65630
65783
  recordId: this.recordId
65631
65784
  };
65632
65785
  this.$emit('row-expand-toggle', expandEventDetail);
65633
- event.stopImmediatePropagation();
65786
+ event?.stopImmediatePropagation();
65634
65787
  // To avoid a visual glitch with improper expand/collapse icons performing an
65635
65788
  // animation (due to visual re-use apparently), we apply a class to the
65636
65789
  // contained expand-collapse button temporarily. We use the 'transitionend' event
@@ -65742,6 +65895,9 @@ img.ProseMirror-separator {
65742
65895
  __decorate$1([
65743
65896
  observable
65744
65897
  ], TableRow.prototype, "nestingLevel", void 0);
65898
+ __decorate$1([
65899
+ observable
65900
+ ], TableRow.prototype, "resolvedRowIndex", void 0);
65745
65901
  __decorate$1([
65746
65902
  attr({ attribute: 'is-parent-row', mode: 'boolean' })
65747
65903
  ], TableRow.prototype, "isParentRow", void 0);
@@ -65772,6 +65928,9 @@ img.ProseMirror-separator {
65772
65928
  __decorate$1([
65773
65929
  volatile
65774
65930
  ], TableRow.prototype, "isNestedParent", null);
65931
+ __decorate$1([
65932
+ volatile
65933
+ ], TableRow.prototype, "showSelectionCheckbox", null);
65775
65934
  __decorate$1([
65776
65935
  volatile
65777
65936
  ], TableRow.prototype, "ariaSelected", null);
@@ -65822,6 +65981,11 @@ img.ProseMirror-separator {
65822
65981
  background-color: ${fillHoverColor};
65823
65982
  }
65824
65983
 
65984
+ :host(${focusVisible}) {
65985
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65986
+ outline-offset: calc(-2 * ${borderWidth});
65987
+ }
65988
+
65825
65989
  .expand-collapse-button {
65826
65990
  margin-left: calc(
65827
65991
  ${mediumPadding} + ${standardPadding} * 2 *
@@ -65882,7 +66046,7 @@ img.ProseMirror-separator {
65882
66046
  <${checkboxTag}
65883
66047
  ${ref('selectionCheckbox')}
65884
66048
  class="selection-checkbox"
65885
- @change="${(x, c) => x.onSelectionChange(c.event)}"
66049
+ @change="${(x, c) => x.onSelectionCheckboxChange(c.event)}"
65886
66050
  @click="${(_, c) => c.event.stopPropagation()}"
65887
66051
  title="${x => tableGroupSelectAllLabel.getValueFor(x)}"
65888
66052
  aria-label="${x => tableGroupSelectAllLabel.getValueFor(x)}"
@@ -65948,7 +66112,7 @@ img.ProseMirror-separator {
65948
66112
  this.expandIcon.addEventListener('transitionend', this.removeAnimatingClass);
65949
66113
  }
65950
66114
  /** @internal */
65951
- onSelectionChange(event) {
66115
+ onSelectionCheckboxChange(event) {
65952
66116
  if (this.ignoreSelectionChangeEvents) {
65953
66117
  return;
65954
66118
  }
@@ -65963,6 +66127,15 @@ img.ProseMirror-separator {
65963
66127
  };
65964
66128
  this.$emit('group-selection-toggle', detail);
65965
66129
  }
66130
+ /** @internal */
66131
+ getFocusableElements() {
66132
+ return {
66133
+ selectionCheckbox: this.selectable
66134
+ ? this.selectionCheckbox
66135
+ : undefined,
66136
+ cells: []
66137
+ };
66138
+ }
65966
66139
  selectionStateChanged() {
65967
66140
  this.setSelectionCheckboxState();
65968
66141
  }
@@ -65985,6 +66158,9 @@ img.ProseMirror-separator {
65985
66158
  __decorate$1([
65986
66159
  observable
65987
66160
  ], TableGroupRow.prototype, "nestingLevel", void 0);
66161
+ __decorate$1([
66162
+ observable
66163
+ ], TableGroupRow.prototype, "resolvedRowIndex", void 0);
65988
66164
  __decorate$1([
65989
66165
  observable
65990
66166
  ], TableGroupRow.prototype, "immediateChildCount", void 0);
@@ -66018,6 +66194,8 @@ img.ProseMirror-separator {
66018
66194
  const template$e = html `
66019
66195
  <template
66020
66196
  role="treegrid"
66197
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66198
+ tabindex="0"
66021
66199
  aria-multiselectable="${x => x.ariaMultiSelectable}"
66022
66200
  ${children$1({ property: 'childItems', filter: elements() })}
66023
66201
  >
@@ -66045,6 +66223,8 @@ img.ProseMirror-separator {
66045
66223
  <span class="checkbox-container">
66046
66224
  <${checkboxTag}
66047
66225
  ${ref('selectionCheckbox')}
66226
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66227
+ tabindex="-1"
66048
66228
  class="${x => `selection-checkbox ${x.selectionMode ? x.selectionMode : ''}`}"
66049
66229
  @change="${(x, c) => x.onAllRowsSelectionChange(c.event)}"
66050
66230
  title="${x => tableSelectAllLabel.getValueFor(x)}"
@@ -66054,6 +66234,9 @@ img.ProseMirror-separator {
66054
66234
  </span>
66055
66235
  `)}
66056
66236
  <${buttonTag}
66237
+ ${ref('collapseAllButton')}
66238
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66239
+ tabindex="-1"
66057
66240
  class="collapse-all-button ${x => `${x.showCollapseAll ? 'visible' : ''}`}"
66058
66241
  content-hidden
66059
66242
  appearance="${ButtonAppearance.ghost}"
@@ -66081,9 +66264,11 @@ img.ProseMirror-separator {
66081
66264
  `)}
66082
66265
  <${tableHeaderTag}
66083
66266
  class="header"
66267
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager (if column sorting not disabled) */}
66084
66268
  sort-direction="${x => (typeof x.columnInternals.currentSortIndex === 'number' ? x.columnInternals.currentSortDirection : TableColumnSortDirection.none)}"
66085
66269
  ?first-sorted-column="${(x, c) => x === c.parent.firstSortedColumn}"
66086
66270
  ?indicators-hidden="${x => x.columnInternals.hideHeaderIndicators}"
66271
+ @keydown="${(x, c) => c.parent.onHeaderKeyDown(x, c.event)}"
66087
66272
  @click="${(x, c) => c.parent.toggleColumnSort(x, c.event.shiftKey)}"
66088
66273
  :isGrouped=${x => (typeof x.columnInternals.groupIndex === 'number' && !x.columnInternals.groupingDisabled)}
66089
66274
  >
@@ -66107,15 +66292,17 @@ img.ProseMirror-separator {
66107
66292
  </span>
66108
66293
  </div>
66109
66294
  </div>
66110
- <div class="table-viewport" ${ref('viewport')}>
66295
+ <div class="table-viewport" tabindex="-1" ${ref('viewport')}>
66111
66296
  <div class="table-scroll"></div>
66112
- <div class="table-row-container" ${children$1({ property: 'rowElements', filter: elements(tableRowTag) })}
66297
+ <div class="table-row-container ${x => `${x.showCollapseAll ? 'collapse-all-visible' : ''}`}" ${children$1({ property: 'rowElements', filter: elements(`${tableRowTag}, ${tableGroupRowTag}`) })}
66113
66298
  role="rowgroup">
66114
66299
  ${when(x => x.columns.length > 0 && x.canRenderRows, html `
66115
66300
  ${repeat(x => x.virtualizer.visibleItems, html `
66116
66301
  ${when((x, c) => c.parent.tableData[x.index]?.isGroupRow, html `
66117
66302
  <${tableGroupRowTag}
66118
66303
  class="group-row"
66304
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66305
+ tabindex="-1"
66119
66306
  :groupRowValue="${(x, c) => c.parent.tableData[x.index]?.groupRowValue}"
66120
66307
  ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}"
66121
66308
  :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}"
@@ -66123,6 +66310,9 @@ img.ProseMirror-separator {
66123
66310
  :groupColumn="${(x, c) => c.parent.tableData[x.index]?.groupColumn}"
66124
66311
  ?selectable="${(_, c) => c.parent.selectionMode === TableRowSelectionMode.multiple}"
66125
66312
  selection-state="${(x, c) => c.parent.tableData[x.index]?.selectionState}"
66313
+ :resolvedRowIndex="${x => x.index}"
66314
+ @focusin="${(_, c) => c.parent.onRowFocusIn(c.event)}"
66315
+ @blur="${(_, c) => c.parent.onRowBlur(c.event)}"
66126
66316
  @group-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event)}"
66127
66317
  @group-expand-toggle="${(x, c) => c.parent.handleGroupRowExpanded(x.index, c.event)}"
66128
66318
  >
@@ -66131,6 +66321,8 @@ img.ProseMirror-separator {
66131
66321
  ${when((x, c) => !c.parent.tableData[x.index]?.isGroupRow, html `
66132
66322
  <${tableRowTag}
66133
66323
  class="row"
66324
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66325
+ tabindex="-1"
66134
66326
  record-id="${(x, c) => c.parent.tableData[x.index]?.id}"
66135
66327
  ?selectable="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.none}"
66136
66328
  ?selected="${(x, c) => c.parent.tableData[x.index]?.selectionState === TableRowSelectionState.selected}"
@@ -66142,12 +66334,14 @@ img.ProseMirror-separator {
66142
66334
  :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}"
66143
66335
  ?row-operation-grid-cell-hidden="${(_, c) => !c.parent.showRowOperationColumn}"
66144
66336
  ?loading="${(x, c) => c.parent.tableData[x.index]?.isLoadingChildren}"
66337
+ :resolvedRowIndex="${x => x.index}"
66145
66338
  @click="${(x, c) => c.parent.onRowClick(x.index, c.event)}"
66339
+ @focusin="${(_, c) => c.parent.onRowFocusIn(c.event)}"
66340
+ @blur="${(_, c) => c.parent.onRowBlur(c.event)}"
66146
66341
  @row-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event)}"
66147
66342
  @row-action-menu-beforetoggle="${(x, c) => c.parent.onRowActionMenuBeforeToggle(x.index, c.event)}"
66148
66343
  @row-action-menu-toggle="${(_, c) => c.parent.onRowActionMenuToggle(c.event)}"
66149
66344
  @row-expand-toggle="${(x, c) => c.parent.handleRowExpanded(x.index)}"
66150
- :dataIndex="${x => x.index}"
66151
66345
  >
66152
66346
  ${when((x, c) => c.parent.openActionMenuRecordId === c.parent.tableData[x.index]?.id, html `
66153
66347
  ${repeat((_, c) => c.parent.actionMenuSlots, html `
@@ -66812,16 +67006,25 @@ img.ProseMirror-separator {
66812
67006
  * @internal
66813
67007
  */
66814
67008
  class Virtualizer {
67009
+ get pageSize() {
67010
+ return this._pageSize;
67011
+ }
67012
+ get rowHeight() {
67013
+ return (parseFloat(controlHeight.getValueFor(this.table))
67014
+ + 2 * parseFloat(borderWidth.getValueFor(this.table)));
67015
+ }
66815
67016
  constructor(table, tanStackTable) {
66816
67017
  this.visibleItems = [];
66817
67018
  this.scrollHeight = 0;
66818
67019
  this.headerContainerMarginRight = 0;
66819
67020
  this.rowContainerYOffset = 0;
67021
+ this._pageSize = 0;
66820
67022
  this.table = table;
66821
67023
  this.tanStackTable = tanStackTable;
66822
67024
  this.viewportResizeObserver = new ResizeObserver(entries => {
66823
67025
  const borderBoxSize = entries[0]?.borderBoxSize[0];
66824
67026
  if (borderBoxSize) {
67027
+ this.updatePageSize();
66825
67028
  // If we have enough rows that a vertical scrollbar is shown, we need to offset the header widths
66826
67029
  // by the same margin so the column headers align with the corresponding rendered cells
66827
67030
  const viewportBoundingWidth = borderBoxSize.inlineSize;
@@ -66842,6 +67045,9 @@ img.ProseMirror-separator {
66842
67045
  this.updateVirtualizer();
66843
67046
  }
66844
67047
  }
67048
+ scrollToIndex(index, options) {
67049
+ this.virtualizer?.scrollToIndex(index, options);
67050
+ }
66845
67051
  updateVirtualizer() {
66846
67052
  const options = this.createVirtualizerOptions();
66847
67053
  if (this.virtualizer) {
@@ -66854,8 +67060,7 @@ img.ProseMirror-separator {
66854
67060
  this.handleVirtualizerChange();
66855
67061
  }
66856
67062
  createVirtualizerOptions() {
66857
- const rowHeight = parseFloat(controlHeight.getValueFor(this.table))
66858
- + 2 * parseFloat(borderWidth.getValueFor(this.table));
67063
+ const rowHeight = this.rowHeight;
66859
67064
  return {
66860
67065
  count: this.tanStackTable.getRowModel().rows.length,
66861
67066
  getScrollElement: () => {
@@ -66872,7 +67077,7 @@ img.ProseMirror-separator {
66872
67077
  };
66873
67078
  }
66874
67079
  handleVirtualizerChange() {
66875
- this.notifyFocusedCellRecycling();
67080
+ this.table.handleFocusedCellRecycling();
66876
67081
  const virtualizer = this.virtualizer;
66877
67082
  this.visibleItems = virtualizer.getVirtualItems();
66878
67083
  this.scrollHeight = virtualizer.getTotalSize();
@@ -66887,24 +67092,8 @@ img.ProseMirror-separator {
66887
67092
  }
66888
67093
  this.rowContainerYOffset = rowContainerYOffset;
66889
67094
  }
66890
- notifyFocusedCellRecycling() {
66891
- let tableFocusedElement = this.table.shadowRoot.activeElement;
66892
- while (tableFocusedElement !== null
66893
- && !(tableFocusedElement instanceof TableCellView)) {
66894
- if (tableFocusedElement.shadowRoot) {
66895
- tableFocusedElement = tableFocusedElement.shadowRoot.activeElement;
66896
- }
66897
- else {
66898
- break;
66899
- }
66900
- }
66901
- if (tableFocusedElement instanceof TableCellView) {
66902
- tableFocusedElement.focusedRecycleCallback();
66903
- }
66904
- if (this.table.openActionMenuRecordId !== undefined) {
66905
- const activeRow = this.table.rowElements.find(row => row.recordId === this.table.openActionMenuRecordId);
66906
- activeRow?.closeOpenActionMenus();
66907
- }
67095
+ updatePageSize() {
67096
+ this._pageSize = Math.round(this.table.viewport.clientHeight / this.rowHeight);
66908
67097
  }
66909
67098
  }
66910
67099
  __decorate$1([
@@ -67264,7 +67453,9 @@ img.ProseMirror-separator {
67264
67453
  'rowParentIds',
67265
67454
  'groupRows',
67266
67455
  'columnIds',
67456
+ 'columnHidden',
67267
67457
  'columnSort',
67458
+ 'columnSortDisabled',
67268
67459
  'columnWidths',
67269
67460
  'columnDefinition',
67270
67461
  'actionMenuSlots',
@@ -67320,6 +67511,13 @@ img.ProseMirror-separator {
67320
67511
  || this.isTracked('columnDefinition')
67321
67512
  || this.isTracked('rowParentIds'));
67322
67513
  }
67514
+ get requiresKeyboardFocusReset() {
67515
+ return (this.isTracked('columnSortDisabled')
67516
+ || this.isTracked('columnDefinition')
67517
+ || this.isTracked('columnHidden')
67518
+ || this.isTracked('selectionMode')
67519
+ || this.isTracked('actionMenuSlots'));
67520
+ }
67323
67521
  trackAllStateChanged() {
67324
67522
  this.trackAll();
67325
67523
  this.queueUpdate();
@@ -67334,13 +67532,20 @@ img.ProseMirror-separator {
67334
67532
  else if (isColumnInternalsProperty(changedColumnProperty, 'operandDataRecordFieldName', 'sortOperation')) {
67335
67533
  this.track('columnDefinition');
67336
67534
  }
67337
- else if (isColumnInternalsProperty(changedColumnProperty, 'sortingDisabled', 'currentSortDirection', 'currentSortIndex')) {
67535
+ else if (isColumnInternalsProperty(changedColumnProperty, 'sortingDisabled')) {
67536
+ this.track('columnSort');
67537
+ this.track('columnSortDisabled');
67538
+ }
67539
+ else if (isColumnInternalsProperty(changedColumnProperty, 'currentSortDirection', 'currentSortIndex')) {
67338
67540
  this.track('columnSort');
67339
67541
  }
67340
- else if (isColumnProperty(changedColumnProperty, 'columnHidden')
67341
- || isColumnInternalsProperty(changedColumnProperty, 'currentFractionalWidth', 'currentPixelWidth', 'minPixelWidth', 'resizingDisabled')) {
67542
+ else if (isColumnInternalsProperty(changedColumnProperty, 'currentFractionalWidth', 'currentPixelWidth', 'minPixelWidth', 'resizingDisabled')) {
67342
67543
  this.track('columnWidths');
67343
67544
  }
67545
+ else if (isColumnProperty(changedColumnProperty, 'columnHidden')) {
67546
+ this.track('columnWidths');
67547
+ this.track('columnHidden');
67548
+ }
67344
67549
  else if (isColumnProperty(changedColumnProperty, 'actionMenuSlot')) {
67345
67550
  this.track('actionMenuSlots');
67346
67551
  }
@@ -67353,6 +67558,7 @@ img.ProseMirror-separator {
67353
67558
  this.track('columnIds');
67354
67559
  this.track('columnDefinition');
67355
67560
  this.track('columnSort');
67561
+ this.track('columnSortDisabled');
67356
67562
  this.track('columnWidths');
67357
67563
  this.track('actionMenuSlots');
67358
67564
  this.track('groupRows');
@@ -67982,6 +68188,992 @@ img.ProseMirror-separator {
67982
68188
  observable
67983
68189
  ], ColumnValidator.prototype, "isColumnValid", void 0);
67984
68190
 
68191
+ /**
68192
+ * Manages the keyboard navigation and focus within the table.
68193
+ * @internal
68194
+ */
68195
+ class KeyboardNavigationManager {
68196
+ get inNavigationMode() {
68197
+ return (this.focusType !== TableFocusType.cellActionMenu
68198
+ && this.focusType !== TableFocusType.cellContent);
68199
+ }
68200
+ constructor(table, virtualizer) {
68201
+ this.table = table;
68202
+ this.virtualizer = virtualizer;
68203
+ this.focusType = TableFocusType.none;
68204
+ this.headerActionIndex = -1;
68205
+ this.rowIndex = -1;
68206
+ this.cellContentIndex = -1;
68207
+ this.columnIndex = -1;
68208
+ this.focusWithinTable = false;
68209
+ this.isCurrentlyFocusingElement = false;
68210
+ this.visibleRowNotifiers = [];
68211
+ this.onTableFocusIn = (event) => {
68212
+ this.focusWithinTable = true;
68213
+ this.updateFocusStateFromActiveElement(false);
68214
+ // Sets initial focus on the appropriate table content
68215
+ const actionMenuOpen = this.table.openActionMenuRecordId !== undefined;
68216
+ if ((event.target === this.table
68217
+ || this.focusType === TableFocusType.none)
68218
+ && !actionMenuOpen) {
68219
+ let focusHeader = true;
68220
+ if (this.hasRowOrCellFocusType()
68221
+ && this.scrollToAndFocusRow(this.rowIndex)) {
68222
+ focusHeader = false;
68223
+ }
68224
+ if (focusHeader && !this.setFocusOnHeader()) {
68225
+ // nothing to focus
68226
+ this.table.blur();
68227
+ }
68228
+ }
68229
+ };
68230
+ this.onTableFocusOut = () => {
68231
+ this.focusWithinTable = false;
68232
+ };
68233
+ this.onCellActionMenuBlur = (event) => {
68234
+ event.stopPropagation();
68235
+ const cell = event.detail;
68236
+ if (cell.actionMenuButton) {
68237
+ // Ensure that action menu buttons get hidden when no longer focused
68238
+ this.setActionMenuButtonFocused(cell.actionMenuButton, false);
68239
+ }
68240
+ };
68241
+ this.onCellViewFocusIn = (event) => {
68242
+ event.stopPropagation();
68243
+ this.updateFocusStateFromActiveElement(false);
68244
+ };
68245
+ this.onCellFocusIn = (event) => {
68246
+ event.stopPropagation();
68247
+ const cell = event.detail;
68248
+ this.updateFocusStateFromActiveElement(true);
68249
+ // Currently, clicking a non-interactive cell only updates the focus state to that row, it
68250
+ // doesn't focus the cell. If we revisit this, we most likely need to set the cells to tabindex=-1
68251
+ // upfront too, so their focusing behavior is consistent whether they've been previously keyboard
68252
+ // focused or not.
68253
+ if (this.focusType === TableFocusType.row
68254
+ && this.getActiveElement() === cell) {
68255
+ this.focusCurrentRow(false);
68256
+ }
68257
+ };
68258
+ this.onCellBlur = (event) => {
68259
+ event.stopPropagation();
68260
+ const cell = event.detail;
68261
+ this.setElementFocusable(cell, false);
68262
+ };
68263
+ this.onCaptureKeyDown = (event) => {
68264
+ let handled = false;
68265
+ if (event.key === keyTab) {
68266
+ handled = this.onTabPressed(event.shiftKey);
68267
+ }
68268
+ else if (this.inNavigationMode) {
68269
+ switch (event.key) {
68270
+ case keyArrowLeft:
68271
+ handled = this.onLeftArrowPressed();
68272
+ break;
68273
+ case keyArrowRight:
68274
+ handled = this.onRightArrowPressed();
68275
+ break;
68276
+ case keyArrowUp:
68277
+ handled = this.onUpArrowPressed();
68278
+ break;
68279
+ case keyArrowDown:
68280
+ handled = this.onDownArrowPressed();
68281
+ break;
68282
+ case keyPageUp:
68283
+ handled = this.onPageUpPressed();
68284
+ break;
68285
+ case keyPageDown:
68286
+ handled = this.onPageDownPressed();
68287
+ break;
68288
+ case keyHome:
68289
+ handled = this.onHomePressed(event.ctrlKey);
68290
+ break;
68291
+ case keyEnd:
68292
+ handled = this.onEndPressed(event.ctrlKey);
68293
+ break;
68294
+ case keyEnter:
68295
+ handled = this.onEnterPressed(event.ctrlKey);
68296
+ break;
68297
+ case keySpace:
68298
+ handled = this.onSpacePressed(event.shiftKey);
68299
+ break;
68300
+ case keyFunction2:
68301
+ handled = this.onF2Pressed();
68302
+ break;
68303
+ }
68304
+ }
68305
+ if (handled) {
68306
+ event.preventDefault();
68307
+ }
68308
+ };
68309
+ this.onKeyDown = (event) => {
68310
+ if (!this.inNavigationMode && !event.defaultPrevented) {
68311
+ if (event.key === keyEscape || event.key === keyFunction2) {
68312
+ const row = this.getCurrentRow();
68313
+ if (row) {
68314
+ this.trySetCellFocus(row.getFocusableElements());
68315
+ }
68316
+ }
68317
+ }
68318
+ };
68319
+ this.onViewportKeyDown = (event) => {
68320
+ let handleEvent = !this.inNavigationMode
68321
+ && (event.key === keyArrowUp || event.key === keyArrowDown);
68322
+ switch (event.key) {
68323
+ case keyPageUp:
68324
+ case keyPageDown:
68325
+ case keyHome:
68326
+ case keyEnd:
68327
+ handleEvent = true;
68328
+ break;
68329
+ }
68330
+ if (handleEvent) {
68331
+ // Swallow key presses that would cause table scrolling, independently of keyboard navigation
68332
+ event.preventDefault();
68333
+ event.stopImmediatePropagation();
68334
+ }
68335
+ };
68336
+ this.tableNotifier = Observable.getNotifier(this.table);
68337
+ this.tableNotifier.subscribe(this, 'rowElements');
68338
+ this.virtualizerNotifier = Observable.getNotifier(this.virtualizer);
68339
+ this.virtualizerNotifier.subscribe(this, 'visibleItems');
68340
+ }
68341
+ resetFocusState() {
68342
+ this.focusType = TableFocusType.none;
68343
+ const activeElement = this.getActiveElement();
68344
+ if (activeElement && this.isInTable(activeElement)) {
68345
+ this.setDefaultFocus();
68346
+ }
68347
+ }
68348
+ get hasActiveRowOrCellFocus() {
68349
+ return this.focusWithinTable && this.hasRowOrCellFocusType();
68350
+ }
68351
+ connect() {
68352
+ this.table.addEventListener('keydown', this.onCaptureKeyDown, { capture: true });
68353
+ this.table.addEventListener('keydown', this.onKeyDown);
68354
+ this.table.addEventListener('focusin', this.onTableFocusIn);
68355
+ this.table.addEventListener('focusout', this.onTableFocusOut);
68356
+ this.table.viewport.addEventListener('keydown', this.onViewportKeyDown);
68357
+ this.table.viewport.addEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
68358
+ this.table.viewport.addEventListener('cell-view-focus-in', this.onCellViewFocusIn);
68359
+ this.table.viewport.addEventListener('cell-focus-in', this.onCellFocusIn);
68360
+ this.table.viewport.addEventListener('cell-blur', this.onCellBlur);
68361
+ }
68362
+ disconnect() {
68363
+ this.table.removeEventListener('keydown', this.onCaptureKeyDown, { capture: true });
68364
+ this.table.removeEventListener('keydown', this.onKeyDown);
68365
+ this.table.removeEventListener('focusin', this.onTableFocusIn);
68366
+ this.table.removeEventListener('focusout', this.onTableFocusOut);
68367
+ this.table.viewport.removeEventListener('keydown', this.onViewportKeyDown);
68368
+ this.table.viewport.removeEventListener('cell-action-menu-blur', this.onCellActionMenuBlur);
68369
+ this.table.viewport.removeEventListener('cell-view-focus-in', this.onCellViewFocusIn);
68370
+ this.table.viewport.removeEventListener('cell-focus-in', this.onCellFocusIn);
68371
+ this.table.viewport.removeEventListener('cell-blur', this.onCellBlur);
68372
+ }
68373
+ handleChange(source, args) {
68374
+ let focusRowAndCell = false;
68375
+ if (source === this.virtualizer && args === 'visibleItems') {
68376
+ focusRowAndCell = true;
68377
+ }
68378
+ else if (source === this.table && args === 'rowElements') {
68379
+ for (const notifier of this.visibleRowNotifiers) {
68380
+ notifier.unsubscribe(this);
68381
+ }
68382
+ this.visibleRowNotifiers = [];
68383
+ for (const visibleRow of this.table.rowElements) {
68384
+ const rowNotifier = Observable.getNotifier(visibleRow);
68385
+ rowNotifier.subscribe(this, 'resolvedRowIndex');
68386
+ if (visibleRow.resolvedRowIndex === this.rowIndex) {
68387
+ focusRowAndCell = true;
68388
+ }
68389
+ }
68390
+ }
68391
+ else if (args === 'resolvedRowIndex'
68392
+ && this.isResolvedRowType(source)) {
68393
+ if (source.resolvedRowIndex === this.rowIndex) {
68394
+ focusRowAndCell = true;
68395
+ }
68396
+ }
68397
+ if (focusRowAndCell) {
68398
+ // Focusable elements in cells, and action menus, are both blurred on scroll. To maintain our row/cell focus state,
68399
+ // we focus the cell instead here. (We also don't want to refocus the cell content when the focusedRecycleCallback just
68400
+ // blurred it.)
68401
+ if (this.focusType === TableFocusType.cellActionMenu
68402
+ || this.focusType === TableFocusType.cellContent) {
68403
+ this.setCellFocusState(this.columnIndex, this.rowIndex, false);
68404
+ }
68405
+ if (this.inNavigationMode && this.hasRowOrCellFocusType()) {
68406
+ if (this.rowIndex > this.table.tableData.length - 1) {
68407
+ // Focused row index no longer valid, coerce to 1st row if possible
68408
+ if (this.table.tableData.length > 0) {
68409
+ this.rowIndex = 0;
68410
+ }
68411
+ else {
68412
+ if (this.focusWithinTable) {
68413
+ this.setDefaultFocus();
68414
+ }
68415
+ else {
68416
+ this.focusType = TableFocusType.none;
68417
+ }
68418
+ return;
68419
+ }
68420
+ }
68421
+ if (this.focusWithinTable) {
68422
+ this.focusCurrentRow(false);
68423
+ }
68424
+ }
68425
+ }
68426
+ }
68427
+ handleFocusedCellRecycling(hadRowOrCellFocus) {
68428
+ if (hadRowOrCellFocus && !this.focusWithinTable) {
68429
+ this.focusCurrentRow(false);
68430
+ }
68431
+ }
68432
+ onRowFocusIn(event) {
68433
+ if (this.isCurrentlyFocusingElement) {
68434
+ return;
68435
+ }
68436
+ const row = event.target;
68437
+ if (this.isResolvedRowType(row)) {
68438
+ if (this.rowIndex !== row.resolvedRowIndex) {
68439
+ // If user focuses a row some other way (e.g. mouse), update our focus state so future keyboard nav
68440
+ // will start from that row
68441
+ this.setRowFocusState(row.resolvedRowIndex);
68442
+ }
68443
+ }
68444
+ }
68445
+ onRowBlur(event) {
68446
+ const row = event.target;
68447
+ if (this.isResolvedRowType(row)) {
68448
+ this.setElementFocusable(row, false);
68449
+ }
68450
+ }
68451
+ onRowActionMenuToggle(event) {
68452
+ const isOpen = event.detail.newState;
68453
+ if (isOpen) {
68454
+ const row = event.target;
68455
+ const columnIndex = this.table.visibleColumns.findIndex(column => column.columnId === event.detail.columnId);
68456
+ this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
68457
+ }
68458
+ }
68459
+ onEnterPressed(ctrlKey) {
68460
+ let row;
68461
+ let rowElements;
68462
+ if (this.hasRowOrCellFocusType()) {
68463
+ row = this.getCurrentRow();
68464
+ rowElements = row?.getFocusableElements();
68465
+ }
68466
+ if (this.focusType === TableFocusType.row) {
68467
+ if (row instanceof TableGroupRow) {
68468
+ this.toggleRowExpanded(row);
68469
+ return true;
68470
+ }
68471
+ }
68472
+ if (this.focusType === TableFocusType.cell) {
68473
+ if (ctrlKey) {
68474
+ const cell = rowElements?.cells[this.columnIndex];
68475
+ if (cell?.actionMenuButton && !cell.actionMenuButton.open) {
68476
+ cell.actionMenuButton.toggleButton.control.click();
68477
+ return true;
68478
+ }
68479
+ }
68480
+ return this.focusFirstInteractiveElementInCurrentCell(rowElements);
68481
+ }
68482
+ return false;
68483
+ }
68484
+ onF2Pressed() {
68485
+ if (this.focusType === TableFocusType.cell) {
68486
+ const row = this.getCurrentRow();
68487
+ const rowElements = row?.getFocusableElements();
68488
+ return this.focusFirstInteractiveElementInCurrentCell(rowElements);
68489
+ }
68490
+ return false;
68491
+ }
68492
+ onSpacePressed(shiftKey) {
68493
+ if (this.focusType === TableFocusType.row
68494
+ || this.focusType === TableFocusType.cell) {
68495
+ if (this.focusType === TableFocusType.row || shiftKey) {
68496
+ const row = this.getCurrentRow();
68497
+ if (row instanceof TableRow && row.selectable) {
68498
+ row.onSelectionChange(row.selected, !row.selected);
68499
+ }
68500
+ else if (row instanceof TableGroupRow) {
68501
+ this.toggleRowExpanded(row);
68502
+ }
68503
+ }
68504
+ // Default Space behavior scrolls down, which is redundant given the rest of our keyboard nav code, and we'd still try to focus a
68505
+ // row that you scrolled away from. So suppress default Space behavior if a row or cell is selected, regardless of if we're
68506
+ // toggling selection or not.
68507
+ return true;
68508
+ }
68509
+ return false;
68510
+ }
68511
+ onLeftArrowPressed() {
68512
+ let row;
68513
+ let rowElements;
68514
+ let headerElements;
68515
+ if (this.hasRowOrCellFocusType()) {
68516
+ row = this.getCurrentRow();
68517
+ rowElements = row?.getFocusableElements();
68518
+ }
68519
+ else if (this.hasHeaderFocusType()) {
68520
+ headerElements = this.getTableHeaderFocusableElements();
68521
+ }
68522
+ switch (this.focusType) {
68523
+ case TableFocusType.headerActions:
68524
+ return this.trySetHeaderActionFocus(headerElements, this.headerActionIndex - 1);
68525
+ case TableFocusType.columnHeader:
68526
+ return (this.trySetColumnHeaderFocus(headerElements, this.columnIndex - 1)
68527
+ || this.trySetHeaderActionFocus(headerElements, headerElements.headerActions.length - 1));
68528
+ case TableFocusType.row:
68529
+ if (this.isRowExpanded(row) === true) {
68530
+ this.toggleRowExpanded(row);
68531
+ return true;
68532
+ }
68533
+ return false;
68534
+ case TableFocusType.rowSelectionCheckbox:
68535
+ this.setRowFocusState();
68536
+ return this.focusCurrentRow(true);
68537
+ case TableFocusType.cell:
68538
+ if (!this.trySetCellFocus(rowElements, this.columnIndex - 1)
68539
+ && !this.trySetRowSelectionCheckboxFocus(rowElements)) {
68540
+ this.setRowFocusState();
68541
+ this.focusCurrentRow(true);
68542
+ }
68543
+ return true;
68544
+ }
68545
+ return false;
68546
+ }
68547
+ onRightArrowPressed() {
68548
+ let row;
68549
+ let rowElements;
68550
+ let headerElements;
68551
+ if (this.hasRowOrCellFocusType()) {
68552
+ row = this.getCurrentRow();
68553
+ rowElements = row?.getFocusableElements();
68554
+ }
68555
+ else if (this.hasHeaderFocusType()) {
68556
+ headerElements = this.getTableHeaderFocusableElements();
68557
+ }
68558
+ switch (this.focusType) {
68559
+ case TableFocusType.headerActions:
68560
+ return (this.trySetHeaderActionFocus(headerElements, this.headerActionIndex + 1) || this.trySetColumnHeaderFocus(headerElements, 0));
68561
+ case TableFocusType.columnHeader:
68562
+ return this.trySetColumnHeaderFocus(headerElements, this.columnIndex + 1);
68563
+ case TableFocusType.row:
68564
+ if (this.isRowExpanded(row) === false) {
68565
+ this.toggleRowExpanded(row);
68566
+ return true;
68567
+ }
68568
+ return (this.trySetRowSelectionCheckboxFocus(rowElements)
68569
+ || this.trySetCellFocus(rowElements, 0));
68570
+ case TableFocusType.rowSelectionCheckbox:
68571
+ return this.trySetCellFocus(rowElements, 0);
68572
+ case TableFocusType.cell:
68573
+ return this.trySetCellFocus(rowElements, this.columnIndex + 1);
68574
+ }
68575
+ return false;
68576
+ }
68577
+ onUpArrowPressed() {
68578
+ this.onMoveUp(1);
68579
+ // Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
68580
+ return true;
68581
+ }
68582
+ onPageUpPressed() {
68583
+ return this.onMoveUp(this.virtualizer.pageSize);
68584
+ }
68585
+ onHomePressed(ctrlKey) {
68586
+ if (this.handleHomeEndWithinRow(ctrlKey)) {
68587
+ const row = this.getCurrentRow();
68588
+ const rowElements = row?.getFocusableElements();
68589
+ return (this.trySetRowSelectionCheckboxFocus(rowElements)
68590
+ || this.trySetCellFocus(rowElements, 0));
68591
+ }
68592
+ return this.onMoveUp(0, 0);
68593
+ }
68594
+ onDownArrowPressed() {
68595
+ this.onMoveDown(1);
68596
+ // Always prevent default - prevents page scroll, and FireFox changing focus if focus is at table extents
68597
+ return true;
68598
+ }
68599
+ onPageDownPressed() {
68600
+ return this.onMoveDown(this.virtualizer.pageSize);
68601
+ }
68602
+ onEndPressed(ctrlKey) {
68603
+ if (this.handleHomeEndWithinRow(ctrlKey)) {
68604
+ const row = this.getCurrentRow();
68605
+ const rowElements = row?.getFocusableElements();
68606
+ return this.trySetCellFocus(rowElements, this.table.visibleColumns.length - 1);
68607
+ }
68608
+ return this.onMoveDown(0, this.table.tableData.length - 1);
68609
+ }
68610
+ handleHomeEndWithinRow(ctrlKey) {
68611
+ return ((this.focusType === TableFocusType.cell
68612
+ || this.focusType === TableFocusType.rowSelectionCheckbox)
68613
+ && !ctrlKey);
68614
+ }
68615
+ onTabPressed(shiftKeyPressed) {
68616
+ const activeElement = this.getActiveElement();
68617
+ if (activeElement === null || activeElement === this.table) {
68618
+ return false;
68619
+ }
68620
+ const nextFocusState = this.hasRowOrCellFocusType()
68621
+ ? this.getNextRowTabStop(shiftKeyPressed)
68622
+ : this.getNextHeaderTabStop(shiftKeyPressed);
68623
+ if (nextFocusState) {
68624
+ this.focusType = nextFocusState.focusType;
68625
+ this.rowIndex = nextFocusState.rowIndex ?? this.rowIndex;
68626
+ this.columnIndex = nextFocusState.columnIndex ?? this.columnIndex;
68627
+ this.headerActionIndex = nextFocusState.headerActionIndex ?? this.headerActionIndex;
68628
+ this.cellContentIndex = nextFocusState.cellContentIndex ?? this.cellContentIndex;
68629
+ if (this.hasRowOrCellFocusType()) {
68630
+ this.focusCurrentRow(false);
68631
+ }
68632
+ else {
68633
+ this.focusHeaderElement();
68634
+ }
68635
+ return true;
68636
+ }
68637
+ this.blurAfterLastTab(activeElement);
68638
+ return false;
68639
+ }
68640
+ getNextRowTabStop(shiftKeyPressed) {
68641
+ const row = this.getCurrentRow();
68642
+ if (row === undefined) {
68643
+ return undefined;
68644
+ }
68645
+ let startIndex = -1;
68646
+ const focusStates = [];
68647
+ const rowElements = row.getFocusableElements();
68648
+ if (rowElements.selectionCheckbox) {
68649
+ focusStates.push({
68650
+ focusType: TableFocusType.rowSelectionCheckbox
68651
+ });
68652
+ if (this.focusType === TableFocusType.rowSelectionCheckbox) {
68653
+ startIndex = 0;
68654
+ }
68655
+ }
68656
+ let cellIndex = 0;
68657
+ while (cellIndex < rowElements.cells.length) {
68658
+ const firstCellTabbableIndex = focusStates.length;
68659
+ const cellInfo = rowElements.cells[cellIndex];
68660
+ const cellViewTabbableChildren = cellInfo.cell.cellView.tabbableChildren;
68661
+ for (let i = 0; i < cellViewTabbableChildren.length; i++) {
68662
+ focusStates.push({
68663
+ focusType: TableFocusType.cellContent,
68664
+ columnIndex: cellIndex,
68665
+ cellContentIndex: i
68666
+ });
68667
+ if (this.focusType === TableFocusType.cellContent
68668
+ && this.columnIndex === cellIndex
68669
+ && this.cellContentIndex === i) {
68670
+ startIndex = focusStates.length - 1;
68671
+ }
68672
+ }
68673
+ if (cellInfo.actionMenuButton) {
68674
+ focusStates.push({
68675
+ focusType: TableFocusType.cellActionMenu,
68676
+ columnIndex: cellIndex
68677
+ });
68678
+ if (this.focusType === TableFocusType.cellActionMenu
68679
+ && this.columnIndex === cellIndex) {
68680
+ startIndex = focusStates.length - 1;
68681
+ }
68682
+ }
68683
+ const lastCellTabbableIndex = focusStates.length - 1;
68684
+ if (this.focusType === TableFocusType.cell
68685
+ && this.columnIndex === cellIndex) {
68686
+ startIndex = shiftKeyPressed
68687
+ ? lastCellTabbableIndex + 1
68688
+ : firstCellTabbableIndex - 1;
68689
+ }
68690
+ cellIndex += 1;
68691
+ }
68692
+ if (this.focusType === TableFocusType.row) {
68693
+ startIndex = shiftKeyPressed ? focusStates.length : -1;
68694
+ }
68695
+ const direction = shiftKeyPressed ? -1 : 1;
68696
+ return focusStates[startIndex + direction];
68697
+ }
68698
+ getNextHeaderTabStop(shiftKeyPressed) {
68699
+ let startIndex = -1;
68700
+ const focusStates = [];
68701
+ const headerTabbableElements = this.getTableHeaderFocusableElements().headerActions;
68702
+ for (let i = 0; i < headerTabbableElements.length; i++) {
68703
+ focusStates.push({
68704
+ focusType: TableFocusType.headerActions,
68705
+ headerActionIndex: i
68706
+ });
68707
+ }
68708
+ if (this.focusType === TableFocusType.headerActions) {
68709
+ startIndex = this.headerActionIndex;
68710
+ }
68711
+ else {
68712
+ // TableFocusType.columnHeader
68713
+ startIndex = focusStates.length;
68714
+ }
68715
+ const direction = shiftKeyPressed ? -1 : 1;
68716
+ return focusStates[startIndex + direction];
68717
+ }
68718
+ blurAfterLastTab(activeElement) {
68719
+ // In order to get the desired browser-provided Tab/Shift-Tab behavior of focusing the
68720
+ // element before/after the table, the table shouldn't have tabIndex=0 when this event
68721
+ // handling ends. However it needs to be tabIndex=0 so we can re-focus the table the next time
68722
+ // it's tabbed to, so set tabIndex back to 0 after a rAF.
68723
+ // Note: In Chrome this is only needed for Shift-Tab, but in Firefox both Tab and Shift-Tab need this
68724
+ // to work as expected.
68725
+ this.table.tabIndex = -1;
68726
+ window.requestAnimationFrame(() => {
68727
+ this.table.tabIndex = 0;
68728
+ });
68729
+ // Don't explicitly call blur() on activeElement (causes unexpected behavior on Safari / Mac Firefox)
68730
+ this.setElementFocusable(activeElement, false);
68731
+ }
68732
+ onMoveUp(rowDelta, newRowIndex) {
68733
+ const coerceRowIndex = rowDelta > 1;
68734
+ switch (this.focusType) {
68735
+ case TableFocusType.row:
68736
+ case TableFocusType.rowSelectionCheckbox:
68737
+ case TableFocusType.cell: {
68738
+ const scrollOptions = {};
68739
+ let rowIndex = this.rowIndex;
68740
+ if (newRowIndex !== undefined) {
68741
+ rowIndex = newRowIndex;
68742
+ }
68743
+ rowIndex -= rowDelta;
68744
+ if (coerceRowIndex && rowIndex < 0) {
68745
+ rowIndex = 0;
68746
+ }
68747
+ if (rowDelta > 1) {
68748
+ scrollOptions.align = 'start';
68749
+ }
68750
+ if (rowIndex < this.rowIndex && rowIndex >= 0) {
68751
+ return this.scrollToAndFocusRow(rowIndex, scrollOptions);
68752
+ }
68753
+ if (rowIndex === -1) {
68754
+ const headerElements = this.getTableHeaderFocusableElements();
68755
+ if (this.focusType === TableFocusType.row
68756
+ || this.focusType === TableFocusType.rowSelectionCheckbox) {
68757
+ return (this.trySetHeaderActionFocus(headerElements, 0)
68758
+ || this.trySetColumnHeaderFocus(headerElements, 0));
68759
+ }
68760
+ return this.trySetColumnHeaderFocus(headerElements, this.columnIndex);
68761
+ }
68762
+ return false;
68763
+ }
68764
+ }
68765
+ return false;
68766
+ }
68767
+ onMoveDown(rowDelta, newRowIndex) {
68768
+ const coerceRowIndex = rowDelta > 1;
68769
+ switch (this.focusType) {
68770
+ case TableFocusType.headerActions: {
68771
+ this.setRowFocusState(0);
68772
+ return this.scrollToAndFocusRow(0);
68773
+ }
68774
+ case TableFocusType.columnHeader: {
68775
+ this.setCellFocusState(this.columnIndex, 0, false);
68776
+ return this.scrollToAndFocusRow(0);
68777
+ }
68778
+ case TableFocusType.row:
68779
+ case TableFocusType.rowSelectionCheckbox:
68780
+ case TableFocusType.cell: {
68781
+ const scrollOptions = {};
68782
+ let rowIndex = this.rowIndex;
68783
+ if (newRowIndex !== undefined) {
68784
+ rowIndex = newRowIndex;
68785
+ }
68786
+ rowIndex += rowDelta;
68787
+ if (coerceRowIndex && rowIndex >= this.table.tableData.length) {
68788
+ rowIndex = this.table.tableData.length - 1;
68789
+ }
68790
+ if (rowDelta > 1) {
68791
+ scrollOptions.align = 'end';
68792
+ }
68793
+ if (rowIndex > this.rowIndex
68794
+ && rowIndex < this.table.tableData.length) {
68795
+ return this.scrollToAndFocusRow(rowIndex, scrollOptions);
68796
+ }
68797
+ return false;
68798
+ }
68799
+ }
68800
+ return false;
68801
+ }
68802
+ updateFocusStateFromActiveElement(setRowFocus) {
68803
+ // If the user is interacting with the table with non-keyboard methods (like mouse), we need to
68804
+ // update our focus state based on the current active/focused element
68805
+ const activeElement = this.getActiveElement();
68806
+ if (activeElement) {
68807
+ const row = this.getContainingRow(activeElement);
68808
+ if (row) {
68809
+ if (!(row instanceof TableGroupRow)) {
68810
+ const cell = this.getContainingCell(activeElement);
68811
+ if (cell) {
68812
+ const columnIndex = this.table.visibleColumns.indexOf(cell.column);
68813
+ if (cell.actionMenuButton === activeElement) {
68814
+ this.setCellActionMenuFocusState(row.resolvedRowIndex, columnIndex, false);
68815
+ return;
68816
+ }
68817
+ const contentIndex = cell.cellView.tabbableChildren.indexOf(activeElement);
68818
+ if (contentIndex > -1) {
68819
+ this.setCellContentFocusState(contentIndex, row.resolvedRowIndex, columnIndex, false);
68820
+ return;
68821
+ }
68822
+ }
68823
+ }
68824
+ if (setRowFocus
68825
+ && this.hasRowOrCellFocusType()
68826
+ && this.rowIndex !== row.resolvedRowIndex) {
68827
+ this.setRowFocusState(row.resolvedRowIndex);
68828
+ }
68829
+ }
68830
+ }
68831
+ }
68832
+ focusElement(element, focusOptions) {
68833
+ const previousActiveElement = this.getActiveElement();
68834
+ if (previousActiveElement !== element) {
68835
+ this.setElementFocusable(element, true);
68836
+ this.isCurrentlyFocusingElement = true;
68837
+ element.focus(focusOptions);
68838
+ this.isCurrentlyFocusingElement = false;
68839
+ if (previousActiveElement
68840
+ && this.isInTable(previousActiveElement)) {
68841
+ this.setElementFocusable(previousActiveElement, false);
68842
+ }
68843
+ }
68844
+ }
68845
+ setElementFocusable(element, focusable) {
68846
+ if (element === this.table) {
68847
+ return;
68848
+ }
68849
+ element.tabIndex = focusable ? 0 : -1;
68850
+ }
68851
+ setActionMenuButtonFocused(menuButton, focused) {
68852
+ // The action MenuButton needs to be visible in order to be focused, so this CSS class styling
68853
+ // handles that (see cell/styles.ts).
68854
+ if (focused) {
68855
+ menuButton.classList.add('cell-action-menu-focused');
68856
+ }
68857
+ else {
68858
+ menuButton.classList.remove('cell-action-menu-focused');
68859
+ }
68860
+ }
68861
+ setFocusOnHeader() {
68862
+ if (this.hasHeaderFocusType()) {
68863
+ return this.focusHeaderElement();
68864
+ }
68865
+ this.setDefaultFocus();
68866
+ return this.focusType !== TableFocusType.none;
68867
+ }
68868
+ setDefaultFocus() {
68869
+ const headerElements = this.getTableHeaderFocusableElements();
68870
+ if (!this.trySetHeaderActionFocus(headerElements, 0)
68871
+ && !this.trySetColumnHeaderFocus(headerElements, 0)
68872
+ && !this.scrollToAndFocusRow(0)) {
68873
+ this.focusType = TableFocusType.none;
68874
+ }
68875
+ }
68876
+ scrollToAndFocusRow(totalRowIndex, scrollOptions) {
68877
+ if (totalRowIndex >= 0 && totalRowIndex < this.table.tableData.length) {
68878
+ switch (this.focusType) {
68879
+ case TableFocusType.none:
68880
+ case TableFocusType.headerActions:
68881
+ case TableFocusType.columnHeader:
68882
+ this.setRowFocusState(totalRowIndex);
68883
+ break;
68884
+ }
68885
+ this.rowIndex = totalRowIndex;
68886
+ this.virtualizer.scrollToIndex(totalRowIndex, scrollOptions);
68887
+ this.focusCurrentRow(true);
68888
+ return true;
68889
+ }
68890
+ return false;
68891
+ }
68892
+ focusCurrentRow(allowScroll) {
68893
+ const visibleRowIndex = this.getCurrentRowVisibleIndex();
68894
+ if (visibleRowIndex < 0) {
68895
+ return false;
68896
+ }
68897
+ const focusedRow = this.table.rowElements[visibleRowIndex];
68898
+ let focusRowOnly = false;
68899
+ switch (this.focusType) {
68900
+ case TableFocusType.row:
68901
+ focusRowOnly = true;
68902
+ break;
68903
+ case TableFocusType.cell:
68904
+ case TableFocusType.cellActionMenu:
68905
+ case TableFocusType.cellContent:
68906
+ focusRowOnly = focusedRow instanceof TableGroupRow;
68907
+ break;
68908
+ }
68909
+ const focusOptions = { preventScroll: !allowScroll };
68910
+ if (focusRowOnly) {
68911
+ this.focusElement(focusedRow, focusOptions);
68912
+ return true;
68913
+ }
68914
+ this.focusRowElement(focusedRow, focusOptions);
68915
+ return true;
68916
+ }
68917
+ focusRowElement(row, focusOptions) {
68918
+ const rowElements = row.getFocusableElements();
68919
+ let focusableElement;
68920
+ switch (this.focusType) {
68921
+ case TableFocusType.rowSelectionCheckbox:
68922
+ focusableElement = rowElements.selectionCheckbox;
68923
+ break;
68924
+ case TableFocusType.cell: {
68925
+ focusableElement = rowElements.cells[this.columnIndex].cell;
68926
+ break;
68927
+ }
68928
+ case TableFocusType.cellActionMenu: {
68929
+ const actionMenuButton = rowElements.cells[this.columnIndex]?.cell.actionMenuButton;
68930
+ if (actionMenuButton) {
68931
+ focusableElement = actionMenuButton;
68932
+ this.setActionMenuButtonFocused(actionMenuButton, true);
68933
+ }
68934
+ break;
68935
+ }
68936
+ case TableFocusType.cellContent: {
68937
+ focusableElement = rowElements.cells[this.columnIndex]?.cell.cellView
68938
+ .tabbableChildren[this.cellContentIndex];
68939
+ break;
68940
+ }
68941
+ }
68942
+ if (focusableElement) {
68943
+ this.focusElement(focusableElement, focusOptions);
68944
+ }
68945
+ }
68946
+ focusHeaderElement() {
68947
+ const headerElements = this.getTableHeaderFocusableElements();
68948
+ let focusableElement;
68949
+ switch (this.focusType) {
68950
+ case TableFocusType.headerActions:
68951
+ focusableElement = headerElements.headerActions[this.headerActionIndex];
68952
+ break;
68953
+ case TableFocusType.columnHeader:
68954
+ focusableElement = headerElements.columnHeaders[this.columnIndex];
68955
+ break;
68956
+ }
68957
+ if (focusableElement) {
68958
+ this.focusElement(focusableElement);
68959
+ return true;
68960
+ }
68961
+ return false;
68962
+ }
68963
+ getCurrentRowVisibleIndex() {
68964
+ return this.table.rowElements.findIndex(row => row.resolvedRowIndex === this.rowIndex);
68965
+ }
68966
+ getTableHeaderFocusableElements() {
68967
+ const headerActions = [];
68968
+ if (this.table.selectionCheckbox?.getRootNode()
68969
+ === this.table.shadowRoot) {
68970
+ headerActions.push(this.table.selectionCheckbox);
68971
+ }
68972
+ if (this.table.showCollapseAll
68973
+ && this.table.collapseAllButton?.getRootNode()
68974
+ === this.table.shadowRoot) {
68975
+ headerActions.push(this.table.collapseAllButton);
68976
+ }
68977
+ const columnHeaders = [];
68978
+ if (this.canFocusColumnHeaders()) {
68979
+ this.table.columnHeadersContainer
68980
+ .querySelectorAll(tableHeaderTag)
68981
+ .forEach(header => columnHeaders.push(header));
68982
+ }
68983
+ return { headerActions, columnHeaders };
68984
+ }
68985
+ canFocusColumnHeaders() {
68986
+ return (this.table.columns.find(c => !c.columnInternals.sortingDisabled)
68987
+ !== undefined);
68988
+ }
68989
+ getCurrentRow() {
68990
+ return this.table.rowElements[this.getCurrentRowVisibleIndex()];
68991
+ }
68992
+ isRowExpanded(row) {
68993
+ if ((row instanceof TableRow && row.isParentRow)
68994
+ || row instanceof TableGroupRow) {
68995
+ return row.expanded;
68996
+ }
68997
+ return undefined;
68998
+ }
68999
+ toggleRowExpanded(row) {
69000
+ if (row instanceof TableGroupRow) {
69001
+ row.onGroupExpandToggle();
69002
+ }
69003
+ else {
69004
+ row.onRowExpandToggle();
69005
+ }
69006
+ this.focusRowElement(row);
69007
+ }
69008
+ getContainingRow(start) {
69009
+ return this.getContainingElement(start, e => this.isResolvedRowType(e));
69010
+ }
69011
+ getContainingCell(start) {
69012
+ return this.getContainingElement(start, e => e instanceof TableCell);
69013
+ }
69014
+ getContainingElement(start, isElementMatch) {
69015
+ let possibleMatch = start;
69016
+ while (possibleMatch && possibleMatch !== this.table) {
69017
+ if (isElementMatch(possibleMatch)) {
69018
+ return possibleMatch;
69019
+ }
69020
+ possibleMatch = possibleMatch.parentElement
69021
+ ?? possibleMatch.parentNode?.host;
69022
+ }
69023
+ return undefined;
69024
+ }
69025
+ isInTable(start) {
69026
+ let possibleMatch = start;
69027
+ while (possibleMatch && possibleMatch !== this.table) {
69028
+ possibleMatch = possibleMatch.parentElement
69029
+ ?? possibleMatch.parentNode?.host;
69030
+ }
69031
+ return possibleMatch === this.table;
69032
+ }
69033
+ getActiveElement() {
69034
+ let activeElement = document.activeElement;
69035
+ while (activeElement?.shadowRoot?.activeElement) {
69036
+ activeElement = activeElement.shadowRoot.activeElement;
69037
+ // In some cases, the active element may be a sub-part of a control (example: MenuButton -> ToggleButton -> a div with tabindex=0). Stop at the outer control boundary, so that
69038
+ // we can more simply check equality against the elements of getTableHeaderFocusableElements() / row.getFocusableElements().
69039
+ // (For rows/cells/cell views, we do need to recurse into them, to get to the appropriate focused controls though)
69040
+ if (activeElement instanceof FoundationElement
69041
+ && !this.isResolvedRowType(activeElement)
69042
+ && !(activeElement instanceof TableCell)
69043
+ && !(activeElement instanceof TableCellView)) {
69044
+ break;
69045
+ }
69046
+ }
69047
+ return activeElement;
69048
+ }
69049
+ focusFirstInteractiveElementInCurrentCell(rowElements) {
69050
+ if (!rowElements) {
69051
+ return false;
69052
+ }
69053
+ return (this.trySetCellContentFocus(rowElements, 0)
69054
+ || this.trySetCellActionMenuFocus(rowElements));
69055
+ }
69056
+ hasRowOrCellFocusType() {
69057
+ switch (this.focusType) {
69058
+ case TableFocusType.cell:
69059
+ case TableFocusType.cellActionMenu:
69060
+ case TableFocusType.cellContent:
69061
+ case TableFocusType.row:
69062
+ case TableFocusType.rowSelectionCheckbox:
69063
+ return true;
69064
+ default:
69065
+ return false;
69066
+ }
69067
+ }
69068
+ hasHeaderFocusType() {
69069
+ switch (this.focusType) {
69070
+ case TableFocusType.headerActions:
69071
+ case TableFocusType.columnHeader:
69072
+ return true;
69073
+ default:
69074
+ return false;
69075
+ }
69076
+ }
69077
+ trySetRowSelectionCheckboxFocus(rowElements) {
69078
+ if (rowElements?.selectionCheckbox) {
69079
+ this.focusType = TableFocusType.rowSelectionCheckbox;
69080
+ this.focusCurrentRow(true);
69081
+ return true;
69082
+ }
69083
+ return false;
69084
+ }
69085
+ trySetColumnHeaderFocus(headerElements, columnIndex) {
69086
+ if (columnIndex >= 0
69087
+ && columnIndex < headerElements.columnHeaders.length) {
69088
+ this.focusType = TableFocusType.columnHeader;
69089
+ this.columnIndex = columnIndex;
69090
+ this.focusHeaderElement();
69091
+ return true;
69092
+ }
69093
+ return false;
69094
+ }
69095
+ trySetHeaderActionFocus(headerElements, headerActionIndex) {
69096
+ if (headerActionIndex >= 0
69097
+ && headerActionIndex < headerElements.headerActions.length) {
69098
+ this.focusType = TableFocusType.headerActions;
69099
+ this.headerActionIndex = headerActionIndex;
69100
+ this.focusHeaderElement();
69101
+ return true;
69102
+ }
69103
+ return false;
69104
+ }
69105
+ trySetCellFocus(rowElements, columnIndex, rowIndex) {
69106
+ if (!rowElements) {
69107
+ return false;
69108
+ }
69109
+ const newColumnIndex = columnIndex ?? this.columnIndex;
69110
+ const newRowIndex = rowIndex ?? this.rowIndex;
69111
+ if (newColumnIndex >= 0 && newColumnIndex < rowElements.cells.length) {
69112
+ this.focusType = TableFocusType.cell;
69113
+ this.setRowCellFocusState(newColumnIndex, newRowIndex, true);
69114
+ return true;
69115
+ }
69116
+ return false;
69117
+ }
69118
+ trySetCellContentFocus(rowElements, cellContentIndex, columnIndex, rowIndex) {
69119
+ if (!rowElements) {
69120
+ return false;
69121
+ }
69122
+ const newColumnIndex = columnIndex ?? this.columnIndex;
69123
+ const newRowIndex = rowIndex ?? this.rowIndex;
69124
+ if (newColumnIndex >= 0
69125
+ && newColumnIndex < rowElements.cells.length
69126
+ && cellContentIndex >= 0
69127
+ && cellContentIndex
69128
+ < rowElements.cells[newColumnIndex].cell.cellView
69129
+ .tabbableChildren.length) {
69130
+ this.setCellContentFocusState(cellContentIndex, newRowIndex, newColumnIndex, true);
69131
+ return true;
69132
+ }
69133
+ return false;
69134
+ }
69135
+ trySetCellActionMenuFocus(rowElements, columnIndex, rowIndex) {
69136
+ const newColumnIndex = columnIndex ?? this.columnIndex;
69137
+ const newRowIndex = rowIndex ?? this.rowIndex;
69138
+ if (newColumnIndex >= 0
69139
+ && newColumnIndex < rowElements.cells.length
69140
+ && rowElements.cells[newColumnIndex].actionMenuButton) {
69141
+ this.setCellActionMenuFocusState(newRowIndex, newColumnIndex, true);
69142
+ return true;
69143
+ }
69144
+ return false;
69145
+ }
69146
+ setCellActionMenuFocusState(rowIndex, columnIndex, focusElement) {
69147
+ this.focusType = TableFocusType.cellActionMenu;
69148
+ this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
69149
+ }
69150
+ setCellContentFocusState(cellContentIndex, rowIndex, columnIndex, focusElement) {
69151
+ this.focusType = TableFocusType.cellContent;
69152
+ this.cellContentIndex = cellContentIndex;
69153
+ this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
69154
+ }
69155
+ setRowFocusState(rowIndex) {
69156
+ this.focusType = TableFocusType.row;
69157
+ if (rowIndex !== undefined) {
69158
+ this.rowIndex = rowIndex;
69159
+ }
69160
+ }
69161
+ setCellFocusState(columnIndex, rowIndex, focusElement) {
69162
+ this.focusType = TableFocusType.cell;
69163
+ this.setRowCellFocusState(columnIndex, rowIndex, focusElement);
69164
+ }
69165
+ setRowCellFocusState(columnIndex, rowIndex, focusElement) {
69166
+ this.rowIndex = rowIndex;
69167
+ this.columnIndex = columnIndex;
69168
+ if (focusElement) {
69169
+ this.focusCurrentRow(true);
69170
+ }
69171
+ }
69172
+ isResolvedRowType(row) {
69173
+ return row instanceof TableRow || row instanceof TableGroupRow;
69174
+ }
69175
+ }
69176
+
67985
69177
  /**
67986
69178
  * A nimble-styled table.
67987
69179
  */
@@ -68122,6 +69314,7 @@ img.ProseMirror-separator {
68122
69314
  };
68123
69315
  this.table = createTable(this.options);
68124
69316
  this.virtualizer = new Virtualizer(this, this.table);
69317
+ this.keyboardNavigationManager = new KeyboardNavigationManager(this, this.virtualizer);
68125
69318
  this.layoutManager = new TableLayoutManager(this);
68126
69319
  this.layoutManagerNotifier = Observable.getNotifier(this.layoutManager);
68127
69320
  this.layoutManagerNotifier.subscribe(this, 'isColumnBeingSized');
@@ -68162,12 +69355,14 @@ img.ProseMirror-separator {
68162
69355
  this.viewport.addEventListener('scroll', this.onViewPortScroll, {
68163
69356
  passive: true
68164
69357
  });
69358
+ this.keyboardNavigationManager.connect();
68165
69359
  document.addEventListener('keydown', this.onKeyDown);
68166
69360
  document.addEventListener('keyup', this.onKeyUp);
68167
69361
  }
68168
69362
  disconnectedCallback() {
68169
69363
  super.disconnectedCallback();
68170
69364
  this.virtualizer.disconnect();
69365
+ this.keyboardNavigationManager.disconnect();
68171
69366
  this.viewport.removeEventListener('scroll', this.onViewPortScroll);
68172
69367
  document.removeEventListener('keydown', this.onKeyDown);
68173
69368
  document.removeEventListener('keyup', this.onKeyUp);
@@ -68217,6 +69412,14 @@ img.ProseMirror-separator {
68217
69412
  return true;
68218
69413
  }
68219
69414
  /** @internal */
69415
+ onRowFocusIn(event) {
69416
+ this.keyboardNavigationManager.onRowFocusIn(event);
69417
+ }
69418
+ /** @internal */
69419
+ onRowBlur(event) {
69420
+ this.keyboardNavigationManager.onRowBlur(event);
69421
+ }
69422
+ /** @internal */
68220
69423
  onAllRowsSelectionChange(event) {
68221
69424
  event.stopPropagation();
68222
69425
  if (this.ignoreSelectionChangeEvents) {
@@ -68310,6 +69513,18 @@ img.ProseMirror-separator {
68310
69513
  }
68311
69514
  this.emitColumnConfigurationChangeEvent();
68312
69515
  }
69516
+ /**
69517
+ * @internal
69518
+ */
69519
+ onHeaderKeyDown(column, event) {
69520
+ const allowMultiSort = event.shiftKey;
69521
+ if (event.key === keyEnter) {
69522
+ this.toggleColumnSort(column, allowMultiSort);
69523
+ }
69524
+ // Return true so that we don't prevent default behavior. Without this, Tab navigation
69525
+ // gets stuck on the column headers.
69526
+ return true;
69527
+ }
68313
69528
  /**
68314
69529
  * @internal
68315
69530
  */
@@ -68325,6 +69540,9 @@ img.ProseMirror-separator {
68325
69540
  this.rowGridColumns = this.layoutManager.getGridTemplateColumns();
68326
69541
  this.visibleColumns = this.columns.filter(column => !column.columnHidden);
68327
69542
  }
69543
+ if (this.tableUpdateTracker.requiresKeyboardFocusReset) {
69544
+ this.keyboardNavigationManager.resetFocusState();
69545
+ }
68328
69546
  }
68329
69547
  get ariaMultiSelectable() {
68330
69548
  switch (this.selectionMode) {
@@ -68367,6 +69585,29 @@ img.ProseMirror-separator {
68367
69585
  }
68368
69586
  return tanStackUpdates;
68369
69587
  }
69588
+ /** @internal */
69589
+ handleFocusedCellRecycling() {
69590
+ const hadActiveRowOrCellFocus = this.keyboardNavigationManager.hasActiveRowOrCellFocus;
69591
+ let tableFocusedElement = this.shadowRoot.activeElement;
69592
+ while (tableFocusedElement !== null
69593
+ && !(tableFocusedElement instanceof TableCellView)) {
69594
+ if (tableFocusedElement.shadowRoot) {
69595
+ tableFocusedElement = tableFocusedElement.shadowRoot.activeElement;
69596
+ }
69597
+ else {
69598
+ break;
69599
+ }
69600
+ }
69601
+ if (tableFocusedElement instanceof TableCellView) {
69602
+ tableFocusedElement.focusedRecycleCallback();
69603
+ }
69604
+ if (this.openActionMenuRecordId !== undefined) {
69605
+ const activeRow = this.rowElements.find(row => row instanceof TableRow
69606
+ && row.recordId === this.openActionMenuRecordId);
69607
+ activeRow?.closeOpenActionMenus();
69608
+ }
69609
+ this.keyboardNavigationManager.handleFocusedCellRecycling(hadActiveRowOrCellFocus);
69610
+ }
68370
69611
  selectionModeChanged(_prev, _next) {
68371
69612
  if (!this.$fastController.isConnected) {
68372
69613
  return;
@@ -68402,6 +69643,7 @@ img.ProseMirror-separator {
68402
69643
  this.$emit('action-menu-beforetoggle', detail);
68403
69644
  }
68404
69645
  async handleRowActionMenuToggleEvent(event) {
69646
+ this.keyboardNavigationManager.onRowActionMenuToggle(event);
68405
69647
  const detail = await this.getActionMenuToggleEventDetail(event);
68406
69648
  this.$emit('action-menu-toggle', detail);
68407
69649
  if (!event.detail.newState) {
@@ -68612,6 +69854,7 @@ img.ProseMirror-separator {
68612
69854
  isParentRow: isParent,
68613
69855
  immediateChildCount: row.subRows.length,
68614
69856
  groupColumn: this.getGroupRowColumn(row),
69857
+ resolvedRowIndex: row.index,
68615
69858
  isLoadingChildren: this.expansionManager.isLoadingChildren(row.id)
68616
69859
  };
68617
69860
  hasDataHierarchy = hasDataHierarchy || isParent;
@@ -68779,6 +70022,9 @@ img.ProseMirror-separator {
68779
70022
  __decorate$1([
68780
70023
  observable
68781
70024
  ], Table.prototype, "selectionCheckbox", void 0);
70025
+ __decorate$1([
70026
+ observable
70027
+ ], Table.prototype, "collapseAllButton", void 0);
68782
70028
  __decorate$1([
68783
70029
  observable
68784
70030
  ], Table.prototype, "showCollapseAll", void 0);
@@ -68932,10 +70178,12 @@ img.ProseMirror-separator {
68932
70178
  }}"
68933
70179
  class="${x => (x.isPlaceholder ? 'placeholder' : '')}"
68934
70180
  >
68935
- ${when(x => typeof x.cellRecord?.href === 'string', html `
70181
+ ${when(x => x.showAnchor, html `
68936
70182
  <${anchorTag}
68937
70183
  ${ref('anchor')}
68938
70184
  ${overflow('hasOverflow')}
70185
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
70186
+ tabindex="-1"
68939
70187
  href="${x => x.cellRecord?.href}"
68940
70188
  hreflang="${x => x.columnConfig?.hreflang}"
68941
70189
  ping="${x => x.columnConfig?.ping}"
@@ -68950,7 +70198,7 @@ img.ProseMirror-separator {
68950
70198
  >
68951
70199
  ${x => x.text}
68952
70200
  </${anchorTag}>`)}
68953
- ${when(x => typeof x.cellRecord?.href !== 'string', html `
70201
+ ${when(x => !x.showAnchor, html `
68954
70202
  <span
68955
70203
  ${overflow('hasOverflow')}
68956
70204
  title=${x => (x.hasOverflow ? x.text : null)}
@@ -68987,9 +70235,19 @@ img.ProseMirror-separator {
68987
70235
  }
68988
70236
  return '';
68989
70237
  }
70238
+ /** @internal */
70239
+ get showAnchor() {
70240
+ return typeof this.cellRecord?.href === 'string';
70241
+ }
68990
70242
  focusedRecycleCallback() {
68991
70243
  this.anchor?.blur();
68992
70244
  }
70245
+ get tabbableChildren() {
70246
+ if (this.showAnchor) {
70247
+ return [this.anchor];
70248
+ }
70249
+ return [];
70250
+ }
68993
70251
  }
68994
70252
  __decorate$1([
68995
70253
  observable
@@ -69000,6 +70258,9 @@ img.ProseMirror-separator {
69000
70258
  __decorate$1([
69001
70259
  volatile
69002
70260
  ], TableColumnAnchorCellView.prototype, "text", null);
70261
+ __decorate$1([
70262
+ volatile
70263
+ ], TableColumnAnchorCellView.prototype, "showAnchor", null);
69003
70264
  const anchorCellView = TableColumnAnchorCellView.compose({
69004
70265
  baseName: 'table-column-anchor-cell-view',
69005
70266
  template: template$c,