lightning-base-components 1.14.2-alpha → 1.14.3-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 (53) hide show
  1. package/metadata/raptor.json +1 -0
  2. package/package.json +17 -1
  3. package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsPlural.js +1 -0
  4. package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsSingular.js +1 -0
  5. package/scopedImports/@salesforce-label-LightningErrorMessage.validitySelectAtleastOne.js +1 -0
  6. package/scopedImports/@salesforce-label-LightningMap.titleWithAddress.js +1 -0
  7. package/src/lightning/ariaObserver/__docs__/ariaObserver.md +142 -0
  8. package/src/lightning/buttonMenu/keyboard.js +0 -10
  9. package/src/lightning/card/card.html +6 -0
  10. package/src/lightning/checkboxGroup/checkboxGroup.html +2 -2
  11. package/src/lightning/checkboxGroup/checkboxGroup.js +6 -1
  12. package/src/lightning/colorPickerCustom/colorPickerCustom.js +20 -1
  13. package/src/lightning/datatable/__docs__/datatable.md +55 -0
  14. package/src/lightning/datatable/__examples__/basic/basic.html +1 -1
  15. package/src/lightning/datatable/columns-shared.js +1 -1
  16. package/src/lightning/datatable/datatable.js +97 -24
  17. package/src/lightning/datatable/errors.js +20 -9
  18. package/src/lightning/datatable/headerActions.js +77 -49
  19. package/src/lightning/datatable/inlineEdit.js +505 -370
  20. package/src/lightning/datatable/inlineEditShared.js +24 -0
  21. package/src/lightning/datatable/keyboard.js +1 -1
  22. package/src/lightning/datatable/renderManager.js +241 -129
  23. package/src/lightning/datatable/rowLevelActions.js +17 -13
  24. package/src/lightning/datatable/rowNumber.js +54 -20
  25. package/src/lightning/datatable/rowSelection.js +760 -0
  26. package/src/lightning/datatable/rowSelectionShared.js +79 -0
  27. package/src/lightning/datatable/rows.js +16 -5
  28. package/src/lightning/datatable/state.js +10 -1
  29. package/src/lightning/datatable/templates/div/div.css +4 -0
  30. package/src/lightning/datatable/templates/div/div.html +1 -0
  31. package/src/lightning/datatable/utils.js +14 -0
  32. package/src/lightning/dualListbox/dualListbox.html +1 -1
  33. package/src/lightning/dualListbox/dualListbox.js +42 -0
  34. package/src/lightning/inputUtils/validity.js +12 -1
  35. package/src/lightning/pillContainer/__docs__/pillContainer.md +45 -1
  36. package/src/lightning/positionLibrary/positionLibrary.js +31 -43
  37. package/src/lightning/primitiveCellActions/primitiveCellActions.js +69 -12
  38. package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +13 -11
  39. package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +13 -8
  40. package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.html +17 -14
  41. package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.js +167 -98
  42. package/src/lightning/primitiveDatatableIeditTypeFactory/primitiveDatatableIeditTypeFactory.js +94 -69
  43. package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.html +4 -4
  44. package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.js +4 -4
  45. package/src/lightning/primitiveHeaderActions/primitiveHeaderActions.js +99 -37
  46. package/src/lightning/progressIndicator/progressIndicator.js +1 -1
  47. package/src/lightning/progressStep/progressStep.js +1 -1
  48. package/src/lightning/staticMap/staticMap.html +1 -0
  49. package/src/lightning/staticMap/staticMap.js +39 -2
  50. package/src/lightning/utils/classSet.js +4 -1
  51. package/src/lightning/datatable/inlineEdit-shared.js +0 -14
  52. package/src/lightning/datatable/selector-shared.js +0 -38
  53. package/src/lightning/datatable/selector.js +0 -527
@@ -1352,6 +1352,7 @@
1352
1352
  "industriesCibApi": {},
1353
1353
  "industriesDecisionMatrixDesignerApi": {},
1354
1354
  "industriesExplainabilityApi": {},
1355
+ "industriesHealthcloudHpiApi": {},
1355
1356
  "industriesIdentityVerificationApi": {},
1356
1357
  "industriesInterestTaggingApi": {},
