@ni/nimble-components 29.5.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 (67) hide show
  1. package/dist/all-components-bundle.js +1332 -41
  2. package/dist/all-components-bundle.js.map +1 -1
  3. package/dist/all-components-bundle.min.js +4059 -3816
  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/dist/esm/theme-provider/design-token-comments.js +8 -0
  59. package/dist/esm/theme-provider/design-token-comments.js.map +1 -1
  60. package/dist/esm/theme-provider/design-token-names.js +8 -0
  61. package/dist/esm/theme-provider/design-token-names.js.map +1 -1
  62. package/dist/esm/theme-provider/design-tokens.d.ts +8 -0
  63. package/dist/esm/theme-provider/design-tokens.js +9 -1
  64. package/dist/esm/theme-provider/design-tokens.js.map +1 -1
  65. package/dist/tokens-internal.scss +48 -0
  66. package/dist/tokens.scss +24 -0
  67. package/package.json +1 -1
@@ -16280,7 +16280,7 @@
16280
16280
 
16281
16281
  /**
16282
16282
  * Do not edit directly
16283
- * Generated on Tue, 18 Jun 2024 21:57:15 GMT
16283
+ * Generated on Tue, 25 Jun 2024 18:25:46 GMT
16284
16284
  */
16285
16285
 
16286
16286
  const Information100DarkUi = "#a46eff";
@@ -16301,11 +16301,25 @@
16301
16301
  const Black91 = "#161617";
16302
16302
  const ForestGreen = "#074023";
16303
16303
  const DigitalGreenLight = "#009b65";
16304
+ const NiSky = "#3cb4e7";
16304
16305
  const BannerFail100DarkUi = "#d63434";
16305
16306
  const Warning100LightUi = "#ff4b00";
16306
16307
  const DigitalGreenDark = "#008557";
16307
16308
  const DigitalGreenDark105 = "#00734b";
16308
16309
  const PowerGreen = "#32eb96";
16310
+ const NiHoneyLight = "#ffe76b";
16311
+ const NiScarlet = "#ef463f";
16312
+ const NiFern = "#76bc43";
16313
+ const NiScarletDark3 = "#371615";
16314
+ const NiScarletDark1 = "#cd1722";
16315
+ const NiFernDark1 = "#499146";
16316
+ const NiSea = "#00aba5";
16317
+ const NiSeaLight = "#a5d8c8";
16318
+ const NiTulip = "#e54c8a";
16319
+ const NiIndigo = "#8e78b7";
16320
+ const NiPlumDark1 = "#8c459a";
16321
+ const NiIndigoDark2 = "#312e7f";
16322
+ const NiSeaDark2 = "#00685f";
16309
16323
  const Title2Family = "Source Sans Pro";
16310
16324
  const Title2Weight = "400";
16311
16325
  const ControlLabel1Family = "Source Sans Pro";
@@ -16632,6 +16646,14 @@
16632
16646
  elevation2BoxShadow: 'elevation-2-box-shadow',
16633
16647
  elevation3BoxShadow: 'elevation-3-box-shadow',
16634
16648
  graphGridlineColor: 'graph-gridline-color',
16649
+ graphTrace1Color: 'graph-trace-1-color',
16650
+ graphTrace2Color: 'graph-trace-2-color',
16651
+ graphTrace3Color: 'graph-trace-3-color',
16652
+ graphTrace4Color: 'graph-trace-4-color',
16653
+ graphTrace5Color: 'graph-trace-5-color',
16654
+ graphTrace6Color: 'graph-trace-6-color',
16655
+ graphTrace7Color: 'graph-trace-7-color',
16656
+ graphTrace8Color: 'graph-trace-8-color',
16635
16657
  mentionFont: 'mention-font',
16636
16658
  mentionFontColor: 'mention-font-color',
16637
16659
  mentionDisabledFontColor: 'mention-disabled-font-color',
@@ -16677,6 +16699,9 @@
16677
16699
  const keyEnter = "Enter";
16678
16700
  const keyEscape = "Escape";
16679
16701
  const keyHome = "Home";
16702
+ const keyFunction2 = "F2";
16703
+ const keyPageDown = "PageDown";
16704
+ const keyPageUp = "PageUp";
16680
16705
  const keyShift = "Shift";
16681
16706
  const keySpace = " ";
16682
16707
  const keyTab = "Tab";
@@ -16861,6 +16886,14 @@
16861
16886
  const popupBorderColor = DesignToken.create(styleNameFromTokenName(tokenNames.popupBorderColor)).withDefault((element) => hexToRgbaCssColor(getColorForTheme(element, Black91, Black15, White), 0.3));
16862
16887
  const cardBorderColor = DesignToken.create(styleNameFromTokenName(tokenNames.cardBorderColor)).withDefault((element) => hexToRgbaCssColor(getColorForTheme(element, Black91, Black15, White), 0.1));
16863
16888
  DesignToken.create(styleNameFromTokenName(tokenNames.graphGridlineColor)).withDefault((element) => hexToRgbaCssColor(getColorForTheme(element, Black91, Black15, White), 0.2));
