lightning-base-components 1.14.6-alpha → 1.15.2-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 (169) hide show
  1. package/metadata/raptor.json +31 -4
  2. package/package.json +13 -2
  3. package/scopedImports/@salesforce-internal-core.appVersion.js +1 -1
  4. package/scopedImports/@salesforce-label-LightningMap.defaultTitle.js +1 -0
  5. package/scopedImports/@salesforce-label-LightningProgressBar.progressBar.js +1 -0
  6. package/src/lightning/alert/__docs__/alert.md +99 -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/baseCombobox/baseCombobox.html +2 -1
  14. package/src/lightning/baseCombobox/baseCombobox.js +41 -6
  15. package/src/lightning/button/__wdio__/utam/utam.html +3 -0
  16. package/src/lightning/button/__wdio__/utam/utam.js +3 -0
  17. package/src/lightning/button/__wdio__/utam/utam.spec.js +20 -0
  18. package/src/lightning/button/button.js +22 -1
  19. package/src/lightning/buttonMenu/buttonMenu.js +12 -0
  20. package/src/lightning/checkboxGroup/checkboxGroup.html +2 -2
  21. package/src/lightning/checkboxGroup/checkboxGroup.js +9 -5
  22. package/src/lightning/combobox/__docs__/combobox.md +3 -1
  23. package/src/lightning/combobox/combobox.js +0 -1
  24. package/src/lightning/confirm/__docs__/confirm.md +98 -0
  25. package/src/lightning/confirm/__examples__disabled/basic/basic.css +7 -0
  26. package/src/lightning/confirm/__examples__disabled/basic/basic.html +8 -0
  27. package/src/lightning/confirm/__examples__disabled/basic/basic.js +14 -0
  28. package/src/lightning/confirm/confirm.html +3 -0
  29. package/src/lightning/confirm/confirm.js +80 -0
  30. package/src/lightning/confirm/confirm.js-meta.xml +6 -0
  31. package/src/lightning/datatable/__docs__/datatable.md +45 -0
  32. package/src/lightning/datatable/__wdio__/utam/utam.html +32 -0
  33. package/src/lightning/datatable/__wdio__/utam/utam.js +91 -0
  34. package/src/lightning/datatable/__wdio__/utam/utam.spec.js +214 -0
  35. package/src/lightning/datatable/columns.js +166 -71
  36. package/src/lightning/datatable/datatable.js +103 -20
  37. package/src/lightning/datatable/headerActions.js +2 -2
  38. package/src/lightning/datatable/inlineEdit.js +0 -5
  39. package/src/lightning/datatable/inlineEditShared.js +4 -2
  40. package/src/lightning/datatable/keyboard.js +17 -13
  41. package/src/lightning/datatable/renderManager.js +45 -13
  42. package/src/lightning/datatable/resizeSensor.js +11 -3
  43. package/src/lightning/datatable/rowSelection.js +9 -3
  44. package/src/lightning/datatable/rowSelectionShared.js +33 -20
  45. package/src/lightning/datatable/rows.js +3 -2
  46. package/src/lightning/datatable/sort.js +8 -8
  47. package/src/lightning/datatable/state.js +9 -1
  48. package/src/lightning/datatable/templates/div/div.html +6 -2
  49. package/src/lightning/datatable/templates/table/table.html +7 -4
  50. package/src/lightning/datatable/tree.js +25 -0
  51. package/src/lightning/datatable/types.js +77 -9
  52. package/src/lightning/datatable/utils.js +51 -24
  53. package/src/lightning/datatable/virtualization.js +319 -0
  54. package/src/lightning/datatable/wrapText.js +54 -16
  55. package/src/lightning/datepicker/__perf__DISABLED/datepickerWithCalendarOpen.perf.js +55 -0
  56. package/src/lightning/datepicker/datepicker.html +1 -0
  57. package/src/lightning/datepicker/datepicker.js +10 -0
  58. package/src/lightning/datetimepicker/datetimepicker.html +2 -0
  59. package/src/lightning/datetimepicker/datetimepicker.js +8 -0
  60. package/src/lightning/dualListbox/dualListbox.js +2 -1
  61. package/src/lightning/formattedAddress/__docs__/formattedAddress.md +3 -0
  62. package/src/lightning/formattedAddress/__examples__/customLocale/customLocale.html +22 -0
  63. package/src/lightning/formattedAddress/__examples__/customLocale/customLocale.js +3 -0
  64. package/src/lightning/formattedAddress/formattedAddress.js +7 -1
  65. package/src/lightning/groupedCombobox/groupedCombobox.html +2 -1
  66. package/src/lightning/groupedCombobox/groupedCombobox.js +16 -2
  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 +6 -0
  115. package/src/lightning/input/input.js +2 -1
  116. package/src/lightning/inputAddress/__docs__/inputAddress.md +5 -0
  117. package/src/lightning/inputAddress/__examples__/customLocale/customLocale.html +12 -0
  118. package/src/lightning/inputAddress/__examples__/customLocale/customLocale.js +3 -0
  119. package/src/lightning/inputAddress/inputAddress.html +2 -0
  120. package/src/lightning/inputAddress/inputAddress.js +26 -3
  121. package/src/lightning/inputName/__docs__/inputName.md +2 -0
  122. package/src/lightning/inputName/inputName.html +4 -1
  123. package/src/lightning/inputUtils/inputUtils.js +11 -0
  124. package/src/lightning/interactiveDialogBase/interactiveDialogBase.css +494 -0
  125. package/src/lightning/interactiveDialogBase/interactiveDialogBase.html +63 -0
  126. package/src/lightning/interactiveDialogBase/interactiveDialogBase.js +200 -0
  127. package/src/lightning/menuItem/menuItem.js +4 -1
  128. package/src/lightning/modalBase/modalBase.css +20 -0
  129. package/src/lightning/modalBase/modalBase.html +54 -0
  130. package/src/lightning/modalBase/modalBase.js +1039 -0
  131. package/src/lightning/overlay/__docs__/overlay.md +90 -0
  132. package/src/lightning/overlay/__examples__/alert/alert.html +27 -0
  133. package/src/lightning/overlay/__examples__/alert/alert.js +33 -0
  134. package/src/lightning/overlay/__examples__/basic/basic.css +7 -0
  135. package/src/lightning/overlay/__examples__/basic/basic.html +18 -0
  136. package/src/lightning/overlay/__examples__/basic/basic.js +61 -0
  137. package/src/lightning/overlay/__examples__/demo/demo.html +29 -0
  138. package/src/lightning/overlay/__examples__/demo/demo.js +40 -0
  139. package/src/lightning/overlay/__examples__/panel/panel.html +17 -0
  140. package/src/lightning/overlay/__examples__/panel/panel.js +21 -0
  141. package/src/lightning/overlay/overlay.html +3 -0
  142. package/src/lightning/overlay/overlay.js +45 -0
  143. package/src/lightning/overlayContainer/__docs__/overlayContainer.md +0 -0
  144. package/src/lightning/overlayContainer/overlayContainer.html +3 -0
  145. package/src/lightning/overlayContainer/overlayContainer.js +138 -0
  146. package/src/lightning/overlayManager/overlayManager.js +54 -0
  147. package/src/lightning/overlayUtils/overlayUtils.js +17 -0
  148. package/src/lightning/picklist/picklist.js +6 -1
  149. package/src/lightning/progressBar/progressBar.html +2 -1
  150. package/src/lightning/progressBar/progressBar.js +18 -1
  151. package/src/lightning/prompt/__docs__/prompt.md +100 -0
  152. package/src/lightning/prompt/__examples__disabled/basic/basic.css +7 -0
  153. package/src/lightning/prompt/__examples__disabled/basic/basic.html +8 -0
  154. package/src/lightning/prompt/__examples__disabled/basic/basic.js +15 -0
  155. package/src/lightning/prompt/prompt.css +81 -0
  156. package/src/lightning/prompt/prompt.html +8 -0
  157. package/src/lightning/prompt/prompt.js +92 -0
  158. package/src/lightning/prompt/prompt.js-meta.xml +6 -0
  159. package/src/lightning/radioGroup/radioGroup.js +9 -0
  160. package/src/lightning/select/select.html +3 -1
  161. package/src/lightning/select/select.js +5 -1
  162. package/src/lightning/textarea/textarea.html +1 -0
  163. package/src/lightning/textarea/textarea.js +5 -0
  164. package/src/lightning/timepicker/timepicker.html +3 -1
  165. package/src/lightning/timepicker/timepicker.js +8 -0
  166. package/src/lightning/utilsPrivate/aria.js +26 -0
  167. package/src/lightning/utilsPrivate/linkify.js +1 -1
  168. package/src/lightning/utilsPrivate/utilsPrivate.js +7 -1
  169. package/src/lightning/icon/__component__/icon-spirite.spec.js +0 -59
