lightning-base-components 1.14.3-alpha → 1.15.1-alpha

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 (152) hide show
  1. package/metadata/raptor.json +37 -4
  2. package/package.json +11 -4
  3. package/scopedImports/@salesforce-internal-core.appVersion.js +1 -1
  4. package/scopedImports/@salesforce-label-LightningModalBase.cancelandclose.js +1 -0
  5. package/scopedImports/@salesforce-label-LightningProgressBar.progressBar.js +1 -0
  6. package/src/lightning/alert/__docs__/alert.md +101 -0
  7. package/src/lightning/alert/__examples__disabled/basic/basic.css +7 -0
  8. package/src/lightning/alert/__examples__disabled/basic/basic.html +8 -0
  9. package/src/lightning/alert/__examples__disabled/basic/basic.js +14 -0
  10. package/src/lightning/alert/alert.html +3 -0
  11. package/src/lightning/alert/alert.js +78 -0
  12. package/src/lightning/alert/alert.js-meta.xml +6 -0
  13. package/src/lightning/ariaObserver/__component__/ariaObserver.spec.js +9 -0
  14. package/src/lightning/ariaObserver/ariaObserver.js +24 -35
  15. package/src/lightning/baseFormattedText/baseFormattedText.html +6 -1
  16. package/src/lightning/baseFormattedText/baseFormattedText.js +5 -0
  17. package/src/lightning/button/__wdio__/utam/utam.html +3 -0
  18. package/src/lightning/button/__wdio__/utam/utam.js +3 -0
  19. package/src/lightning/button/__wdio__/utam/utam.spec.js +20 -0
  20. package/src/lightning/buttonMenu/buttonMenu.js +12 -0
  21. package/src/lightning/confirm/__docs__/confirm.md +100 -0
  22. package/src/lightning/confirm/__examples__disabled/basic/basic.css +7 -0
  23. package/src/lightning/confirm/__examples__disabled/basic/basic.html +8 -0
  24. package/src/lightning/confirm/__examples__disabled/basic/basic.js +14 -0
  25. package/src/lightning/confirm/confirm.html +3 -0
  26. package/src/lightning/confirm/confirm.js +80 -0
  27. package/src/lightning/confirm/confirm.js-meta.xml +6 -0
  28. package/src/lightning/datatable/__examples__/withInfiniteLoading/fetchDataHelper.js +21 -0
  29. package/src/lightning/datatable/__examples__/withInfiniteLoading/withInfiniteLoading.html +13 -0
  30. package/src/lightning/datatable/__examples__/withInfiniteLoading/withInfiniteLoading.js +42 -0
  31. package/src/lightning/datatable/__wdio__/utam/utam.html +17 -0
  32. package/src/lightning/datatable/__wdio__/utam/utam.js +91 -0
  33. package/src/lightning/datatable/__wdio__/utam/utam.spec.js +189 -0
  34. package/src/lightning/datatable/autoWidthStrategy.js +170 -61
  35. package/src/lightning/datatable/{resizer.js → columnResizer.js} +0 -0
  36. package/src/lightning/datatable/columnWidthManager.js +226 -44
  37. package/src/lightning/datatable/columns.js +166 -71
  38. package/src/lightning/datatable/datatable.js +136 -60
  39. package/src/lightning/datatable/fixedWidthStrategy.js +43 -8
  40. package/src/lightning/datatable/headerActions.js +2 -2
  41. package/src/lightning/datatable/infiniteLoading.js +100 -28
  42. package/src/lightning/datatable/inlineEdit.js +21 -30
  43. package/src/lightning/datatable/keyboard.js +166 -131
  44. package/src/lightning/datatable/renderManager.js +117 -122
  45. package/src/lightning/datatable/{datatableResizeObserver.js → resizeObserver.js} +46 -29
  46. package/src/lightning/datatable/resizeSensor.js +19 -3
  47. package/src/lightning/datatable/rowSelection.js +1 -1
  48. package/src/lightning/datatable/rowSelectionShared.js +33 -20
  49. package/src/lightning/datatable/rows.js +9 -8
  50. package/src/lightning/datatable/sort.js +8 -8
  51. package/src/lightning/datatable/state.js +14 -2
  52. package/src/lightning/datatable/templates/div/div.html +133 -119
  53. package/src/lightning/datatable/templates/table/table.html +10 -2
  54. package/src/lightning/datatable/tree.js +25 -0
  55. package/src/lightning/datatable/types.js +77 -9
  56. package/src/lightning/datatable/utils.js +51 -24
  57. package/src/lightning/datatable/virtualization.js +319 -0
  58. package/src/lightning/datatable/widthManagerShared.js +27 -3
  59. package/src/lightning/datatable/wrapText.js +115 -48
  60. package/src/lightning/datepicker/__perf__DISABLED/datepickerWithCalendarOpen.perf.js +55 -0
  61. package/src/lightning/formattedDateTime/__docs__/formattedDateTime.md +36 -3
  62. package/src/lightning/formattedDateTime/__examples__/datetime/datetime.html +2 -2
  63. package/src/lightning/formattedDateTime/__examples__/datetime/datetime.js +3 -1
  64. package/src/lightning/formattedDateTime/__examples__/time/time.html +1 -1
  65. package/src/lightning/formattedDateTime/__examples__/time/time.js +3 -1
  66. package/src/lightning/formattedDateTime/formattedDateTime.js +1 -0
  67. package/src/lightning/iconSvgTemplates/buildTemplates/standard/dashboard_component.html +7 -0
  68. package/src/lightning/iconSvgTemplates/buildTemplates/standard/slack.html +7 -0
  69. package/src/lightning/iconSvgTemplates/buildTemplates/standard/tableau.html +7 -0
  70. package/src/lightning/iconSvgTemplates/buildTemplates/standard/travel_mode.html +2 -2
  71. package/src/lightning/iconSvgTemplates/buildTemplates/templates.js +8 -1
  72. package/src/lightning/iconSvgTemplates/buildTemplates/utility/data_model.html +7 -0
  73. package/src/lightning/iconSvgTemplates/buildTemplates/utility/serialized_product.html +1 -1
  74. package/src/lightning/iconSvgTemplates/buildTemplates/utility/serialized_product_transaction.html +2 -1
  75. package/src/lightning/iconSvgTemplates/buildTemplates/utility/slack.html +7 -0
  76. package/src/lightning/iconSvgTemplates/buildTemplates/utility/tableau.html +7 -0
  77. package/src/lightning/iconSvgTemplates/buildTemplates/utility/video_off.html +7 -0
  78. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/dashboard_component.html +7 -0
  79. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/slack.html +7 -0
  80. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/tableau.html +7 -0
  81. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/travel_mode.html +2 -2
  82. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/templates.js +8 -1
  83. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/data_model.html +7 -0
  84. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/serialized_product.html +1 -1
  85. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/serialized_product_transaction.html +2 -1
  86. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/slack.html +7 -0
  87. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/tableau.html +7 -0
  88. package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/video_off.html +7 -0
  89. package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/dashboard_component.html +7 -0
  90. package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/slack.html +7 -0
  91. package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/tableau.html +7 -0
  92. package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/travel_mode.html +2 -2
  93. package/src/lightning/iconSvgTemplatesStandard/buildTemplates/templates.js +4 -1
  94. package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/dashboard_component.html +7 -0
  95. package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/slack.html +7 -0
  96. package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/tableau.html +7 -0
  97. package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/travel_mode.html +2 -2
  98. package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/templates.js +4 -1
  99. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/templates.js +5 -1
  100. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/data_model.html +7 -0
  101. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/serialized_product.html +1 -1
  102. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/serialized_product_transaction.html +2 -1
  103. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/slack.html +7 -0
  104. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/tableau.html +7 -0
  105. package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/video_off.html +7 -0
  106. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/templates.js +5 -1
  107. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/data_model.html +7 -0
  108. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/serialized_product.html +1 -1
  109. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/serialized_product_transaction.html +2 -1
  110. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/slack.html +7 -0
  111. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/tableau.html +7 -0
  112. package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/video_off.html +7 -0
  113. package/src/lightning/input/__docs__/input.md +2 -0
  114. package/src/lightning/input/input.html +2 -5
  115. package/src/lightning/interactiveDialogBase/interactiveDialogBase.css +494 -0
  116. package/src/lightning/interactiveDialogBase/interactiveDialogBase.html +63 -0
  117. package/src/lightning/interactiveDialogBase/interactiveDialogBase.js +200 -0
  118. package/src/lightning/menuItem/menuItem.js +4 -1
  119. package/src/lightning/modalBase/modalBase.css +20 -0
  120. package/src/lightning/modalBase/modalBase.html +54 -0
  121. package/src/lightning/modalBase/modalBase.js +1039 -0
  122. package/src/lightning/overlay/__docs__/overlay.md +90 -0
  123. package/src/lightning/overlay/__examples__/alert/alert.html +27 -0
  124. package/src/lightning/overlay/__examples__/alert/alert.js +33 -0
  125. package/src/lightning/overlay/__examples__/basic/basic.css +7 -0
  126. package/src/lightning/overlay/__examples__/basic/basic.html +18 -0
  127. package/src/lightning/overlay/__examples__/basic/basic.js +61 -0
  128. package/src/lightning/overlay/__examples__/demo/demo.html +29 -0
  129. package/src/lightning/overlay/__examples__/demo/demo.js +40 -0
  130. package/src/lightning/overlay/__examples__/panel/panel.html +17 -0
  131. package/src/lightning/overlay/__examples__/panel/panel.js +21 -0
  132. package/src/lightning/overlay/overlay.html +3 -0
  133. package/src/lightning/overlay/overlay.js +45 -0
  134. package/src/lightning/overlayContainer/__docs__/overlayContainer.md +0 -0
  135. package/src/lightning/overlayContainer/overlayContainer.html +3 -0
  136. package/src/lightning/overlayContainer/overlayContainer.js +138 -0
  137. package/src/lightning/overlayManager/overlayManager.js +54 -0
  138. package/src/lightning/overlayUtils/overlayUtils.js +17 -0
  139. package/src/lightning/progressBar/progressBar.html +2 -1
  140. package/src/lightning/progressBar/progressBar.js +18 -1
  141. package/src/lightning/prompt/__docs__/prompt.md +102 -0
  142. package/src/lightning/prompt/__examples__disabled/basic/basic.css +7 -0
  143. package/src/lightning/prompt/__examples__disabled/basic/basic.html +8 -0
  144. package/src/lightning/prompt/__examples__disabled/basic/basic.js +15 -0
  145. package/src/lightning/prompt/prompt.css +81 -0
  146. package/src/lightning/prompt/prompt.html +8 -0
  147. package/src/lightning/prompt/prompt.js +92 -0
  148. package/src/lightning/prompt/prompt.js-meta.xml +6 -0
  149. package/src/lightning/spinner/spinner.html +1 -1
  150. package/src/lightning/spinner/spinner.js +12 -0
  151. package/src/lightning/utilsPrivate/phonify.js +1 -1
  152. package/scopedImports/@salesforce-label-LightningModalBase.close.js +0 -1