1357
1358
  "industriesLoyaltyEngineApi": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightning-base-components",
3
- "version": "1.14.2-alpha",
3
+ "version": "1.14.3-alpha",
4
4
  "engines": {
5
5
  "node": ">=12.18.3"
6
6
  },
@@ -57,6 +57,10 @@
57
57
  "name": "@salesforce/label/LightningErrorMessage.validityTooShort",
58
58
  "path": "scopedImports/@salesforce-label-LightningErrorMessage.validityTooShort.js"
59
59
  },
60
+ {
61
+ "name": "@salesforce/label/LightningErrorMessage.validitySelectAtleastOne",
62
+ "path": "scopedImports/@salesforce-label-LightningErrorMessage.validitySelectAtleastOne.js"
63
+ },
60
64
  {
61
65
  "name": "@salesforce/label/LightningCarousel.tabString",
62
66
  "path": "scopedImports/@salesforce-label-LightningCarousel.tabString.js"
@@ -369,6 +373,14 @@
369
373
  "name": "@salesforce/label/LightningDualListbox.requiredOptionError",
370
374
  "path": "scopedImports/@salesforce-label-LightningDualListbox.requiredOptionError.js"
371
375
  },
376
+ {
377
+ "name": "@salesforce/label/LightningDualListbox.movedOptionsSingular",
378
+ "path": "scopedImports/@salesforce-label-LightningDualListbox.movedOptionsSingular.js"
379
+ },
380
+ {
381
+ "name": "@salesforce/label/LightningDualListbox.movedOptionsPlural",
382
+ "path": "scopedImports/@salesforce-label-LightningDualListbox.movedOptionsPlural.js"
383
+ },
372
384
  {
373
385
  "name": "@salesforce/label/LightningProgressIndicator.stageComplete",
374
386
  "path": "scopedImports/@salesforce-label-LightningProgressIndicator.stageComplete.js"
@@ -861,6 +873,10 @@
861
873
  "name": "@salesforce/label/LightningMap.iframeTitle",
862
874
  "path": "scopedImports/@salesforce-label-LightningMap.iframeTitle.js"
863
875
  },