@@ -82,6 +82,7 @@ import {
82
82
  refocusCellElement,
83
83
  isCellElement,
84
84
  getActiveCellElement,
85
+ getDataRow,
85
86
  FOCUS_CLASS,
86
87
  } from './keyboard';
87
88
  import {
@@ -138,10 +139,15 @@ import {
138
139
  isViewportRenderingEnabled,
139
140
  setViewportRendering,
140
141
  getDTWrapperHeight,
141
- setFirstVisibleIndex,
142
142
  setVirtualize,
143
143
  RenderManager,
144
144
  } from './renderManager';
145
+ import {
146
+ handleVariableRowHeights,
147
+ resetRowHeights,
148
+ resetTableHeight,
149
+ findFirstVisibleIndex,
150
+ } from './virtualization';
145
151
 
146
152
  import { hasTreeDataType } from './tree';
147
153
  import { setErrors, getTableError, getErrors } from './errors';
@@ -200,9 +206,11 @@ export default class LightningDatatable extends LightningElement {
200
206
  _datatableId = generateUniqueId('lgt-datatable');
201
207
  _draftValues = [];
202
208
  _isResizing = false; // Whether resizing is in progress
209
+ _lastRenderedRow = null; // last rendered row, used for UTAM
203
210
  _privateTypes = {};
204
211
  _privateWidthObserver = null; // Instance of LightningDatatableResizeObserver
205
212
  _renderMode = 'table';
213
+ _shouldResetFocus = false; // used to ensure focus isn't lost from changes in renderedRows
206
214
  _suppressBottomBar = false;
207
215
 
208
216
  /************************* PUBLIC PROPERTIES *************************/
@@ -283,6 +291,7 @@ export default class LightningDatatable extends LightningElement {
283
291
  // do necessary updates since rows have changed
284
292
  if (hasValidKeyField(this.state)) {
285
293
  this.updateRowsState();
294
+ resetTableHeight(this.state);
286
295
  }
287
296
  if (this._customerSelectedRows) {
288
297
  this.setSelectedRows(this._customerSelectedRows);
@@ -504,8 +513,8 @@ export default class LightningDatatable extends LightningElement {
504
513
  */
505
514
 
506
515
  /**
507
- * Reserved for internal use.
508
516
  * Enables and configures advanced rendering modes.
517
+ * It supports properties 'bufferSize', 'fixedHeight', 'rowHeight', and 'virtualize'.
509
518
  *
510
519
  * @type {RenderManagerConfig} value - config object for datatable rendering
511
520
  */
@@ -542,7 +551,8 @@ export default class LightningDatatable extends LightningElement {
542
551
  * `default` -> Renders <table>
543
552
  */
544
553
  /**
545
- * Reserved for internal use.
554
+ * Enables virtual rendering when set to 'role-based'.
555
+ * Default value is 'default'.
546
556
  */
547
557
  @api
548
558
  get renderMode() {
@@ -797,8 +807,8 @@ export default class LightningDatatable extends LightningElement {
797
807
  * virtualization is enabled
798
808
  */
799
809
  get computedTbodyStyle() {
800
- const style = [];
801
- const { firstVisibleIndex, bufferSize, virtualize, rows, rowHeight } =
810
+ const style = {};
811
+ const { firstVisibleIndex, bufferSize, virtualize, tableHeight } =
802
812
  this.state;
803
813
  if (
804
814
  hasRowNumberColumn(this.state) &&
@@ -809,11 +819,11 @@ export default class LightningDatatable extends LightningElement {
809
819
  0
810
820
  );
811
821
  const rowNumber = firstRenderedRow + getRowNumberOffset(this.state);
812
- style.push(`counter-reset: row-number ${rowNumber}`);
822
+ style['counter-reset'] = `row-number ${rowNumber}`;
813
823
  }
814
824
  if (virtualize) {
815
- const length = rows.length;
816
- style.push('position: relative', `height:${length * rowHeight}px`);
825
+ style.position = 'relative';
826
+ style.height = `${tableHeight}px`;
817
827
  }
818
828
  return styleToString(style);
819
829
  }
@@ -938,11 +948,16 @@ export default class LightningDatatable extends LightningElement {
938
948
  if (virtualize) {
939
949
  const { firstIndex, lastIndex } =
940
950
  this._renderManager.getRenderedRange(this.state);
951
+ this._lastRenderedRow = lastIndex + 1; // UTAM rows are 1-indexed
952
+ // we shouldn't lose focus from re-renders caused by a change in renderedRows
953
+ this._shouldResetFocus = true;
941
954
  return rows.slice(firstIndex, lastIndex);
942
955
  }
943
956
  if (this.viewportRendering && !isIE11) {
957
+ this._lastRenderedRow = renderedRowCount;
944
958
  return rows.slice(0, renderedRowCount);
945
959
  }
960
+ this._lastRenderedRow = rows.length;
946
961
  return rows;
947
962
  }
948
963
 
@@ -1124,10 +1139,7 @@ export default class LightningDatatable extends LightningElement {
1124
1139
  if (fireResizeEvent) {
1125
1140
  this.fireOnResize(false);
1126
1141
  }
1127
-
1128
- const role = '[role="' + this.computedTableRole + '"]';
1129
-
1130
- this.template.querySelector(role).style = this.computedTableStyle;
1142
+ this.updateTableAndScrollerStyleOnRender();
1131
1143
  }
1132
1144
 
1133
1145
  // Managing the cell widths is only required for the role-based table
@@ -1142,17 +1154,28 @@ export default class LightningDatatable extends LightningElement {
1142
1154
  // set the previous focused cell to null after render is done
1143
1155
  resetCellToFocusFromPrev(state);
1144
1156
  // reset focus styles on re-render
1145
- if (state.activeCell && state.activeCell.focused) {
1146
- const cellElement = getActiveCellElement(template, state);
1157
+ if (this._shouldResetFocus) {
1158
+ // since focus is now getting reset, can change this back to false
1159
+ this._shouldResetFocus = false;
1160
+ // don't return focus to active cell when inline edit panel is open
1147
1161
  if (
1148
- cellElement &&
1149
- cellElement.parentElement &&
1150
- !cellElement.parentElement.classList.contains(FOCUS_CLASS)
1162
+ state.activeCell &&
1163
+ state.activeCell.focused &&
1164
+ !state.inlineEdit.isPanelVisible
1151
1165
  ) {
1152
- setFocusActiveCell(template, state, null, null, false);
1166
+ const cellElement = getActiveCellElement(template, state);
1167
+ if (
1168
+ cellElement &&
1169
+ cellElement.parentElement &&
1170
+ !cellElement.parentElement.classList.contains(FOCUS_CLASS)
1171
+ ) {
1172
+ setFocusActiveCell(template, state, null, null, false);
1173
+ }
1153
1174
  }
1154
1175
  }
1155
1176
 
1177
+ this.updateVirtualizedRowHeights();
1178
+
1156
1179
  if (this.viewportRendering || state.virtualize) {
1157
1180
  const resizeTarget = this.template.querySelector(
1158
1181
  'div.dt-outer-container'
@@ -1175,6 +1198,18 @@ export default class LightningDatatable extends LightningElement {
1175
1198
  }
1176
1199
  }
1177
1200
 
1201
+ updateTableAndScrollerStyleOnRender() {
1202
+ const role = '[role="' + this.computedTableRole + '"]';
1203
+ const tableElement = this.template.querySelector(role);
1204
+ const scrollYEle = this.template.querySelector('.slds-scrollable_y');
1205
+ if (tableElement) {
1206
+ tableElement.style = this.computedTableStyle;
1207
+ }
1208
+ if (scrollYEle) {
1209
+ scrollYEle.style = this.computedScrollerStyle;
1210
+ }
1211
+ }
1212
+
1178
1213
  disconnectedCallback() {
1179
1214
  if (this._privateWidthObserver) {
1180
1215
  this._privateWidthObserver.disconnect();
@@ -1253,7 +1288,10 @@ export default class LightningDatatable extends LightningElement {
1253
1288
 
1254
1289
  handleInlineEditPanelScroll.call(this, event);
1255
1290
  if (this.state.virtualize) {
1256
- setFirstVisibleIndex(this.state, event.target.scrollTop);
1291
+ this.state.firstVisibleIndex = findFirstVisibleIndex(
1292
+ this.state,
1293
+ event.target.scrollTop
1294
+ );
1257
1295
  } else if (this.viewportRendering) {
1258
1296
  this._renderManager.handleScroll(this.state, event);
1259
1297
  }
@@ -1347,6 +1385,7 @@ export default class LightningDatatable extends LightningElement {
1347
1385
  handleResizeEnd(event) {
1348
1386
  event.stopPropagation();
1349
1387
  this._isResizing = false;
1388
+ this.state.shouldResetHeights = true;
1350
1389
  }
1351
1390
 
1352
1391
  /**
@@ -1419,10 +1458,19 @@ export default class LightningDatatable extends LightningElement {
1419
1458
  * Handles the `focusout` event on <table> and the corresponding
1420
1459
  * <div> on the role-based table
1421
1460
  *
1461
+ * This gets called both when we expect the table to lose focus
1462
+ * and when the active cell loses focus after renderedRows changes
1463
+ * on a virtualized table, in which case we don't want to lose focus.
1464
+ *
1465
+ * We account for this by setting activeCell.focused to the value of
1466
+ * _shouldResetFocus, which will be true if and only if focus was
1467
+ * lost due to a renderedRows change for a virtualized table.
1468
+ *
1422
1469
  * @param {FocusEvent} event - `focusout`
1423
1470
  */
1424
1471
  handleTableFocusOut(event) {
1425
1472
  handleDatatableFocusOut.call(this, event);
1473
+ this.state.activeCell.focused = this._shouldResetFocus;
1426
1474
  }
1427
1475
 
1428
1476
  /**
@@ -1601,6 +1649,41 @@ export default class LightningDatatable extends LightningElement {
1601
1649
  }
1602
1650
  }
1603
1651
 
1652
+ updateVirtualizedRowHeights() {
1653
+ const state = this.state;
1654
+ const virtualizedRows = state.virtualize && this.renderedRows.length;
1655
+
1656
+ // no need to handle other virtualization/row height logic
1657
+ // if heights need to be reset
1658
+ if (this.state.shouldResetHeights) {
1659
+ resetRowHeights(state);
1660
+ this.state.shouldResetHeights = false;
1661
+ } else if (virtualizedRows && !state.fixedHeight) {
1662
+ // if row heights aren't fixed, we need to update items
1663
+ // in state to know where rows should be positioned
1664
+ handleVariableRowHeights(this.template, state, this.renderedRows);
1665
+ } else if (virtualizedRows && state.fixedHeight) {
1666
+ // if heights are fixed, we only need to check height of first row
1667
+ const rowElement = this.template.querySelector(
1668
+ getDataRow(this.renderedRows[0].key)
1669
+ );
1670
+ // increase height by 1 since first rendered row is missing an extra 1px border
1671
+ if (rowElement) {
1672
+ const height = rowElement.getBoundingClientRect().height + 1;
1673
+ if (state.rowHeight !== height) {
1674
+ state.rowHeight = height;
1675
+ resetTableHeight(state);
1676
+ state.rows.forEach((row) => {
1677
+ row.style = styleToString({
1678
+ position: 'absolute',
1679
+ top: `${row.rowIndex * height}px`,
1680
+ });
1681
+ });
1682
+ }
1683
+ }
1684
+ }
1685
+ }
1686
+
1604
1687
  setSelectedRows(value) {
1605
1688
  setSelectedRowsKeys(this.state, value);
1606
1689
  handleRowSelectionChange.call(this);
@@ -1620,7 +1703,7 @@ export default class LightningDatatable extends LightningElement {
1620
1703
  }
1621
1704
 
1622
1705
  /**
1623
- * @return {Object} containing the visible dimensions of the table { left, right, top, bottom, }
1706
+ * @returns {Object} containing the visible dimensions of the table { left, right, top, bottom, }
1624
1707
  */
1625
1708
  getViewableRect() {
1626
1709
  const scrollerX = this.template
@@ -16,7 +16,7 @@ const DIVIDER_REM_HEIGHT = 1.0625;
16
16
  *
17
17
  * @param {Object} state The state of the datatable
18
18
  * @param {Object} columnDefinition The column definition to extract internal actions from
19
- * @return {Array} All wrapText internal actions
19
+ * @returns {Array} All wrapText internal actions
20
20
  */
21
21
  export function getInternalActions(state, columnDefinition) {
22
22
  return [...getActions(state, columnDefinition)];
@@ -113,7 +113,7 @@ function dispatchHeaderActionEvent(dt, action, colKeyValue) {
113
113
  *
114
114
  * @param {Array} columns Array of all the columns
115
115
  * @param {Integer} index The current column index to check
116
- * @return {String} The computed alignment
116
+ * @returns {String} The computed alignment
117
117
  */
118
118
  function getMenuAlignment(columns, index) {
119
119
  const isLastColumn = index === columns.length - 1;
@@ -317,11 +317,6 @@ function openInlineEdit(dt, target) {
317
317
 
318
318
  const { rowKeyValue, colKeyValue } = target;
319
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
-
325
320
  inlineEdit.isPanelVisible = true;
326
321
  inlineEdit.rowKeyValue = rowKeyValue;
327
322
  inlineEdit.colKeyValue = colKeyValue;
@@ -14,8 +14,10 @@ export function getDirtyValueFromCell(state, rowKeyValue, colKeyValue) {
14
14
 
15
15
  if (
16
16
  dirtyValues &&
17
- dirtyValues[rowKeyValue] &&
18
- dirtyValues[rowKeyValue][colKeyValue]
17
+ // eslint-disable-next-line no-prototype-builtins
18
+ dirtyValues.hasOwnProperty(rowKeyValue) &&
19
+ // eslint-disable-next-line no-prototype-builtins
20
+ dirtyValues[rowKeyValue].hasOwnProperty(colKeyValue)
19
21
  ) {
20
22
  return dirtyValues[rowKeyValue][colKeyValue];
21
23
  }
@@ -10,7 +10,7 @@ import {
10
10
  } from './tree';
11
11
  import { isCellEditable, getRowByKey } from './rows';
12
12
  import { isRTL, getShadowActiveElements } from 'lightning/utilsPrivate';
13
- import { setFirstVisibleIndex } from './renderManager';
13
+ import { findFirstVisibleIndex } from './virtualization';
14
14
 
15
15
  // Indicator/flag for a header row
16
16
  const HEADER_ROW = 'HEADER';
@@ -88,7 +88,7 @@ const SELECTORS = {
88
88
  * user keys down on a cell that contains actionable items (ex. edit button, links,
89
89
  * email, buttons).
90
90
  *
91
- * @param {Event} event - Custom DOM event (privatecellkeydown) sent by the cell
91
+ * @param {Event} event Custom DOM event (privatecellkeydown) sent by the cell
92
92
  */
93
93
  export function handleKeydownOnCell(event) {
94
94
  event.stopPropagation();
@@ -110,7 +110,7 @@ export function handleKeydownOnCell(event) {
110
110
  * Those events are handled by `handleKeydownOnCell()` and the remaining are
111
111
  * handled by this function.
112
112
  *
113
- * @param {*} event
113
+ * @param {Event} event
114
114
  */
115
115
  export function handleKeydownOnTable(event) {
116
116
  const targetTagName = event.target.tagName.toLowerCase();
@@ -127,10 +127,10 @@ export function handleKeydownOnTable(event) {
127
127
  * Changes the datatable state based on the keyboard event sent from the cell component.
128
128
  * The result of those changes may trigger a re-render on the table
129
129
  *
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
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
134
134
  */
135
135
  function reactToKeyboardInActionMode(template, state, event) {
136
136
  switch (event.detail.keyCode) {
@@ -799,7 +799,13 @@ function stillValidActiveCell(state) {
799
799
  const {
800
800
  activeCell: { rowKeyValue, colKeyValue },
801
801
  } = state;
802
+
803
+ let sortableColumns = state.columns.filter((column) => column.sortable);
804
+
802
805
  if (rowKeyValue === HEADER_ROW) {
806
+ if (state.rows.length && sortableColumns.length === 0) {
807
+ return false;
808
+ }
803
809
  return state.headerIndexes[colKeyValue] !== undefined;
804
810
  }
805
811
  return !!(
@@ -830,9 +836,7 @@ export function setFocusActiveCell(
830
836
  const { keyboardMode } = state;
831
837
  const { rowIndex, colIndex } = getIndexesActiveCell(state);
832
838
 
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);
839
+ state.activeCell.focused = !(info && isActiveCellValid(state));
836
840
  updateTabIndex(state, rowIndex, colIndex);
837
841
 
838
842
  let cellElement = getActiveCellElement(template, state);
@@ -1313,7 +1317,7 @@ function isHeaderRow(rowIndex) {
1313
1317
  return rowIndex === -1;
1314
1318
  }
1315
1319
 
1316
- function getDataRow(rowKeyValue) {
1320
+ export function getDataRow(rowKeyValue) {
1317
1321
  return `[data-row-key-value="${rowKeyValue}"]`;
1318
1322
  }
1319
1323
 
@@ -1365,7 +1369,7 @@ function updateScrollTop(state, template, element) {
1365
1369
  } else if (findMeRect.bottom > parentRect.bottom - BOTTOM_MARGIN) {
1366
1370
  scrollableY.scrollTop += SCROLL_OFFSET;
1367
1371
  }
1368
- setFirstVisibleIndex(state, scrollableY.scrollTop);
1372
+ findFirstVisibleIndex(state, scrollableY.scrollTop);
1369
1373
  }
1370
1374
 
1371
1375
  function scrollToCell(state, template, rowIndex) {
@@ -1380,7 +1384,7 @@ function scrollToCell(state, template, rowIndex) {
1380
1384
 
1381
1385
  const scrollableY = template.querySelector('.slds-scrollable_y');
1382
1386
  scrollableY.scrollTop = scrollTop;
1383
- setFirstVisibleIndex(state, scrollTop);
1387
+ findFirstVisibleIndex(state, scrollTop);
1384
1388
  }
1385
1389
 
1386
1390
  export function isActiveCellEditable(state) {
@@ -1,6 +1,7 @@
1
1
  import { getScrollOffsetFromTableEnd, normalizeNumberAttribute } from './utils';
2
2
  import { LightningResizeObserver } from 'lightning/resizeObserver';
3
3
  import { normalizeBoolean, normalizeString } from 'lightning/utilsPrivate';
4
+ import { resetRowHeights } from './virtualization';
4
5
 
5
6
  export const DEFAULT_ROW_HEIGHT = 30.5;
6
7
  const DEFAULT_BUFFER_SIZE = 5;
@@ -31,17 +32,6 @@ export function getDTWrapperHeight() {
31
32
  return this.template.querySelector('.slds-scrollable_x').offsetHeight;
32
33
  }
33
34
 
34
- /**
35
- * Sets the first visible index in the datatable state based on
36
- * a given scrollTop value and the current rowHeight
37
- *
38
- * @param {Object} state - datatable state
39
- * @param {Number} scrollTop - scroll top to calculate first visible index for
40
- */
41
- export function setFirstVisibleIndex(state, scrollTop) {
42
- state.firstVisibleIndex = Math.floor(scrollTop / state.rowHeight);
43
- }
44
-
45
35
  function getDefaultPreviousInfo() {
46
36
  return {
47
37
  renderedRowCount: 0,
@@ -81,7 +71,13 @@ export class RenderManager {
81
71
  * @param {RenderManagerConfig} config - set of properties used for render management
82
72
  */
83
73
  configure(state, getWrapperHeight, config) {
84
- const { viewportRendering, rowHeight, virtualize, bufferSize } = config;
74
+ const {
75
+ viewportRendering,
76
+ rowHeight,
77
+ virtualize,
78
+ bufferSize,
79
+ fixedHeight,
80
+ } = config;
85
81
  setVirtualize(state, virtualize);
86
82
 
87
83
  if (normalizeBoolean(viewportRendering) || state.virtualize) {
@@ -93,6 +89,7 @@ export class RenderManager {
93
89
  'non-negative',
94
90
  DEFAULT_BUFFER_SIZE
95
91
  );
92
+ state.fixedHeight = normalizeBoolean(fixedHeight);
96
93
  if (typeof rowHeight === 'number') {
97
94
  state.rowHeight = rowHeight;
98
95
  this.threshold = ROW_THRESHOLD * state.rowHeight;
@@ -157,9 +154,42 @@ export class RenderManager {
157
154
  * @returns {Object} object with firstIndex and lastIndex of rendered range of rows
158
155
  */
159
156
  getRenderedRange(state) {
160
- const { firstVisibleIndex, bufferSize, renderedRowCount, rows } = state;
157
+ const {
158
+ firstVisibleIndex,
159
+ bufferSize,
160
+ renderedRowCount,
161
+ rows,
162
+ fixedHeight,
163
+ rowHeight,
164
+ heightCache,
165
+ } = state;
166
+
161
167
  const firstIndex = Math.max(firstVisibleIndex - bufferSize, 0);
162
168
  let lastIndex = firstVisibleIndex - bufferSize + renderedRowCount;
169
+ const firstVisibleKey =
170
+ rows[firstVisibleIndex] && rows[firstVisibleIndex].key;
171
+
172
+ if (!fixedHeight && heightCache[firstVisibleKey]) {
173
+ let i = firstVisibleIndex;
174
+ let currentHeight = 0;
175
+ const knownRowHeight = rows[i] && heightCache[rows[i].key];
176
+ // loop through rows until we find last row based on wrapper height
177
+ while (knownRowHeight && currentHeight < this.wrapperHeight) {
178
+ currentHeight += knownRowHeight;
179
+ i = i + 1;
180
+ }
181
+ if (currentHeight >= this.wrapperHeight) {
182
+ // row heights were measured; i represents last visible index
183
+ lastIndex = i + bufferSize - 1;
184
+ } else {
185
+ // row heights not yet measured, i is last visible index with known height
186
+ // guess lastIndex based on default row height
187
+ const extraRowEstimate =
188
+ (this.wrapperHeight - currentHeight) / rowHeight;
189
+ lastIndex = i + extraRowEstimate + bufferSize;
190
+ }
191
+ }
192
+
163
193
  // without this, we may render too few rows when there
164
194
  // aren't enough rows for virtualization to be needed
165
195
  if (
@@ -224,6 +254,8 @@ export class RenderManager {
224
254
  const rowCountWithBuffer =
225
255
  this.getRowCountWithBuffer(state);
226
256
 
257
+ resetRowHeights(state);
258
+
227
259
  if (
228
260
  rowCountWithBuffer > state.renderedRowCount ||
229
261
  state.virtualize
@@ -40,7 +40,8 @@ class EventQueue {
40
40
 
41
41
  /**
42
42
  * Get element size
43
- * @param {HTMLElement} element - element to return the size.
43
+ *
44
+ * @param {HTMLElement} element Element to return the size.
44
45
  * @returns {Object} {width, height}
45
46
  */
46
47
  function getElementSize(element) {
@@ -73,9 +74,10 @@ function createResizeSensor() {
73
74
  }
74
75
 
75
76
  /**
77
+ * Attaches the resize event to an element
76
78
  *
77
- * @param {HTMLElement} element - element to listen resize.
78
- * @param {Function} resizeListener - resize event listener.
79
+ * @param {HTMLElement} element Element to listen resize
80
+ * @param {Function} resizeListener Resize event listener
79
81
  */
80
82
  function attachResizeEvent(element, resizeListener) {
81
83
  if (!element) {
@@ -192,6 +194,12 @@ function attachResizeEvent(element, resizeListener) {
192
194
  requestAnimationFrame(reset);
193
195
  }
194
196
 
197
+ /**
198
+ * Removes a resize event from an element.
199
+ *
200
+ * @param {HTMLElement} element Element to remove resize listener from
201
+ * @param {Event} event The event to remove
202
+ */
195
203
  function detach(elem, ev) {
196
204
  if (!elem) {
197
205
  return;
@@ -430,7 +430,7 @@ export function setSelectedRowsKeys(state, value) {
430
430
  export function syncSelectedRowsKeys(state, selectedRows) {
431
431
  let changed = false;
432
432
  const { selectedRowsKeys, keyField } = state;
433
-
433
+ const maxRowSelection = getMaxRowSelection(state) || getRowsTotal(state);
434
434
  if (Object.keys(selectedRowsKeys).length !== selectedRows.length) {
435
435
  changed = true;
436
436
  state.selectedRowsKeys = updateSelectedRowsKeysFromSelectedRows(
@@ -446,7 +446,13 @@ export function syncSelectedRowsKeys(state, selectedRows) {
446
446
  );
447
447
  }
448
448
  }
449
-
449
+ if (maxRowSelection > 1 && changed) {
450
+ if (selectedRows.length < maxRowSelection) {
451
+ markDeselectedRowEnabled(state);
452
+ } else {
453
+ markDeselectedRowDisabled(state);
454
+ }
455
+ }
450
456
  updateBulkSelectionState(state);
451
457
 
452
458
  return {
@@ -497,7 +503,7 @@ export function resetSelectedRowsKeys(state) {
497
503
  * Returns undefined if the row key value is invalid
498
504
  *
499
505
  * @param {Object} state - the datatable state.
500
- * @return {String | undefined } the row key or undefined.
506
+ * @returns {String | undefined } the row key or undefined.
501
507
  */
502
508
  function getLastRowSelection(state) {
503
509
  const lastSelectedRowKey = state.lastSelectedRowKey;
@@ -9,31 +9,27 @@
9
9
  */
10
10
 
11
11
  /**
12
- * Returns whether or not the row is selected using the state and the rowKeyValue
13
- * The state maintains the list of selected rows from which
14
- * this value can be retrieved.
12
+ * Returns whether or not the row is selected using the state and the `rowKeyValue`.
13
+ * The state maintains the list of selected rows from which this value can be retrieved.
15
14
  *
16
- * @param {Object} state - datatable state object
17
- * @param {String} rowKeyValue
18
- * @returns
15
+ * @param {Object} state Datatable state object
16
+ * @param {String} rowKeyValue The row key value to lookup
17
+ * @returns {Boolean} Whether the row is currently selected
19
18
  */
20
19
  export function isSelectedRow(state, rowKeyValue) {
21
20
  return !!state.selectedRowsKeys[rowKeyValue];
22
21
  }
23
22
 
24
23
  /**
25
- * Returns whether the row (whose row key value is specified)
26
- * should be disabled or not.
27
- * Should not disable if the row is selected.
28
- * If the particular row is not selected, the row should be disabled
29
- * when max-row-selection > 1 and the selection limit is reached
24
+ * Returns whether the row (whose row key value is specified) should be disabled or not.
25
+ * Should not disable if the row is selected. If the particular row is not selected, the
26
+ * row should be disabled when `max-row-selection` > 1 and the selection limit is reached.
30
27
  *
31
- * Note: Do not disable selection when max-row-selection = 1 and
32
- * a row has been selected.
28
+ * NOTE: Do not disable selection when `max-row-selection` is 1 and a row has been selected.
33
29
  *
34
- * @param {Object} state
35
- * @param {String} rowKeyValue
36
- * @returns {Boolean} whether the row should be disabled or not
30
+ * @param {Object} state Datatable state object
31
+ * @param {String} rowKeyValue The row key value to lookup
32
+ * @returns {Boolean} Whether the row should be disabled or not
37
33
  */
38
34
  export function isDisabledRow(state, rowKeyValue) {
39
35
  if (!isSelectedRow(state, rowKeyValue)) {
@@ -51,11 +47,10 @@ export function isDisabledRow(state, rowKeyValue) {
51
47
 
52
48
  /**
53
49
  * Returns which input type to use for row selection.
54
- * If max-row-selection = 1, use radio buttons,
55
- * otherwise, use checkboxes.
50
+ * If `max-row-selection` is 1, use radio buttons. Otherwise, use checkboxes.
56
51
  *
57
- * @param {Object} state - datatable's state object
58
- * @returns
52
+ * @param {Object} state Datatable state object
53
+ * @returns {String} `radio` is only one row is allowed to be selected, otherwise `checkbox`
59
54
  */
60
55
  export function getRowSelectionInputType(state) {
61
56
  if (getMaxRowSelection(state) === 1) {
@@ -64,14 +59,32 @@ export function getRowSelectionInputType(state) {
64
59
  return 'checkbox';
65
60
  }
66
61
 
62
+ /**
63
+ * Retrieves the maximum number of rows that can be selected from state.
64
+ *
65
+ * @param {Object} state Datatable state object
66
+ * @returns {Integer} The maximum rows that can be selected
67
+ */
67
68
  export function getMaxRowSelection(state) {
68
69
  return state.maxRowSelection;
69
70
  }
70
71
 
72
+ /**
73
+ * Determines the number of rows currently selected.
74
+ *
75
+ * @param {Object} state Datatable state object
76
+ * @returns {Integer} The number of currently selected rows
77
+ */
71
78
  export function getCurrentSelectionLength(state) {
72
79
  return getSelectedRowsKeys(state).length;
73
80
  }
74
81
 
82
+ /**
83
+ * Retrieves the row keys that are currently selected.
84
+ *
85
+ * @param {Object} state Datatable state object
86
+ * @returns {Array} An array of the row keys that are currently selected
87
+ */
75
88
  export function getSelectedRowsKeys(state) {
76
89
  return Object.keys(state.selectedRowsKeys).filter(
77
90
  (key) => state.selectedRowsKeys[key]
@@ -76,8 +76,7 @@ export function resolveRowClassNames(row) {
76
76
  * @param {object} state - data table state
77
77
  * @param {string} rowKeyValue - computed id for the row
78
78
  * @param {string} colKeyValue - computed id for the column
79
- *
80
- * @return {object} The user row that its related to the action.
79
+ * @returns {object} The user row that its related to the action.
81
80
  */
82
81
  export function getUserRowByCellKeys(state, rowKeyValue, colKeyValue) {
83
82
  const rowIndex = state.indexes[rowKeyValue][colKeyValue][0];
@@ -135,6 +134,8 @@ export function updateRowsAndCellIndexes() {
135
134
  state.indexes[row.key] = { rowIndex };
136
135
 
137
136
  row.rowIndex = rowIndex;
137
+ row.rowNumber = rowIndex + 1; // for UTAM since methods are base-1
138
+ row.ariaRowIndex = rowIndex + 2; // aria attrs are base-1 and also count header as a row
138
139
  row.inputType = getRowSelectionInputType(state);
139
140
  row.isSelected = isSelectedRow(state, row.key);
140
141
  row.ariaSelected = row.isSelected ? 'true' : false;