@@ -8,14 +8,16 @@ import {
8
8
  reactToTabBackward,
9
9
  reactToTabForward,
10
10
  getActiveCellElement,
11
- getCellElementByIndexes,
12
11
  updateActiveCell,
12
+ isActiveCellEditable,
13
+ isValidCell,
13
14
  } from './keyboard';
14
15
  import {
15
16
  updateRowsAndCellIndexes,
16
17
  getRowByKey,
17
18
  getKeyField,
18
19
  getUserRowByCellKeys,
20
+ isCellEditable,
19
21
  } from './rows';
20
22
  import {
21
23
  getColumnIndexByColumnKey,
@@ -315,6 +317,11 @@ function openInlineEdit(dt, target) {
315
317
 
316
318
  const { rowKeyValue, colKeyValue } = target;
317
319
 
320
+ // ensure that focus remains on inline edit panel instead of active cell
321
+ if (state.activeCell) {
322
+ state.activeCell.focused = false;
323
+ }
324
+
318
325
  inlineEdit.isPanelVisible = true;
319
326
  inlineEdit.rowKeyValue = rowKeyValue;
320
327
  inlineEdit.colKeyValue = colKeyValue;
@@ -378,11 +385,7 @@ function openInlineEdit(dt, target) {
378
385
  export function openInlineEditOnActiveCell(dt) {
379
386
  const hasData = dt.state.data && dt.state.data.length > 0;
380
387
  if (hasData) {
381
- const activeCellElement = getActiveCellElement(dt.template, dt.state);
382
- const isEditable = activeCellElement.editable;
383
- if (isEditable) {
384
- setFocusAndOpenInlineEdit(dt, activeCellElement);
385
- } else {
388
+ if (!isActiveCellEditable(dt.state)) {
386
389
  const firstEditableCell = getFirstEditableCell(dt);
387
390
  if (firstEditableCell) {
388
391
  updateActiveCell(
@@ -390,8 +393,10 @@ export function openInlineEditOnActiveCell(dt) {
390
393
  firstEditableCell.rowKeyValue,
391
394
  firstEditableCell.colKeyValue
392
395
  );
393
- setFocusAndOpenInlineEdit(dt, firstEditableCell);
396
+ setFocusAndOpenInlineEdit(dt, dt.state.activeCell);
394
397
  }
398
+ } else {
399
+ setFocusAndOpenInlineEdit(dt, dt.state.activeCell);
395
400
  }
396
401
  }
397
402
  }
@@ -400,11 +405,11 @@ export function openInlineEditOnActiveCell(dt) {
400
405
  * Async function to await setting focus on an editable cell before opening inline-edit panel
401
406
  *
402
407
  * @param {Object} dt - The datatable instance
403
- * @param {Object} cell - editable cell to be focused before open inline-edit panel
404
408
  */
405
409
  // eslint-disable-next-line @lwc/lwc/no-async-await
406
- async function setFocusAndOpenInlineEdit(dt, cell) {
410
+ async function setFocusAndOpenInlineEdit(dt) {
407
411
  await setFocusActiveCell(dt.template, dt.state, 0);
412
+ const cell = getActiveCellElement(dt.template, dt.state);
408
413
  openInlineEdit(dt, cell);
409
414
  }
410
415
 
@@ -716,8 +721,8 @@ function resolveNestedTypeAttributesHelper(rowData, typeAttributesValue) {
716
721
  /************************** HELPER FUNCTIONS **************************/
717
722
 
718
723
  /**
719
- * Returns a reference to the first editable cell in the table. If no editable cells exist in the table
720
- * then undefined is returned.
724
+ * Returns the row and column keys of the first editable cell in the table.
725
+ * If no editable cells exist in the table then undefined is returned.
721
726
  *
722
727
  * @param {Object} dt - The datatable instance. Must be a truthy and valid datatable reference.
723
728
  */
@@ -732,18 +737,11 @@ function getFirstEditableCell(dt) {
732
737
  // Loop through the editable columns in order and examine the corresponding cells
733
738
  // in the current row for editability, returning the first such cell that is editable
734
739
  const editableColumn = editableColumns[i];
735
- const editableColumnIndex = getStateColumnIndex(
736
- dt.state,
737
- editableColumn.colKeyValue
738
- );
739
- const cell = getCellElementByIndexes(
740
- dt.template,
741
- rowIndex,
742
- editableColumnIndex,
743
- dt.state
744
- );
745
- if (cell.editable) {
746
- return cell;
740
+ if (isCellEditable(rows[rowIndex], editableColumn)) {
741
+ return {
742
+ rowKeyValue: rows[rowIndex].key,
743
+ colKeyValue: editableColumn.colKeyValue,
744
+ };
747
745
  }
748
746
  }
749
747
  }
@@ -768,13 +766,6 @@ function getCellValue(state, rowKeyValue, colKeyValue) {
768
766
  return row.cells[colIndex].value;
769
767
  }
770
768
 
771
- function isValidCell(state, rowKeyValue, colKeyValue) {
772
- const row = getRowByKey(state, rowKeyValue);
773
- const colIndex = getStateColumnIndex(state, colKeyValue);
774
-
775
- return row && row.cells[colIndex];
776
- }
777
-
778
769
  /**
779
770
  * Sets `aria-selected` to true on cells whose rows are selected
780
771
  * and are in the same column as the cell being currently edited
@@ -1,16 +1,22 @@
1
- import { isCustomerColumn, generateColKeyValue } from './columns';
1
+ import {
2
+ isCustomerColumn,
3
+ generateColKeyValue,
4
+ getStateColumnIndex,
5
+ } from './columns';
2
6
  import {
3
7
  hasTreeDataType,
4
8
  getStateTreeColumn,
5
9
  fireRowToggleEvent,
6
10
  } from './tree';
11
+ import { isCellEditable, getRowByKey } from './rows';
7
12
  import { isRTL, getShadowActiveElements } from 'lightning/utilsPrivate';
13
+ import { findFirstVisibleIndex } from './virtualization';
8
14
 
9
15
  // Indicator/flag for a header row
10
16
  const HEADER_ROW = 'HEADER';
11
17
 
12
18
  // SLDS Class for Focus
13
- const FOCUS_CLASS = 'slds-has-focus';
19
+ export const FOCUS_CLASS = 'slds-has-focus';
14
20
 
15
21
  // Keyboard Navigation Modes
16
22
  const NAVIGATION_MODE = 'NAVIGATION';
@@ -82,7 +88,7 @@ const SELECTORS = {
82
88
  * user keys down on a cell that contains actionable items (ex. edit button, links,
83
89
  * email, buttons).
84
90
  *
85
- * @param {Event} event - Custom DOM event (privatecellkeydown) sent by the cell
91
+ * @param {Event} event Custom DOM event (privatecellkeydown) sent by the cell
86
92
  */
87
93
  export function handleKeydownOnCell(event) {
88
94
  event.stopPropagation();
@@ -104,7 +110,7 @@ export function handleKeydownOnCell(event) {
104
110
  * Those events are handled by `handleKeydownOnCell()` and the remaining are
105
111
  * handled by this function.
106
112
  *
107
- * @param {*} event
113
+ * @param {Event} event
108
114
  */
109
115
  export function handleKeydownOnTable(event) {
110
116
  const targetTagName = event.target.tagName.toLowerCase();
@@ -121,28 +127,28 @@ export function handleKeydownOnTable(event) {
121
127
  * Changes the datatable state based on the keyboard event sent from the cell component.
122
128
  * The result of those changes may trigger a re-render on the table
123
129
  *
124
- * @param {node} element - the custom element root `this.template`
125
- * @param {object} state - datatable state
126
- * @param {event} event - custom DOM event sent by the cell
127
- * @returns {object} - mutated state
130
+ * @param {Node} template The custom element root `this.template`
131
+ * @param {Object} state Datatable state
132
+ * @param {Event} event Custom DOM event sent by the cell
133
+ * @returns {Object} Mutated state
128
134
  */
129
- function reactToKeyboardInActionMode(element, state, event) {
135
+ function reactToKeyboardInActionMode(template, state, event) {
130
136
  switch (event.detail.keyCode) {
131
137
  case ARROW_LEFT:
132
- return reactToArrowLeft(element, state, event);
138
+ return reactToArrowLeft(template, state, event);
133
139
  case ARROW_RIGHT:
134
- return reactToArrowRight(element, state, event);
140
+ return reactToArrowRight(template, state, event);
135
141
  case ARROW_UP:
136
- return reactToArrowUp(element, state, event);
142
+ return reactToArrowUp(template, state, event);
137
143
  case ARROW_DOWN:
138
- return reactToArrowDown(element, state, event);
144
+ return reactToArrowDown(template, state, event);
139
145
  case ENTER:
140
146
  case SPACE:
141
- return reactToEnter(element, state, event);
147
+ return reactToEnter(template, state, event);
142
148
  case ESCAPE:
143
- return reactToEscape(element, state, event);
149
+ return reactToEscape(template, state, event);
144
150
  case TAB:
145
- return reactToTab(element, state, event);
151
+ return reactToTab(template, state, event);
146
152
  default:
147
153
  return state;
148
154
  }
@@ -642,12 +648,15 @@ function setDefaultActiveCell(state) {
642
648
  * Given a datatable template and state, returns an LWC component reference that represents
643
649
  * the currently active cell in the table.
644
650
  *
645
- * @param {Object} element - A reference to the datatable's template
651
+ * @param {Object} template - A reference to the datatable's template
646
652
  * @param {Object} state - A reference to the datatable's state
647
653
  */
648
- export function getActiveCellElement(element, state) {
649
- const { rowIndex, colIndex } = getIndexesActiveCell(state);
650
- return getCellElementByIndexes(element, rowIndex, colIndex, state);
654
+ export function getActiveCellElement(template, state) {
655
+ if (state.activeCell) {
656
+ const { rowKeyValue, colKeyValue } = state.activeCell;
657
+ return getCellElementByKeys(template, rowKeyValue, colKeyValue);
658
+ }
659
+ return null;
651
660
  }
652
661
 
653
662
  /**
@@ -805,45 +814,59 @@ function stillValidActiveCell(state) {
805
814
  * - update the tabindex of the activeCell
806
815
  * - set the current keyboard mode
807
816
  * - set the focus to the cell
808
- * @param {node} element - the custom element template `this.template`
817
+ * @param {node} template - the custom element template `this.template`
809
818
  * @param {object} state - datatable state
810
819
  * @param {int} direction - direction (-1 left, 1 right and 0 for no direction) its used to know which actionable element to activate.
811
- * @param {object} info - extra information when setting the cell mode.
820
+ * @param {object} info - extra information when setting the cell mode; currently only set when pressing tab
821
+ * @param {boolean} shouldScroll - true if scrollTop should be adjusted when setting focus
812
822
  */
813
- export function setFocusActiveCell(element, state, direction, info) {
823
+ export function setFocusActiveCell(
824
+ template,
825
+ state,
826
+ direction,
827
+ info,
828
+ shouldScroll = true
829
+ ) {
814
830
  const { keyboardMode } = state;
815
831
  const { rowIndex, colIndex } = getIndexesActiveCell(state);
816
832
 
833
+ // if pressing tab on editable cell, focus will go to inline edit panel instead of cell
834
+ state.activeCell.focused =
835
+ !(info && isActiveCellEditable(state)) && isActiveCellValid(state);
817
836
  updateTabIndex(state, rowIndex, colIndex);
837
+
838
+ let cellElement = getActiveCellElement(template, state);
839
+ // if the cell wasn't found, but does exist in the table, scroll to where it should be
840
+ if (!cellElement && isActiveCellValid(state) && shouldScroll) {
841
+ scrollToCell(state, template, rowIndex);
842
+ }
843
+
818
844
  return new Promise((resolve) => {
819
845
  // eslint-disable-next-line @lwc/lwc/no-async-operation
820
846
  setTimeout(() => {
821
- const cellElement = getCellElementByIndexes(
822
- element,
823
- rowIndex,
824
- colIndex,
825
- state
826
- );
847
+ // reset cell element if falsy or no longer valid
848
+ if (
849
+ !cellElement ||
850
+ !isValidCell(
851
+ state,
852
+ cellElement.rowKeyValue,
853
+ cellElement.colKeyValue
854
+ )
855
+ ) {
856
+ cellElement = getActiveCellElement(template, state);
857
+ }
827
858
  if (cellElement) {
828
859
  if (direction) {
829
860
  cellElement.resetCurrentInputIndex(direction, keyboardMode);
830
861
  }
831
862
  cellElement.addFocusStyles();
832
863
  cellElement.parentElement.classList.add(FOCUS_CLASS);
833
- cellElement.parentElement.focus();
864
+ cellElement.parentElement.focus({
865
+ preventScroll: !shouldScroll,
866
+ });
834
867
  cellElement.setMode(keyboardMode, info);
835
-
836
- const scrollableY = element.querySelector('.slds-scrollable_y');
837
- const scrollingParent = scrollableY.parentElement;
838
- const parentRect = scrollingParent.getBoundingClientRect();
839
- const findMeRect = cellElement.getBoundingClientRect();
840
- if (findMeRect.top < parentRect.top + TOP_MARGIN) {
841
- scrollableY.scrollTop -= SCROLL_OFFSET;
842
- } else if (
843
- findMeRect.bottom >
844
- parentRect.bottom - BOTTOM_MARGIN
845
- ) {
846
- scrollableY.scrollTop += SCROLL_OFFSET;
868
+ if (shouldScroll) {
869
+ updateScrollTop(state, template, cellElement);
847
870
  }
848
871
  }
849
872
  resolve();
@@ -855,22 +878,27 @@ export function setFocusActiveCell(element, state, direction, info) {
855
878
  * It blur to the current activeCell, this operation imply multiple changes
856
879
  * - blur the activeCell
857
880
  * - update the tabindex to -1
858
- * @param {node} element - the custom element root `this.template`
881
+ * @param {node} template - the custom element root `this.template`
859
882
  * @param {object} state - datatable state
860
883
  */
861
- export function setBlurActiveCell(element, state) {
884
+ export function setBlurActiveCell(template, state) {
862
885
  if (state.activeCell) {
863
886
  const { rowIndex, colIndex } = getIndexesActiveCell(state);
887
+ let cellElement = getActiveCellElement(template, state);
888
+ state.activeCell.focused = false;
864
889
  // eslint-disable-next-line @lwc/lwc/no-async-operation
865
890
  setTimeout(() => {
866
- const cellElement = getCellElementByIndexes(
867
- element,
868
- rowIndex,
869
- colIndex,
870
- state
871
- );
872
- // we need to check because of the tree,
873
- // at this point it may remove/change the rows/keys because opening or closing a row.
891
+ // check cellElement; value may have changed
892
+ if (
893
+ !cellElement ||
894
+ !isValidCell(
895
+ state,
896
+ cellElement.rowKeyValue,
897
+ cellElement.colKeyValue
898
+ )
899
+ ) {
900
+ cellElement = getActiveCellElement(template, state);
901
+ }
874
902
  if (cellElement) {
875
903
  if (document.activeElement === cellElement) {
876
904
  cellElement.blur();
@@ -940,18 +968,12 @@ export function resetCellToFocusFromPrev(state) {
940
968
  /**
941
969
  * It adds and the focus classes to the th/td or div[role=gridcell/rowheader].
942
970
  *
943
- * @param {node} element - the custom element template `this.template`
971
+ * @param {node} template - the custom element template `this.template`
944
972
  * @param {object} state - datatable state
945
973
  */
946
- export function addFocusStylesToActiveCell(element, state) {
947
- const { rowIndex, colIndex } = getIndexesActiveCell(state);
948
-
949
- const cellElement = getCellElementByIndexes(
950
- element,
951
- rowIndex,
952
- colIndex,
953
- state
954
- );
974
+ export function addFocusStylesToActiveCell(template, state) {
975
+ const cellElement = getActiveCellElement(template, state);
976
+ state.activeCell.focused = true;
955
977
 
956
978
  if (cellElement) {
957
979
  cellElement.parentElement.classList.add(FOCUS_CLASS);
@@ -959,31 +981,22 @@ export function addFocusStylesToActiveCell(element, state) {
959
981
  }
960
982
 
961
983
  /**
962
- * It set the focus to the current activeCell, this operation imply multiple changes
984
+ * It set the focus to the row of current activeCell, this operation implies multiple changes
963
985
  * - update the tabindex of the activeCell
964
986
  * - set the current keyboard mode
965
- * - set the focus to the cell
966
- * @param {node} element - the custom element root `this.template`
987
+ * - set the focus to the row
988
+ * @param {node} template - the custom element root `this.template`
967
989
  * @param {object} state - datatable state
968
990
  */
969
- function setFocusActiveRow(element, state) {
991
+ function setFocusActiveRow(template, state) {
970
992
  const { rowIndex } = getIndexesActiveCell(state);
993
+ const row = getActiveCellRow(template, state);
971
994
 
972
995
  updateTabIndexRow(state, rowIndex);
973
996
  // eslint-disable-next-line @lwc/lwc/no-async-operation
974
997
  setTimeout(() => {
975
- const row = getRowElementByIndexes(element, rowIndex, state);
976
- row.focus();
977
-
978
- const scrollableY = element.querySelector('.slds-scrollable_y');
979
- const scrollingParent = scrollableY.parentElement;
980
- const parentRect = scrollingParent.getBoundingClientRect();
981
- const findMeRect = row.getBoundingClientRect();
982
- if (findMeRect.top < parentRect.top + TOP_MARGIN) {
983
- scrollableY.scrollTop -= SCROLL_OFFSET;
984
- } else if (findMeRect.bottom > parentRect.bottom - BOTTOM_MARGIN) {
985
- scrollableY.scrollTop += SCROLL_OFFSET;
986
- }
998
+ row.focus({ preventScroll: true });
999
+ updateScrollTop(state, template, row);
987
1000
  }, 0);
988
1001
  }
989
1002
 
@@ -991,15 +1004,15 @@ function setFocusActiveRow(element, state) {
991
1004
  * It blurs the active row, this operation implies multiple changes
992
1005
  * - blur the active row
993
1006
  * - update the tabindex to -1
994
- * @param {node} element - the custom element root `this.template`
1007
+ * @param {node} template - the custom element root `this.template`
995
1008
  * @param {object} state - datatable state
996
1009
  */
997
- function setBlurActiveRow(element, state) {
1010
+ function setBlurActiveRow(template, state) {
998
1011
  if (state.activeCell) {
999
1012
  const { rowIndex } = getIndexesActiveCell(state);
1000
1013
  // eslint-disable-next-line @lwc/lwc/no-async-operation
1001
1014
  setTimeout(() => {
1002
- const row = getRowElementByIndexes(element, rowIndex, state);
1015
+ const row = getActiveCellRow(template, state);
1003
1016
  if (document.activeElement === row) {
1004
1017
  row.blur();
1005
1018
  }
@@ -1017,13 +1030,7 @@ function setBlurActiveRow(element, state) {
1017
1030
  */
1018
1031
  export function refocusCellElement(template, state, needsRefocusOnCellElement) {
1019
1032
  if (needsRefocusOnCellElement) {
1020
- const { rowIndex, colIndex } = getIndexesActiveCell(state);
1021
- const cellElement = getCellElementByIndexes(
1022
- template,
1023
- rowIndex,
1024
- colIndex,
1025
- state
1026
- );
1033
+ const cellElement = getActiveCellElement(template, state);
1027
1034
  if (cellElement) {
1028
1035
  cellElement.parentElement.focus();
1029
1036
  }
@@ -1056,13 +1063,8 @@ export function handleDatatableFocusIn(event) {
1056
1063
  // workaround for delegatesFocus issue that focusin is called when not supposed to W-6220418
1057
1064
  if (isFocusInside(event.currentTarget)) {
1058
1065
  if (!state.rowMode && state.activeCell) {
1059
- const { rowIndex, colIndex } = getIndexesActiveCell(state);
1060
- const cellElement = getCellElementByIndexes(
1061
- this.template,
1062
- rowIndex,
1063
- colIndex,
1064
- state
1065
- );
1066
+ state.activeCell.focused = true;
1067
+ const cellElement = getActiveCellElement(this.template, state);
1066
1068
  // we need to check because of the tree,
1067
1069
  // at this point it may remove/change the rows/keys because opening or closing a row.
1068
1070
  if (cellElement) {
@@ -1090,13 +1092,7 @@ export function handleDatatableFocusOut(event) {
1090
1092
  state.isExitingActionMode)
1091
1093
  ) {
1092
1094
  if (state.activeCell && !state.rowMode) {
1093
- const { rowIndex, colIndex } = getIndexesActiveCell(state);
1094
- const cellElement = getCellElementByIndexes(
1095
- this.template,
1096
- rowIndex,
1097
- colIndex,
1098
- state
1099
- );
1095
+ const cellElement = getActiveCellElement(this.template, state);
1100
1096
  // we need to check because of the tree,
1101
1097
  // at this point it may remove/change the rows/keys because opening or closing a row.
1102
1098
  if (cellElement) {
@@ -1317,40 +1313,24 @@ function isHeaderRow(rowIndex) {
1317
1313
  return rowIndex === -1;
1318
1314
  }
1319
1315
 
1320
- function getHeaderRow(isRenderModeRoleBased) {
1321
- const selectors = SELECTORS.headerRow;
1322
- return isRenderModeRoleBased ? selectors.roleBased : selectors.default;
1323
- }
1324
-
1325
- function getDataRow(rowIndex, isRenderModeRoleBased) {
1326
- const dataRowRowGroupSelector = isRenderModeRoleBased
1327
- ? SELECTORS.dataRowRowGroup.roleBased
1328
- : SELECTORS.dataRowRowGroup.default;
1329
- return `${dataRowRowGroupSelector} > :nth-child(${rowIndex + 1})`;
1316
+ export function getDataRow(rowKeyValue) {
1317
+ return `[data-row-key-value="${rowKeyValue}"]`;
1330
1318
  }
1331
1319
 
1332
- export function getCellElementByIndexes(element, rowIndex, colIndex, state) {
1333
- const isRenderModeRoleBased = state.renderModeRoleBased;
1334
- let selector = '';
1335
-
1336
- if (isHeaderRow(rowIndex)) {
1337
- selector = `${getHeaderRow(isRenderModeRoleBased)}
1338
- > :nth-child(${colIndex + 1}) > :first-child`;
1339
- return element.querySelector(selector);
1340
- }
1341
-
1342
- selector = `${getDataRow(rowIndex, isRenderModeRoleBased)}
1343
- > :nth-child(${colIndex + 1}) > :first-child`;
1344
- return element.querySelector(selector);
1320
+ export function getCellElementByKeys(template, rowKeyValue, colKeyValue) {
1321
+ const selector = `${getDataRow(
1322
+ rowKeyValue
1323
+ )} [data-col-key-value="${colKeyValue}"] > :first-child`;
1324
+ return template.querySelector(selector);
1345
1325
  }
1346
1326
 
1347
- function getRowElementByIndexes(element, rowIndex, state) {
1348
- const isRenderModeRoleBased = state.renderModeRoleBased;
1349
- if (isHeaderRow(rowIndex)) {
1350
- return element.querySelector(getHeaderRow(isRenderModeRoleBased));
1327
+ function getActiveCellRow(template, state) {
1328
+ if (state.activeCell) {
1329
+ const { rowKeyValue } = state.activeCell;
1330
+ const selector = getDataRow(rowKeyValue);
1331
+ return template.querySelector(selector);
1351
1332
  }
1352
-
1353
- return element.querySelector(getDataRow(rowIndex, isRenderModeRoleBased));
1333
+ return null;
1354
1334
  }
1355
1335
 
1356
1336
  export function getRowParent(state, rowLevel, rowIndex) {
@@ -1374,3 +1354,58 @@ function getCellFromIndexes(state, rowIndex, colIndex) {
1374
1354
  }
1375
1355
  return undefined;
1376
1356
  }
1357
+
1358
+ function updateScrollTop(state, template, element) {
1359
+ const scrollableY = template.querySelector('.slds-scrollable_y');
1360
+ const scrollingParent = scrollableY.parentElement;
1361
+ const parentRect = scrollingParent.getBoundingClientRect();
1362
+ const findMeRect = element.getBoundingClientRect();
1363
+ if (findMeRect.top < parentRect.top + TOP_MARGIN) {
1364
+ scrollableY.scrollTop -= SCROLL_OFFSET;
1365
+ } else if (findMeRect.bottom > parentRect.bottom - BOTTOM_MARGIN) {
1366
+ scrollableY.scrollTop += SCROLL_OFFSET;
1367
+ }
1368
+ findFirstVisibleIndex(state, scrollableY.scrollTop);
1369
+ }
1370
+
1371
+ function scrollToCell(state, template, rowIndex) {
1372
+ const { firstVisibleIndex, bufferSize, renderedRowCount, rowHeight } =
1373
+ state;
1374
+
1375
+ let scrollTop = rowIndex * rowHeight;
1376
+ if (firstVisibleIndex > rowIndex) {
1377
+ const rowsInViewport = renderedRowCount - 2 * bufferSize;
1378
+ scrollTop = Math.max(scrollTop - rowsInViewport * rowHeight, 0);
1379
+ }
1380
+
1381
+ const scrollableY = template.querySelector('.slds-scrollable_y');
1382
+ scrollableY.scrollTop = scrollTop;
1383
+ findFirstVisibleIndex(state, scrollTop);
1384
+ }
1385
+
1386
+ export function isActiveCellEditable(state) {
1387
+ const { activeCell, rows, columns } = state;
1388
+ if (activeCell) {
1389
+ const { rowIndex, colIndex } = getIndexesActiveCell(state);
1390
+ return isCellEditable(rows[rowIndex], columns[colIndex]);
1391
+ }
1392
+ return false;
1393
+ }
1394
+
1395
+ export function isValidCell(state, rowKeyValue, colKeyValue) {
1396
+ if (rowKeyValue === HEADER_ROW) {
1397
+ return state.headerIndexes[colKeyValue] !== undefined;
1398
+ }
1399
+ const row = getRowByKey(state, rowKeyValue);
1400
+ const colIndex = getStateColumnIndex(state, colKeyValue);
1401
+
1402
+ return row && row.cells[colIndex];
1403
+ }
1404
+
1405
+ function isActiveCellValid(state) {
1406
+ if (state.activeCell) {
1407
+ const { rowKeyValue, colKeyValue } = state.activeCell;
1408
+ return isValidCell(state, rowKeyValue, colKeyValue);
1409
+ }
1410
+ return false;
1411
+ }