876
+ {
877
+ "name": "@salesforce/label/LightningMap.titleWithAddress",
878
+ "path": "scopedImports/@salesforce-label-LightningMap.titleWithAddress.js"
879
+ },
864
880
  {
865
881
  "name": "@salesforce/label/LightningPrimitiveCoordinate.selected",
866
882
  "path": "scopedImports/@salesforce-label-LightningPrimitiveCoordinate.selected.js"
@@ -0,0 +1 @@
1
+ export default 'Items {0} moved to the list {1}';
@@ -0,0 +1 @@
1
+ export default 'Item {0} moved to the list {1}';
@@ -0,0 +1 @@
1
+ export default 'You must select at least one choice from this set.';
@@ -0,0 +1 @@
1
+ export default 'Map of {0}';
@@ -0,0 +1,142 @@
1
+ The `lightning/ariaObserver` module provides an easy way for users to write accessible component that works in both synthetic and native shadow.
2
+
3
+ ## Aria ID referencing in native shadow
4
+ Use the` AriaObserver` library to write accessible component that works where `ariaLabelledBy` would break native shadow.
5
+
6
+ Here's an example that won't work with native shadow. In the following code, we support attribute `ariaLabelledBy` in our component `c-foo`, so the `input` element is labelled by external elements.
7
+
8
+ ``` html
9
+ <template>
10
+ <input aria-labelledby={ariaLabelledBy}>
11
+ </template>
12
+ ```
13
+
14
+ ```
15
+ class Foo extends LightningElement {
16
+ @api ariaLabelledBy;
17
+ }
18
+ ```
19
+
20
+ This example uses the `aria-labelledby` attribute to use the internal input as an external label in `c-foo`.
21
+
22
+ ``` html
23
+ <span id="my-label">Input field</span>
24
+ <c-foo aria-labelledby="my-label"></c-foo>
25
+ ```
26
+
27
+ The above example works fine in synthetic shadow, but in native shadow mode, the `aria-labelledby` ID reference is broken. The `input` element is isolated in its own shadow DOM, so the label with id `my-label` isn't in the same shadow boundary.
28
+
29
+ ## Creating AriaObserver
30
+
31
+ To use `AriaObserver` in your component, first import it from `lightning/ariaObserver`. Then, instantiate the `AriaObserver` within your component.
32
+
33
+ The `AriaObserver` constructor takes one parameter:
34
+ - `cmpReference` The reference of the current component (`this`).
35
+
36
+ ``` js
37
+ import AriaObserver from 'lightning/ariaObserver';
38
+
39
+ class Foo extends LightningElement {
40
+ constructor() {
41
+ super();
42
+ this.ariaObserver = new AriaObserver(this);
43
+ }
44
+ }
45
+ ```
46
+
47
+ Next, use the `connect(options)` method to connect between the internal element and the external reference. It takes an options object with the following keys:
48
+ - `targetSelector` The selector to the internal element where the aria attribute should be attached.
49
+ - `attribute` The name of the aria attribute. Two supported options: `aria-labelledby`and `aria-describedby`.
50
+ - `id` The ID of the external element. Alternatively, you can use `ids` for multiple IDs.
51
+
52
+ This example uses `connect(options)` to display an aria label for the internal `input` element.
53
+ ``` js
54
+ @api
55
+ get ariaLabelledBy() {
56
+ return this._ariaLabelledBy;
57
+ }
58
+ set ariaLabelledBy(refs) {
59
+ this._ariaLabelledBy = refs;
60
+
61
+ this.ariaObserver.connect({
62
+ targetSelector: 'input',
63
+ attribute: 'aria-labelledby',
64
+ id: refs
65
+ });
66
+ }
67
+ ```
68
+
69
+ Then use the `sync()` method to synchronize the ID references when the template is re-rendered.
70
+
71
+ ``` js
72
+ renderedCallback() {
73
+ this.ariaObserver.sync();
74
+ }
75
+ ```
76
+
77
+ Finally, disconnect the aria observer and free the resources at the end of the component lifecycle.
78
+
79
+ ``` js
80
+ disconnectedCallback() {
81
+ if (this.ariaObserver) {
82
+ this.ariaObserver.disconnect();
83
+ this.ariaObserver = undefined;
84
+ }
85
+ }
86
+ ```
87
+
88
+ Here is all these steps combined into a complete example of a component using `AriaObserver`.
89
+
90
+ ``` html
91
+ <template>
92
+ <!-- element where the aria attribute is attached -->
93
+ <input>
94
+ </template>
95
+ ```
96
+
97
+ ``` js
98
+ import {api, LightningElement} from 'lwc';
99
+ import AriaObserver from 'lightning/ariaObserver';
100
+
101
+ export default class Foo extends LightningElement {
102
+ constructor() {
103
+ super();
104
+ this.ariaObserver = new AriaObserver(this);
105
+ }
106
+
107
+ _ariaLabelledBy = '';
108
+
109
+ @api
110
+ get ariaLabelledBy() {
111
+ return this._ariaLabelledBy;
112
+ }
113
+ set ariaLabelledBy(refs) {
114
+ this._ariaLabelledBy = refs;
115
+
116
+ /* Establish the connection between input and the external label */
117
+ this.ariaObserver.connect({
118
+ targetSelector: 'input',
119
+ attribute: 'aria-labelledby',
120
+ id: refs
121
+ });
122
+ }
123
+
124
+ renderedCallback() {
125
+ this.ariaObserver.sync();
126
+ }
127
+
128
+ disconnectedCallback() {
129
+ if (this.ariaObserver) {
130
+ this.ariaObserver.disconnect();
131
+ this.ariaObserver = undefined;
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Limitations
138
+ `AriaObserver` only works with text-only aria ID references.
139
+
140
+ Supported attributes:
141
+ - `aria-labelledby`
142
+ - `aria-describedby`
@@ -113,16 +113,6 @@ export function handleKeyDownOnMenuTrigger(event, menuInterface) {
113
113
  menuInterface.toggleMenuVisibility();
114
114
  }
115
115
  break;
116
- // W3: Home and End: If arrow key wrapping is not supported, move focus to first and last item
117
- // Note: We do support wrapping, but it doesn't hurt to support these keys anyway.
118
- case keyCodes.home:
119
- preventDefaultAndStopPropagation(event);
120
- menuInterface.focusOnIndex(0);
121
- break;
122
- case keyCodes.end:
123
- preventDefaultAndStopPropagation(event);
124
- menuInterface.focusOnIndex(menuInterface.getTotalMenuItems() - 1);
125
- break;
126
116
  // W3: Escape: Close the menu and return focus to the element or context, e.g., menu button or
127
117
  // parent menu item, from which the menu was opened
128
118
  case keyCodes.escape:
@@ -40,5 +40,11 @@
40
40
  <slot name="footer"></slot>
41
41
  </div>
42
42
  </template>
43
+ <!-- This dummy placeholder slot is required because
44
+ a slot's presence is verified by assignedElements count
45
+ and not whether the slot itself is present or not -->
46
+ <template if:false={showFooter}>
47
+ <slot name="footer" class="slds-hide"></slot>
48
+ </template>
43
49
  </article>
44
50
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <fieldset aria-required={required}>
2
+ <fieldset aria-describedby={computearaiDescriedBy}>
3
3
 
4
4
  <legend class={computedLegendClass}>
5
5
  <template if:true={required}>
@@ -32,6 +32,6 @@
32
32
  <template if:true={_helpMessage}>
33
33
  <div id="helptext" data-helptext class="slds-form-element__help">{_helpMessage}</div>
34
34
  </template>
35
-
35
+
36
36
  </fieldset>
37
37
  </template>
@@ -295,7 +295,7 @@ export default class LightningCheckboxGroup extends LightningElement {
295
295
  get _constraint() {
296
296
  if (!this._constraintApi) {
297
297
  this._constraintApi = new FieldConstraintApi(() => this, {
298
- valueMissing: () =>
298
+ validitySelectAtleastOneValue: () =>
299
299
  !this.disabled && this.required && this.value.length === 0,
300
300
  });
301
301
  }
@@ -313,4 +313,9 @@ export default class LightningCheckboxGroup extends LightningElement {
313
313
  })
314
314
  .toString();
315
315
  }
316
+
317
+ computearaiDescriedBy() {
318
+ const helpMessage = this.template.querySelector('[data-helptext]');
319
+ return getRealDOMId(helpMessage);
320
+ }
316
321
  }
@@ -8,6 +8,7 @@ import labelHexLabel from '@salesforce/label/LightningColorPicker.hexLabel';
8
8
  import labelHueInput from '@salesforce/label/LightningColorPicker.hueInput';
9
9
  import labelRInput from '@salesforce/label/LightningColorPicker.rInput';
10
10
  import labelRedAbbr from '@salesforce/label/LightningColorPicker.redAbbr';
11
+ import formFactorPropertyName from '@salesforce/client/formFactor';
11
12
  import { LightningElement, api, track } from 'lwc';
12
13
  import { keyCodes } from 'lightning/utilsPrivate';
13
14
  import { generateUniqueId, getErrorMessage } from 'lightning/inputUtils';
@@ -58,7 +59,25 @@ export default class LightningColorPickerCustom extends LightningElement {
58
59
  if (!this._initialized) {
59
60
  // eslint-disable-next-line @lwc/lwc/no-async-operation
60
61
  requestAnimationFrame(() => {
61
- this.focus();
62
+ // (*1*)
63
+ if (formFactorPropertyName !== 'Large') {
64
+ /**
65
+ * We need to wait for one more animation frame and invoke .focus()
66
+ * in iOS. This is because the positionLibray.js initially sets the position
67
+ * of this color-picker element to "top: 0px" and then later repositions it asynchronously
68
+ * it the next animation frames. The first (*1*) rAF callback is fired between
69
+ * setting "top: 0px" and then later repositioning it. Calling .focus() in this callback
70
+ * triggers a re-paint step and the page is scrolled to the top due to "top: 0px" being
71
+ * present in the styles. To avoid this, we can delay the next re-paint after color-picker
72
+ * is repositioned correctly by positionLibrary.js. Hence we wait for next animation frame
73
+ * and then call .focus() to trigger the next re-paint after the color-picker is repositioned correctly
74
+ * which was initially positioned with "top: 0px" by positionLibrary.js
75
+ */
76
+ // eslint-disable-next-line @lwc/lwc/no-async-operation
77
+ requestAnimationFrame(() => this.focus());
78
+ } else {
79
+ this.focus();
80
+ }
62
81
  });
63
82
  this.gradient();
64
83
  this.handleUpdateAnchor();
@@ -244,6 +244,28 @@ Valid data types and their supported attributes include:
244
244
  | text | Displays text using [lightning-formatted-text](bundle/lightning-formatted-text/). This is the default data type. | linkify |
245
245
  | url | Displays a URL using [lightning-formatted-url](bundle/lightning-formatted-url/) | label, target, tooltip |
246
246
 
247
+ In some situations, multiple columns reference the same `fieldName` but have different `fieldApiNames` and different ways of working with the field information. To give a column a unique ID when using the same field name for two columns, use the attribute `columnKey` instead of `fieldName` on one of the column definitions.
248
+
249
+ ```javascript
250
+ get columns() {
251
+ return [
252
+ // Column using 'fieldName' as identifier
253
+ {
254
+ label: 'Lead',
255
+ fieldName: 'leadId',
256
+ ...
257
+ },
258
+ // Column using 'columnKey' as identifier
259
+ {
260
+ label: 'Company',
261
+ columnKey: 'leadCompany',
262
+ fieldName: 'leadId',
263
+ ...
264
+ }
265
+ ]
266
+ }
267
+ ```
268
+
247
269
  #### Custom Formatting Examples
248
270
 
249
271
  To customize the formatting based on the data type, pass in the attributes for
@@ -494,6 +516,21 @@ const columns = [
494
516
 
495
517
  Custom classes are currently not supported. To apply custom styling on your datatable cells, create a custom data type and then apply your custom CSS classes. See [Custom Data Type Layout and Styles](docs/component-library/documentation/lwc/lwc.data_table_custom_types_styling).
496
518
 
519
+ #### Displaying Indicators for Read-Only Fields
520
+
521
+ You can display a lock icon on read-only fields to specify they're not editable. To specify that a column's field as read-only, set the column attribute `editable` to `false` and the column attribute `displayReadOnlyIcon` to `true` in the associated column definition. This example displays a lock icon on each field in a column called "Website."
522
+
523
+ ```javascript
524
+ const columns = [
525
+ {
526
+ label: 'Website',
527
+ fieldName: 'website',
528
+ type: 'url',
529
+ editable: false,
530
+ displayReadOnlyIcon: true,
531
+ }
532
+ ```
533
+
497
534
  #### Using Infinite Scrolling to Load More Rows
498
535
 
499
536
  Infinite scrolling enables you to load a subset of data and then display more
@@ -1031,6 +1068,24 @@ handleSave(event) {
1031
1068
  }
1032
1069
  ```
1033
1070
 
1071
+ You can also program an external element, such as a button, to open an inline edit panel on the currently active cell or next editable cell in the datatable. Invoke the `openInlineEdit()` method in the external element that should direct a user to the first editable cell in your datatable component.
1072
+
1073
+ This example opens a datatable cell for inline edit when the user clicks an "Edit" button.
1074
+
1075
+ ```javascript
1076
+ import { LightningElement } from 'lwc';
1077
+ export default class DatatableWithInlineEdit extends LightningElement {
1078
+ data = [];
1079
+ columns = columns;
1080
+ rowOffset = 0;
1081
+
1082
+ handleClick() {
1083
+ const dt = this.template.querySelector('lightning-datatable');
1084
+ dt.openInlineEdit();
1085
+ }
1086
+ }
1087
+ ```
1088
+
1034
1089
  For more information, see [Display Data in a Table with Inline Editing](docs/component-library/documentation/lwc/lwc.data_table_inline_edit).
1035
1090
 
1036
1091
  #### Displaying Errors
@@ -5,5 +5,5 @@
5
5
  data={data}
6
6
  columns={columns}>
7
7
  </lightning-datatable>
8
- </div>
8
+ </div>
9
9
  </template>
@@ -4,7 +4,7 @@
4
4
  * which also has a dependency on `columns.js` for `getColumnName()`.
5
5
  *
6
6
  * We split out some of the functions that could cause circular dependencies with
7
- * `column.js` into the `*-shared.js` files. `inlineEdit-shared.js` is another.
7
+ * `column.js` into the `*-shared.js` files. `inlineEditShared.js` is another.
8
8
  */
9
9
 
10
10
  export function getColumnName(column) {
@@ -42,7 +42,7 @@ import {
42
42
  import {
43
43
  syncSelectedRowsKeys,
44
44
  handleRowSelectionChange,
45
- updateSelectionState,
45
+ updateBulkSelectionState,
46
46
  getMaxRowSelection,
47
47
  setMaxRowSelection,
48
48
  getSelectedRowsKeys,
@@ -53,7 +53,7 @@ import {
53
53
  handleDeselectRow,
54
54
  getHideSelectAllCheckbox,
55
55
  getCurrentSelectionLength,
56
- } from './selector';
56
+ } from './rowSelection';
57
57
  import {
58
58
  syncActiveCell,
59
59
  handleKeydownOnCell,
@@ -80,6 +80,7 @@ import {
80
80
  handleKeydownOnTable,
81
81
  addFocusStylesToActiveCell,
82
82
  refocusCellElement,
83
+ isCellElement,
83
84
  } from './keyboard';
84
85
  import {
85
86
  getRowNumberOffset,
@@ -134,7 +135,13 @@ import {
134
135
  import {
135
136
  isViewportRenderingEnabled,
136
137
  setViewportRendering,
138
+ getDTRows,
139
+ getDTRenderedRowCount,
140
+ setDTRenderedRowCount,
141
+ getDTWrapperHeight,
142
+ normalizeVirtualization,
137
143
  RenderManager,
144
+ DEFAULT_ROW_HEIGHT,
138
145
  } from './renderManager';
139
146
 
140
147
  import { hasTreeDataType } from './tree';
@@ -144,6 +151,7 @@ import { generateUniqueId } from 'lightning/inputUtils';
144
151
  import DatatableTypes from './types';
145
152
  import labelAriaLiveNavigationMode from '@salesforce/label/LightningDatatable.ariaLiveNavigationMode';
146
153
  import labelAriaLiveActionMode from '@salesforce/label/LightningDatatable.ariaLiveActionMode';
154
+ import { styleToString } from './utils';
147
155
 
148
156
  const i18n = {
149
157
  ariaLiveNavigationMode: labelAriaLiveNavigationMode,
@@ -192,12 +200,14 @@ export default class LightningDatatable extends LightningElement {
192
200
  _customerSelectedRows = null;
193
201
  _datatableId = generateUniqueId('lgt-datatable');
194
202
  _draftValues = [];
203
+ _firstVisibleIndex = 0; // first row that should be visible in viewport, used for virtualization
195
204
  _isResizing = false; // Whether resizing is in progress
196
205
  _privateTypes = {};
197
206
  _privateWidthObserver = null; // Instance of LightningDatatableResizeObserver
198
207
  _renderMode = 'table';
199
208
  _renderedRowCount = 0;
200
209
  _suppressBottomBar = false;
210
+ _virtualize = '';
201
211
 
202
212
  /************************* PUBLIC PROPERTIES *************************/
203
213
 
@@ -493,6 +503,7 @@ export default class LightningDatatable extends LightningElement {
493
503
  * @type {object}
494
504
  * @property {boolean} viewportRendering - Specifies whether to defer rendering of rows outside the viewport until the user begins scrolling. To use this feature, create a fixed-height container element for lightning-datatable.
495
505
  * @property {number} rowHeight - Specifies the height of a row, in px
506
+ * @property {string} virtualize - specifies whether to enable virtualization. This requires the "role-based" render mode and a fixed-height container for lightning-datatable
496
507
  */
497
508
 
498
509
  /**
@@ -508,10 +519,28 @@ export default class LightningDatatable extends LightningElement {
508
519
 
509
520
  set renderConfig(value) {
510
521
  if (typeof value === 'object' && !isIE11) {
511
- const enableViewportRendering = value.viewportRendering;
512
- setViewportRendering(this.state, enableViewportRendering);
522
+ const { viewportRendering, virtualize } = value;
523
+ setViewportRendering(this.state, viewportRendering);
524
+ if (this.state.renderModeRoleBased) {
525
+ this._virtualize = normalizeVirtualization(virtualize);
526
+ }
513
527
 
514
- this._renderManager.configure(this, value);
528
+ this._renderManager.configure(
529
+ this.getRows,
530
+ this.getWrapperHeight,
531
+ this.getRenderedRowCount,
532
+ this.setRenderedRowCount,
533
+ value
534
+ );
535
+ // if renderConfig already exists, update rendering
536
+ if (this._renderConfig) {
537
+ this._renderManager.updateViewportRendering(
538
+ this.state.rows,
539
+ this.setRenderedRowCount,
540
+ this.gridContainer,
541
+ true
542
+ );
543
+ }
515
544
  this._renderConfig = value;
516
545
  }
517
546
  }
@@ -759,24 +788,38 @@ export default class LightningDatatable extends LightningElement {
759
788
 
760
789
  get computedTableStyle() {
761
790
  if (this._columnWidthManager.isAutoResizingUpdateQueued()) {
762
- return ['table-layout:auto'].join(';');
791
+ return styleToString(['table-layout:auto']);
763
792
  }
764
- return [
793
+ return styleToString([
765
794
  'table-layout:fixed',
766
795
  getCSSWidthStyleOfTable(this.widthsData),
767
- ].join(';');
796
+ ]);
768
797
  }
769
798
 
799
+ /**
800
+ * Resets row-number counter to offset to show
801
+ * correct value when row number column is present
802
+ * and adds necessary position and height styles when
803
+ * virtualization is enabled
804
+ */
770
805
  get computedTbodyStyle() {
806
+ const style = [];
771
807
  if (
772
808
  hasRowNumberColumn(this.state) &&
773
809
  getRowNumberOffset(this.state) >= 0
774
810
  ) {
775
- return (
811
+ style.push(
776
812
  'counter-reset: row-number ' + getRowNumberOffset(this.state)
777
813
  );
778
814
  }
779
- return '';
815
+ if (this._virtualize) {
816
+ const length = this.state.rows.length;
817
+ style.push(
818
+ 'position: relative',
819
+ `height:${length * DEFAULT_ROW_HEIGHT}px`
820
+ );
821
+ }
822
+ return styleToString(style);
780
823
  }
781
824
 
782
825
  /**
@@ -812,9 +855,7 @@ export default class LightningDatatable extends LightningElement {
812
855
  styles['overflow-x'] = 'auto';
813
856
  }
814
857
 
815
- return Object.entries(styles)
816
- .map(([key, value]) => key + ':' + value)
817
- .join(';');
858
+ return styleToString(styles);
818
859
  }
819
860
 
820
861
  /**
@@ -897,6 +938,14 @@ export default class LightningDatatable extends LightningElement {
897
938
  }
898
939
 
899
940
  get renderedRows() {
941
+ if (this._virtualize) {
942
+ const { firstIndex, lastIndex } =
943
+ this._renderManager.getRenderedRange(
944
+ this._firstVisibleIndex,
945
+ this._renderedRowCount
946
+ );
947
+ return this.state.rows.slice(firstIndex, lastIndex);
948
+ }
900
949
  if (this.viewportRendering && !isIE11) {
901
950
  return this.state.rows.slice(0, this._renderedRowCount);
902
951
  }
@@ -936,6 +985,10 @@ export default class LightningDatatable extends LightningElement {
936
985
  this.updateRowsAndCellIndexes = updateRowsAndCellIndexes.bind(this);
937
986
 
938
987
  this._renderManager = new RenderManager();
988
+ this.getRenderedRowCount = getDTRenderedRowCount.bind(this);
989
+ this.setRenderedRowCount = setDTRenderedRowCount.bind(this);
990
+ this.getRows = getDTRows.bind(this);
991
+ this.getWrapperHeight = getDTWrapperHeight.bind(this);
939
992
  }
940
993
 
941
994
  /**
@@ -1095,16 +1148,24 @@ export default class LightningDatatable extends LightningElement {
1095
1148
  // set the previous focused cell to null after render is done
1096
1149
  resetCellToFocusFromPrev(state);
1097
1150
 
1098
- if (this.viewportRendering) {
1099
- this._renderManager.connectResizeObserver(this);
1151
+ if (this.viewportRendering || this._virtualize) {
1152
+ const resizeTarget = this.template.querySelector(
1153
+ 'div.dt-outer-container'
1154
+ );
1155
+ this._renderManager.connectResizeObserver(resizeTarget);
1100
1156
  if (!this._renderManager.hasWrapperHeight()) {
1101
- this._renderManager.updateWrapperHeight(this);
1157
+ this._renderManager.updateWrapperHeight(this.getWrapperHeight);
1102
1158
 
1103
1159
  // Reset the row count if we already had one before updating the wrapper height.
1104
1160
  // This can happen if the number of rows was calculated before the datatable
1105
1161
  // was rendered.
1106
1162
  if (this._renderedRowCount) {
1107
- this._renderManager.updateViewportRendering(this, true);
1163
+ this._renderManager.updateViewportRendering(
1164
+ this.state.rows,
1165
+ this.setRenderedRowCount,
1166
+ this.gridContainer,
1167
+ true
1168
+ );
1108
1169
  }
1109
1170
  }
1110
1171
  }
@@ -1187,9 +1248,16 @@ export default class LightningDatatable extends LightningElement {
1187
1248
  }
1188
1249
 
1189
1250
  handleInlineEditPanelScroll.call(this, event);
1190
-
1191
- if (this.viewportRendering) {
1192
- this._renderManager.handleScroll(this, event);
1251
+ if (this._virtualize) {
1252
+ this._firstVisibleIndex =
1253
+ this._renderManager.getFirstVisibleIndex(event);
1254
+ } else if (this.viewportRendering) {
1255
+ this._renderManager.handleScroll(
1256
+ this.state.rows,
1257
+ this._renderedRowCount,
1258
+ this.setRenderedRowCount,
1259
+ event
1260
+ );
1193
1261
  }
1194
1262
  }
1195
1263
 
@@ -1202,8 +1270,9 @@ export default class LightningDatatable extends LightningElement {
1202
1270
  handleCellClick(event) {
1203
1271
  // handles the case when clicking on the margin/pading of the td/th
1204
1272
  const targetTagName = event.target.tagName.toLowerCase();
1273
+ const targetRole = event.target.getAttribute('role');
1205
1274
 
1206
- if (targetTagName === 'td' || targetTagName === 'th') {
1275
+ if (isCellElement(targetTagName, targetRole)) {
1207
1276
  // get the row/col key value from the primitive cell.
1208
1277
  const { rowKeyValue, colKeyValue } =
1209
1278
  event.target.querySelector(':first-child');
@@ -1464,8 +1533,12 @@ export default class LightningDatatable extends LightningElement {
1464
1533
 
1465
1534
  this.updateRowsAndCellIndexes(state);
1466
1535
 
1467
- if (this.viewportRendering) {
1468
- this._renderManager.updateViewportRendering(this);
1536
+ if (this.viewportRendering || this._virtualize) {
1537
+ this._renderManager.updateViewportRendering(
1538
+ this.state.rows,
1539
+ this.setRenderedRowCount,
1540
+ this.gridContainer
1541
+ );
1469
1542
  }
1470
1543
 
1471
1544
  this._columnWidthManager.handleRowNumberOffsetChange(state, widthsData);
@@ -1508,7 +1581,7 @@ export default class LightningDatatable extends LightningElement {
1508
1581
  // Updates state.wrapText and when isWrapableType, sets internal header actions
1509
1582
  updateHeaderActions(state);
1510
1583
  this.updateRowsAndCellIndexes(state);
1511
- updateSelectionState(state);
1584
+ updateBulkSelectionState(state);
1512
1585
  this._columnWidthManager.handleRowNumberOffsetChange(state, widthsData);
1513
1586
  updateColumnWidthsMetadata(getColumns(state), widthsData);
1514
1587
  // set the celltofocus next to null if the column still exists after indexes calculation