16889
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace1Color)).withDefault((element) => getColorForTheme(element, NiIndigoDark2, NiSky, White));
16890
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace2Color)).withDefault((element) => getColorForTheme(element, NiScarletDark1, NiScarlet, hexToRgbaCssColor(White, 0.7)));
16891
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace3Color)).withDefault((element) => getColorForTheme(element, NiFernDark1, NiFern, hexToRgbaCssColor(White, 0.4)));
16892
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace4Color)).withDefault((element) => getColorForTheme(element, NiPlumDark1, NiSeaLight, hexToRgbaCssColor(White, 0.25)));
16893
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace5Color)).withDefault((element) => getColorForTheme(element, NiSeaDark2, NiSea, hexToRgbaCssColor(White, 0.55)));
16894
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace6Color)).withDefault((element) => getColorForTheme(element, NiTulip, NiTulip, hexToRgbaCssColor(White, 0.85)));
16895
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace7Color)).withDefault((element) => getColorForTheme(element, NiScarletDark3, NiHoneyLight, hexToRgbaCssColor(White, 0.325)));
16896
+ DesignToken.create(styleNameFromTokenName(tokenNames.graphTrace8Color)).withDefault((element) => getColorForTheme(element, NiIndigo, NiIndigo, hexToRgbaCssColor(White, 0.625)));
16864
16897
  DesignToken.create(styleNameFromTokenName(tokenNames.tooltipBackgroundColor)).withDefault((element) => getColorForTheme(element, Black15, Black85, ForestGreen));
16865
16898
  const tableRowBorderColor = DesignToken.create(styleNameFromTokenName(tokenNames.tableRowBorderColor)).withDefault((element) => getColorForTheme(element, Black15, Black80, ForestGreen));
16866
16899
  DesignToken.create(styleNameFromTokenName(tokenNames.tagFillColor)).withDefault((element) => hexToRgbaCssColor(getColorForTheme(element, Black91, Black15, White), 0.1));
@@ -64403,6 +64436,20 @@ img.ProseMirror-separator {
64403
64436
  selected: 'selected',
64404
64437
  partiallySelected: 'partially-selected'
64405
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
+ };
64406
64453
 
64407
64454
  /**
64408
64455
  * The possible operations to use when sorting a table column.
@@ -64484,6 +64531,13 @@ img.ProseMirror-separator {
64484
64531
  this.delegatedEvents = [];
64485
64532
  this.delegatedEventHandler = () => { };
64486
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
+ }
64487
64541
  /**
64488
64542
  * Called if an element inside this cell view has focus, and this row/cell is being recycled.
64489
64543
  * Expected implementation is to commit changes as needed, and blur the focusable element (or close
@@ -64867,6 +64921,15 @@ img.ProseMirror-separator {
64867
64921
  --ni-private-column-divider-padding: 3px;
64868
64922
  }
64869
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
+
64870
64933
  .disable-select {
64871
64934
  ${userSelectNone}
64872
64935
  }
@@ -64997,6 +65060,10 @@ img.ProseMirror-separator {
64997
65060
  position: relative;
64998
65061
  }
64999
65062
 
65063
+ .table-viewport${focusVisible} {
65064
+ outline: none;
65065
+ }
65066
+
65000
65067
  .table-scroll {
65001
65068
  pointer-events: none;
65002
65069
  position: absolute;
@@ -65023,10 +65090,17 @@ img.ProseMirror-separator {
65023
65090
 
65024
65091
  .group-row {
65025
65092
  position: relative;
65093
+ --ni-private-table-cell-focus-offset-multiplier: 0;
65026
65094
  }
65027
65095
 
65028
65096
  .row {
65029
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;
65030
65104
  }
65031
65105
 
65032
65106
  .accessibly-hidden {
@@ -65057,6 +65131,11 @@ img.ProseMirror-separator {
65057
65131
  cursor: default;
65058
65132
  }
65059
65133
 
65134
+ :host(${focusVisible}) {
65135
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65136
+ outline-offset: calc(-2 * ${borderWidth});
65137
+ }
65138
+
65060
65139
  .sort-indicator,
65061
65140
  .grouped-indicator {
65062
65141
  flex: 0 0 auto;
@@ -65210,6 +65289,11 @@ img.ProseMirror-separator {
65210
65289
  background-color: ${fillHoverSelectedColor};
65211
65290
  }
65212
65291
 
65292
+ :host(${focusVisible}) {
65293
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65294
+ outline-offset: calc(-2 * ${borderWidth});
65295
+ }
65296
+
65213
65297
  .expand-collapse-button {
65214
65298
  flex: 0 0 auto;
65215
65299
  margin-left: max(
@@ -65284,6 +65368,34 @@ img.ProseMirror-separator {
65284
65368
  --ni-private-table-cell-action-menu-display: block;
65285
65369
  }
65286
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
+
65287
65399
  :host(:hover) nimble-table-cell {
65288
65400
  --ni-private-table-cell-action-menu-display: block;
65289
65401
  }
@@ -65291,6 +65403,10 @@ img.ProseMirror-separator {
65291
65403
  :host([selected]) nimble-table-cell {
65292
65404
  --ni-private-table-cell-action-menu-display: block;
65293
65405
  }
65406
+
65407
+ :host(${focusVisible}) nimble-table-cell {
65408
+ --ni-private-table-cell-action-menu-display: block;
65409
+ }
65294
65410
  `.withBehaviors(themeBehavior(Theme.color, css `
65295
65411
  :host([selectable]:not([selected]):hover)::before {
65296
65412
  background-color: ${hexToRgbaCssColor(White, 0.05)};
@@ -65321,6 +65437,15 @@ img.ProseMirror-separator {
65321
65437
  --ni-private-table-cell-action-menu-display: block;
65322
65438
  }
65323
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
+
65324
65449
  .cell-view {
65325
65450
  overflow: hidden;
65326
65451
  }
@@ -65333,19 +65458,33 @@ img.ProseMirror-separator {
65333
65458
  height: ${controlSlimHeight};
65334
65459
  align-self: center;
65335
65460
  }
65461
+
65462
+ ${
65463
+ /* This CSS class is applied dynamically by KeyboardNavigationManager */ ''}
65464
+ .action-menu.cell-action-menu-focused {
65465
+ display: block;
65466
+ }
65336
65467
  `;
65337
65468
 
65338
65469
  // prettier-ignore
65339
65470
  const template$h = html `
65340
- <template role="cell" style="--ni-private-table-cell-nesting-level: ${x => x.nestingLevel}">
65341
- ${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>
65342
65478
  ${when(x => x.hasActionMenu, html `
65343
65479
  <${menuButtonTag} ${ref('actionMenuButton')}
65344
65480
  content-hidden
65345
65481
  appearance="${ButtonAppearance.ghost}"
65482
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
65483
+ tabindex="-1"
65346
65484
  @beforetoggle="${(x, c) => x.onActionMenuBeforeToggle(c.event)}"
65347
65485
  @toggle="${(x, c) => x.onActionMenuToggle(c.event)}"
65348
65486
  @click="${(_, c) => c.event.stopPropagation()}"
65487
+ @blur="${x => x.onActionMenuBlur()}"
65349
65488
  class="action-menu"
65350
65489
  title="${x => (x.actionMenuLabel ? x.actionMenuLabel : tableCellActionMenuLabel.getValueFor(x))}"
65351
65490
  >
@@ -65368,6 +65507,11 @@ img.ProseMirror-separator {
65368
65507
  this.menuOpen = false;
65369
65508
  this.nestingLevel = 0;
65370
65509
  }
65510
+ /** @internal */
65511
+ get cellView() {
65512
+ return this.cellViewContainer
65513
+ .firstElementChild;
65514
+ }
65371
65515
  onActionMenuBeforeToggle(event) {
65372
65516
  this.$emit('cell-action-menu-beforetoggle', event.detail);
65373
65517
  }
@@ -65375,6 +65519,18 @@ img.ProseMirror-separator {
65375
65519
  this.menuOpen = event.detail.newState;
65376
65520
  this.$emit('cell-action-menu-toggle', event.detail);
65377
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
+ }
65378
65534
  }
65379
65535
  __decorate$1([
65380
65536
  observable
@@ -65400,6 +65556,9 @@ img.ProseMirror-separator {
65400
65556
  __decorate$1([
65401
65557
  observable
65402
65558
  ], TableCell.prototype, "cellViewTemplate", void 0);
65559
+ __decorate$1([
65560
+ observable
65561
+ ], TableCell.prototype, "cellViewContainer", void 0);
65403
65562
  __decorate$1([
65404
65563
  observable
65405
65564
  ], TableCell.prototype, "nestingLevel", void 0);
@@ -65421,11 +65580,13 @@ img.ProseMirror-separator {
65421
65580
  >
65422
65581
  ${when(x => !x.rowOperationGridCellHidden, html `
65423
65582
  <span role="gridcell" class="row-operations-container">
65424
- ${when(x => x.selectable && !x.hideSelection, html `
65583
+ ${when(x => x.showSelectionCheckbox, html `
65425
65584
  <${checkboxTag}
65426
65585
  ${ref('selectionCheckbox')}
65427
65586
  class="selection-checkbox"
65428
- @change="${(x, c) => x.onSelectionChange(c.event)}"
65587
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
65588
+ tabindex="-1"
65589
+ @change="${(x, c) => x.onSelectionCheckboxChange(c.event)}"
65429
65590
  @click="${(_, c) => c.event.stopPropagation()}"
65430
65591
  title="${x => tableRowSelectLabel.getValueFor(x)}"
65431
65592
  aria-label="${x => tableRowSelectLabel.getValueFor(x)}"
@@ -65546,6 +65707,10 @@ img.ProseMirror-separator {
65546
65707
  get isNestedParent() {
65547
65708
  return this.isParentRow && this.nestingLevel > 0;
65548
65709
  }
65710
+ /** @internal */
65711
+ get showSelectionCheckbox() {
65712
+ return this.selectable && !this.hideSelection;
65713
+ }
65549
65714
  get ariaSelected() {
65550
65715
  if (this.selectable) {
65551
65716
  return this.selected ? 'true' : 'false';
@@ -65553,16 +65718,20 @@ img.ProseMirror-separator {
65553
65718
  return null;
65554
65719
  }
65555
65720
  /** @internal */
65556
- onSelectionChange(event) {
65721
+ onSelectionCheckboxChange(event) {
65557
65722
  if (this.ignoreSelectionChangeEvents) {
65558
65723
  return;
65559
65724
  }
65560
65725
  const checkbox = event.target;
65561
65726
  const checked = checkbox.checked;
65562
- this.selected = checked;
65727
+ this.onSelectionChange(!checked, checked);
65728
+ }
65729
+ /** @internal */
65730
+ onSelectionChange(oldState, newState) {
65731
+ this.selected = newState;
65563
65732
  const detail = {
65564
- oldState: !checked,
65565
- newState: checked
65733
+ oldState,
65734
+ newState
65566
65735
  };
65567
65736
  this.$emit('row-selection-toggle', detail);
65568
65737
  }
@@ -65593,6 +65762,20 @@ img.ProseMirror-separator {
65593
65762
  this.updateCellStates();
65594
65763
  }
65595
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
+ }
65596
65779
  onRowExpandToggle(event) {
65597
65780
  const expandEventDetail = {
65598
65781
  oldState: this.expanded,
@@ -65600,7 +65783,7 @@ img.ProseMirror-separator {
65600
65783
  recordId: this.recordId
65601
65784
  };
65602
65785
  this.$emit('row-expand-toggle', expandEventDetail);
65603
- event.stopImmediatePropagation();
65786
+ event?.stopImmediatePropagation();
65604
65787
  // To avoid a visual glitch with improper expand/collapse icons performing an
65605
65788
  // animation (due to visual re-use apparently), we apply a class to the
65606
65789
  // contained expand-collapse button temporarily. We use the 'transitionend' event
@@ -65712,6 +65895,9 @@ img.ProseMirror-separator {
65712
65895
  __decorate$1([
65713
65896
  observable
65714
65897
  ], TableRow.prototype, "nestingLevel", void 0);
65898
+ __decorate$1([
65899
+ observable
65900
+ ], TableRow.prototype, "resolvedRowIndex", void 0);
65715
65901
  __decorate$1([
65716
65902
  attr({ attribute: 'is-parent-row', mode: 'boolean' })
65717
65903
  ], TableRow.prototype, "isParentRow", void 0);
@@ -65742,6 +65928,9 @@ img.ProseMirror-separator {
65742
65928
  __decorate$1([
65743
65929
  volatile
65744
65930
  ], TableRow.prototype, "isNestedParent", null);
65931
+ __decorate$1([
65932
+ volatile
65933
+ ], TableRow.prototype, "showSelectionCheckbox", null);
65745
65934
  __decorate$1([
65746
65935
  volatile
65747
65936
  ], TableRow.prototype, "ariaSelected", null);
@@ -65792,6 +65981,11 @@ img.ProseMirror-separator {
65792
65981
  background-color: ${fillHoverColor};
65793
65982
  }
65794
65983
 
65984
+ :host(${focusVisible}) {
65985
+ outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
65986
+ outline-offset: calc(-2 * ${borderWidth});
65987
+ }
65988
+
65795
65989
  .expand-collapse-button {
65796
65990
  margin-left: calc(
65797
65991
  ${mediumPadding} + ${standardPadding} * 2 *
@@ -65852,7 +66046,7 @@ img.ProseMirror-separator {
65852
66046
  <${checkboxTag}
65853
66047
  ${ref('selectionCheckbox')}
65854
66048
  class="selection-checkbox"
65855
- @change="${(x, c) => x.onSelectionChange(c.event)}"
66049
+ @change="${(x, c) => x.onSelectionCheckboxChange(c.event)}"
65856
66050
  @click="${(_, c) => c.event.stopPropagation()}"
65857
66051
  title="${x => tableGroupSelectAllLabel.getValueFor(x)}"
65858
66052
  aria-label="${x => tableGroupSelectAllLabel.getValueFor(x)}"
@@ -65918,7 +66112,7 @@ img.ProseMirror-separator {
65918
66112
  this.expandIcon.addEventListener('transitionend', this.removeAnimatingClass);
65919
66113
  }
65920
66114
  /** @internal */
65921
- onSelectionChange(event) {
66115
+ onSelectionCheckboxChange(event) {
65922
66116
  if (this.ignoreSelectionChangeEvents) {
65923
66117
  return;
65924
66118
  }
@@ -65933,6 +66127,15 @@ img.ProseMirror-separator {
65933
66127
  };
65934
66128
  this.$emit('group-selection-toggle', detail);
65935
66129
  }
66130
+ /** @internal */
66131
+ getFocusableElements() {
66132
+ return {
66133
+ selectionCheckbox: this.selectable
66134
+ ? this.selectionCheckbox
66135
+ : undefined,
66136
+ cells: []
66137
+ };
66138
+ }
65936
66139
  selectionStateChanged() {
65937
66140
  this.setSelectionCheckboxState();
65938
66141
  }
@@ -65955,6 +66158,9 @@ img.ProseMirror-separator {
65955
66158
  __decorate$1([
65956
66159
  observable
65957
66160
  ], TableGroupRow.prototype, "nestingLevel", void 0);
66161
+ __decorate$1([
66162
+ observable
66163
+ ], TableGroupRow.prototype, "resolvedRowIndex", void 0);
65958
66164
  __decorate$1([
65959
66165
  observable
65960
66166
  ], TableGroupRow.prototype, "immediateChildCount", void 0);
@@ -65988,6 +66194,8 @@ img.ProseMirror-separator {
65988
66194
  const template$e = html `
65989
66195
  <template
65990
66196
  role="treegrid"
66197
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66198
+ tabindex="0"
65991
66199
  aria-multiselectable="${x => x.ariaMultiSelectable}"
65992
66200
  ${children$1({ property: 'childItems', filter: elements() })}
65993
66201
  >
@@ -66015,6 +66223,8 @@ img.ProseMirror-separator {
66015
66223
  <span class="checkbox-container">
66016
66224
  <${checkboxTag}
66017
66225
  ${ref('selectionCheckbox')}
66226
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66227
+ tabindex="-1"
66018
66228
  class="${x => `selection-checkbox ${x.selectionMode ? x.selectionMode : ''}`}"
66019
66229
  @change="${(x, c) => x.onAllRowsSelectionChange(c.event)}"
66020
66230
  title="${x => tableSelectAllLabel.getValueFor(x)}"
@@ -66024,6 +66234,9 @@ img.ProseMirror-separator {
66024
66234
  </span>
66025
66235
  `)}
66026
66236
  <${buttonTag}
66237
+ ${ref('collapseAllButton')}
66238
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66239
+ tabindex="-1"
66027
66240
  class="collapse-all-button ${x => `${x.showCollapseAll ? 'visible' : ''}`}"
66028
66241
  content-hidden
66029
66242
  appearance="${ButtonAppearance.ghost}"
@@ -66051,9 +66264,11 @@ img.ProseMirror-separator {
66051
66264
  `)}
66052
66265
  <${tableHeaderTag}
66053
66266
  class="header"
66267
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager (if column sorting not disabled) */}
66054
66268
  sort-direction="${x => (typeof x.columnInternals.currentSortIndex === 'number' ? x.columnInternals.currentSortDirection : TableColumnSortDirection.none)}"
66055
66269
  ?first-sorted-column="${(x, c) => x === c.parent.firstSortedColumn}"
66056
66270
  ?indicators-hidden="${x => x.columnInternals.hideHeaderIndicators}"
66271
+ @keydown="${(x, c) => c.parent.onHeaderKeyDown(x, c.event)}"
66057
66272
  @click="${(x, c) => c.parent.toggleColumnSort(x, c.event.shiftKey)}"
66058
66273
  :isGrouped=${x => (typeof x.columnInternals.groupIndex === 'number' && !x.columnInternals.groupingDisabled)}
66059
66274
  >
@@ -66077,15 +66292,17 @@ img.ProseMirror-separator {
66077
66292
  </span>
66078
66293
  </div>
66079
66294
  </div>
66080
- <div class="table-viewport" ${ref('viewport')}>
66295
+ <div class="table-viewport" tabindex="-1" ${ref('viewport')}>
66081
66296
  <div class="table-scroll"></div>
66082
- <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}`) })}
66083
66298
  role="rowgroup">
66084
66299
  ${when(x => x.columns.length > 0 && x.canRenderRows, html `
66085
66300
  ${repeat(x => x.virtualizer.visibleItems, html `
66086
66301
  ${when((x, c) => c.parent.tableData[x.index]?.isGroupRow, html `
66087
66302
  <${tableGroupRowTag}
66088
66303
  class="group-row"
66304
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66305
+ tabindex="-1"
66089
66306
  :groupRowValue="${(x, c) => c.parent.tableData[x.index]?.groupRowValue}"
66090
66307
  ?expanded="${(x, c) => c.parent.tableData[x.index]?.isExpanded}"
66091
66308
  :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}"
@@ -66093,6 +66310,9 @@ img.ProseMirror-separator {
66093
66310
  :groupColumn="${(x, c) => c.parent.tableData[x.index]?.groupColumn}"
66094
66311
  ?selectable="${(_, c) => c.parent.selectionMode === TableRowSelectionMode.multiple}"
66095
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)}"
66096
66316
  @group-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event)}"
66097
66317
  @group-expand-toggle="${(x, c) => c.parent.handleGroupRowExpanded(x.index, c.event)}"
66098
66318
  >
@@ -66101,6 +66321,8 @@ img.ProseMirror-separator {
66101
66321
  ${when((x, c) => !c.parent.tableData[x.index]?.isGroupRow, html `
66102
66322
  <${tableRowTag}
66103
66323
  class="row"
66324
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
66325
+ tabindex="-1"
66104
66326
  record-id="${(x, c) => c.parent.tableData[x.index]?.id}"
66105
66327
  ?selectable="${(_, c) => c.parent.selectionMode !== TableRowSelectionMode.none}"
66106
66328
  ?selected="${(x, c) => c.parent.tableData[x.index]?.selectionState === TableRowSelectionState.selected}"
@@ -66112,12 +66334,14 @@ img.ProseMirror-separator {
66112
66334
  :nestingLevel="${(x, c) => c.parent.tableData[x.index]?.nestingLevel}"
66113
66335
  ?row-operation-grid-cell-hidden="${(_, c) => !c.parent.showRowOperationColumn}"
66114
66336
  ?loading="${(x, c) => c.parent.tableData[x.index]?.isLoadingChildren}"
66337
+ :resolvedRowIndex="${x => x.index}"
66115
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)}"
66116
66341
  @row-selection-toggle="${(x, c) => c.parent.onRowSelectionToggle(x.index, c.event)}"
66117
66342
  @row-action-menu-beforetoggle="${(x, c) => c.parent.onRowActionMenuBeforeToggle(x.index, c.event)}"
66118
66343
  @row-action-menu-toggle="${(_, c) => c.parent.onRowActionMenuToggle(c.event)}"
66119
66344
  @row-expand-toggle="${(x, c) => c.parent.handleRowExpanded(x.index)}"
66120
- :dataIndex="${x => x.index}"
66121
66345
  >
66122
66346
  ${when((x, c) => c.parent.openActionMenuRecordId === c.parent.tableData[x.index]?.id, html `
66123
66347
  ${repeat((_, c) => c.parent.actionMenuSlots, html `
@@ -66782,16 +67006,25 @@ img.ProseMirror-separator {
66782
67006
  * @internal
66783
67007
  */
66784
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
+ }
66785
67016
  constructor(table, tanStackTable) {
66786
67017
  this.visibleItems = [];
66787
67018
  this.scrollHeight = 0;
66788
67019
  this.headerContainerMarginRight = 0;
66789
67020
  this.rowContainerYOffset = 0;
67021
+ this._pageSize = 0;
66790
67022
  this.table = table;
66791
67023
  this.tanStackTable = tanStackTable;
66792
67024
  this.viewportResizeObserver = new ResizeObserver(entries => {
66793
67025
  const borderBoxSize = entries[0]?.borderBoxSize[0];
66794
67026
  if (borderBoxSize) {
67027
+ this.updatePageSize();
66795
67028
  // If we have enough rows that a vertical scrollbar is shown, we need to offset the header widths
66796
67029
  // by the same margin so the column headers align with the corresponding rendered cells
66797
67030
  const viewportBoundingWidth = borderBoxSize.inlineSize;
@@ -66812,6 +67045,9 @@ img.ProseMirror-separator {
66812
67045
  this.updateVirtualizer();
66813
67046
  }
66814
67047
  }
67048
+ scrollToIndex(index, options) {
67049
+ this.virtualizer?.scrollToIndex(index, options);
67050
+ }
66815
67051
  updateVirtualizer() {
66816
67052
  const options = this.createVirtualizerOptions();
66817
67053
  if (this.virtualizer) {
@@ -66824,8 +67060,7 @@ img.ProseMirror-separator {
66824
67060
  this.handleVirtualizerChange();
66825
67061
  }
66826
67062
  createVirtualizerOptions() {
66827
- const rowHeight = parseFloat(controlHeight.getValueFor(this.table))
66828
- + 2 * parseFloat(borderWidth.getValueFor(this.table));
67063
+ const rowHeight = this.rowHeight;
66829
67064
  return {
66830
67065
  count: this.tanStackTable.getRowModel().rows.length,
66831
67066
  getScrollElement: () => {
@@ -66842,7 +67077,7 @@ img.ProseMirror-separator {
66842
67077
  };
66843
67078
  }
66844
67079
  handleVirtualizerChange() {
66845
- this.notifyFocusedCellRecycling();
67080
+ this.table.handleFocusedCellRecycling();
66846
67081
  const virtualizer = this.virtualizer;
66847
67082
  this.visibleItems = virtualizer.getVirtualItems();
66848
67083
  this.scrollHeight = virtualizer.getTotalSize();
@@ -66857,24 +67092,8 @@ img.ProseMirror-separator {
66857
67092
  }
66858
67093
  this.rowContainerYOffset = rowContainerYOffset;
66859
67094
  }
66860
- notifyFocusedCellRecycling() {
66861
- let tableFocusedElement = this.table.shadowRoot.activeElement;
66862
- while (tableFocusedElement !== null
66863
- && !(tableFocusedElement instanceof TableCellView)) {
66864
- if (tableFocusedElement.shadowRoot) {
66865
- tableFocusedElement = tableFocusedElement.shadowRoot.activeElement;
66866
- }
66867
- else {
66868
- break;
66869
- }
66870
- }
66871
- if (tableFocusedElement instanceof TableCellView) {
66872
- tableFocusedElement.focusedRecycleCallback();
66873
- }
66874
- if (this.table.openActionMenuRecordId !== undefined) {
66875
- const activeRow = this.table.rowElements.find(row => row.recordId === this.table.openActionMenuRecordId);
66876
- activeRow?.closeOpenActionMenus();
66877
- }
67095
+ updatePageSize() {
67096
+ this._pageSize = Math.round(this.table.viewport.clientHeight / this.rowHeight);
66878
67097
  }
66879
67098
  }
66880
67099
  __decorate$1([
@@ -67234,7 +67453,9 @@ img.ProseMirror-separator {
67234
67453
  'rowParentIds',
67235
67454
  'groupRows',
67236
67455
  'columnIds',
67456
+ 'columnHidden',
67237
67457
  'columnSort',
67458
+ 'columnSortDisabled',
67238
67459
  'columnWidths',
67239
67460
  'columnDefinition',
67240
67461
  'actionMenuSlots',
@@ -67290,6 +67511,13 @@ img.ProseMirror-separator {
67290
67511
  || this.isTracked('columnDefinition')
67291
67512
  || this.isTracked('rowParentIds'));
67292
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
+ }
67293
67521
  trackAllStateChanged() {
67294
67522
  this.trackAll();
67295
67523
  this.queueUpdate();
@@ -67304,13 +67532,20 @@ img.ProseMirror-separator {
67304
67532
  else if (isColumnInternalsProperty(changedColumnProperty, 'operandDataRecordFieldName', 'sortOperation')) {
67305
67533
  this.track('columnDefinition');
67306
67534
  }
67307
- 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')) {
67308
67540
  this.track('columnSort');
67309
67541
  }
67310
- else if (isColumnProperty(changedColumnProperty, 'columnHidden')
67311
- || isColumnInternalsProperty(changedColumnProperty, 'currentFractionalWidth', 'currentPixelWidth', 'minPixelWidth', 'resizingDisabled')) {
67542
+ else if (isColumnInternalsProperty(changedColumnProperty, 'currentFractionalWidth', 'currentPixelWidth', 'minPixelWidth', 'resizingDisabled')) {
67312
67543
  this.track('columnWidths');
67313
67544
  }
67545
+ else if (isColumnProperty(changedColumnProperty, 'columnHidden')) {
67546
+ this.track('columnWidths');
67547
+ this.track('columnHidden');
67548
+ }
67314
67549
  else if (isColumnProperty(changedColumnProperty, 'actionMenuSlot')) {
67315
67550
  this.track('actionMenuSlots');
67316
67551
  }
@@ -67323,6 +67558,7 @@ img.ProseMirror-separator {
67323
67558
  this.track('columnIds');
67324
67559
  this.track('columnDefinition');
67325
67560
  this.track('columnSort');
67561
+ this.track('columnSortDisabled');
67326
67562
  this.track('columnWidths');
67327
67563
  this.track('actionMenuSlots');
67328
67564
  this.track('groupRows');
@@ -67952,6 +68188,992 @@ img.ProseMirror-separator {
67952
68188
  observable
67953
68189
  ], ColumnValidator.prototype, "isColumnValid", void 0);
67954
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
+
67955
69177
  /**
67956
69178
  * A nimble-styled table.
67957
69179
  */
@@ -68092,6 +69314,7 @@ img.ProseMirror-separator {
68092
69314
  };
68093
69315
  this.table = createTable(this.options);
68094
69316
  this.virtualizer = new Virtualizer(this, this.table);
69317
+ this.keyboardNavigationManager = new KeyboardNavigationManager(this, this.virtualizer);
68095
69318
  this.layoutManager = new TableLayoutManager(this);
68096
69319
  this.layoutManagerNotifier = Observable.getNotifier(this.layoutManager);
68097
69320
  this.layoutManagerNotifier.subscribe(this, 'isColumnBeingSized');
@@ -68132,12 +69355,14 @@ img.ProseMirror-separator {
68132
69355
  this.viewport.addEventListener('scroll', this.onViewPortScroll, {
68133
69356
  passive: true
68134
69357
  });
69358
+ this.keyboardNavigationManager.connect();
68135
69359
  document.addEventListener('keydown', this.onKeyDown);
68136
69360
  document.addEventListener('keyup', this.onKeyUp);
68137
69361
  }
68138
69362
  disconnectedCallback() {
68139
69363
  super.disconnectedCallback();
68140
69364
  this.virtualizer.disconnect();
69365
+ this.keyboardNavigationManager.disconnect();
68141
69366
  this.viewport.removeEventListener('scroll', this.onViewPortScroll);
68142
69367
  document.removeEventListener('keydown', this.onKeyDown);
68143
69368
  document.removeEventListener('keyup', this.onKeyUp);
@@ -68187,6 +69412,14 @@ img.ProseMirror-separator {
68187
69412
  return true;
68188
69413
  }
68189
69414
  /** @internal */
69415
+ onRowFocusIn(event) {
69416
+ this.keyboardNavigationManager.onRowFocusIn(event);
69417
+ }
69418
+ /** @internal */
69419
+ onRowBlur(event) {
69420
+ this.keyboardNavigationManager.onRowBlur(event);
69421
+ }
69422
+ /** @internal */
68190
69423
  onAllRowsSelectionChange(event) {
68191
69424
  event.stopPropagation();
68192
69425
  if (this.ignoreSelectionChangeEvents) {
@@ -68280,6 +69513,18 @@ img.ProseMirror-separator {
68280
69513
  }
68281
69514
  this.emitColumnConfigurationChangeEvent();
68282
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
+ }
68283
69528
  /**
68284
69529
  * @internal
68285
69530
  */
@@ -68295,6 +69540,9 @@ img.ProseMirror-separator {
68295
69540
  this.rowGridColumns = this.layoutManager.getGridTemplateColumns();
68296
69541
  this.visibleColumns = this.columns.filter(column => !column.columnHidden);
68297
69542
  }
69543
+ if (this.tableUpdateTracker.requiresKeyboardFocusReset) {
69544
+ this.keyboardNavigationManager.resetFocusState();
69545
+ }
68298
69546
  }
68299
69547
  get ariaMultiSelectable() {
68300
69548
  switch (this.selectionMode) {
@@ -68337,6 +69585,29 @@ img.ProseMirror-separator {
68337
69585
  }
68338
69586
  return tanStackUpdates;
68339
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
+ }
68340
69611
  selectionModeChanged(_prev, _next) {
68341
69612
  if (!this.$fastController.isConnected) {
68342
69613
  return;
@@ -68372,6 +69643,7 @@ img.ProseMirror-separator {
68372
69643
  this.$emit('action-menu-beforetoggle', detail);
68373
69644
  }
68374
69645
  async handleRowActionMenuToggleEvent(event) {
69646
+ this.keyboardNavigationManager.onRowActionMenuToggle(event);
68375
69647
  const detail = await this.getActionMenuToggleEventDetail(event);
68376
69648
  this.$emit('action-menu-toggle', detail);
68377
69649
  if (!event.detail.newState) {
@@ -68582,6 +69854,7 @@ img.ProseMirror-separator {
68582
69854
  isParentRow: isParent,
68583
69855
  immediateChildCount: row.subRows.length,
68584
69856
  groupColumn: this.getGroupRowColumn(row),
69857
+ resolvedRowIndex: row.index,
68585
69858
  isLoadingChildren: this.expansionManager.isLoadingChildren(row.id)
68586
69859
  };
68587
69860
  hasDataHierarchy = hasDataHierarchy || isParent;
@@ -68749,6 +70022,9 @@ img.ProseMirror-separator {
68749
70022
  __decorate$1([
68750
70023
  observable
68751
70024
  ], Table.prototype, "selectionCheckbox", void 0);
70025
+ __decorate$1([
70026
+ observable
70027
+ ], Table.prototype, "collapseAllButton", void 0);
68752
70028
  __decorate$1([
68753
70029
  observable
68754
70030
  ], Table.prototype, "showCollapseAll", void 0);
@@ -68902,10 +70178,12 @@ img.ProseMirror-separator {
68902
70178
  }}"
68903
70179
  class="${x => (x.isPlaceholder ? 'placeholder' : '')}"
68904
70180
  >
68905
- ${when(x => typeof x.cellRecord?.href === 'string', html `
70181
+ ${when(x => x.showAnchor, html `
68906
70182
  <${anchorTag}
68907
70183
  ${ref('anchor')}
68908
70184
  ${overflow('hasOverflow')}
70185
+ ${'' /* tabindex managed dynamically by KeyboardNavigationManager */}
70186
+ tabindex="-1"
68909
70187
  href="${x => x.cellRecord?.href}"
68910
70188
  hreflang="${x => x.columnConfig?.hreflang}"
68911
70189
  ping="${x => x.columnConfig?.ping}"
@@ -68920,7 +70198,7 @@ img.ProseMirror-separator {
68920
70198
  >
68921
70199
  ${x => x.text}
68922
70200
  </${anchorTag}>`)}
68923
- ${when(x => typeof x.cellRecord?.href !== 'string', html `
70201
+ ${when(x => !x.showAnchor, html `
68924
70202
  <span
68925
70203
  ${overflow('hasOverflow')}
68926
70204
  title=${x => (x.hasOverflow ? x.text : null)}
@@ -68957,9 +70235,19 @@ img.ProseMirror-separator {
68957
70235
  }
68958
70236
  return '';
68959
70237
  }
70238
+ /** @internal */
70239
+ get showAnchor() {
70240
+ return typeof this.cellRecord?.href === 'string';
70241
+ }
68960
70242
  focusedRecycleCallback() {
68961
70243
  this.anchor?.blur();
68962
70244
  }
70245
+ get tabbableChildren() {
70246
+ if (this.showAnchor) {
70247
+ return [this.anchor];
70248
+ }
70249
+ return [];
70250
+ }
68963
70251
  }
68964
70252
  __decorate$1([
68965
70253
  observable
@@ -68970,6 +70258,9 @@ img.ProseMirror-separator {
68970
70258
  __decorate$1([
68971
70259
  volatile
68972
70260
  ], TableColumnAnchorCellView.prototype, "text", null);
70261
+ __decorate$1([
70262
+ volatile
70263
+ ], TableColumnAnchorCellView.prototype, "showAnchor", null);
68973
70264
  const anchorCellView = TableColumnAnchorCellView.compose({
68974
70265
  baseName: 'table-column-anchor-cell-view',
68975
70266
  template: template$c,