lightning-base-components 1.14.2-alpha → 1.14.6-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.
- package/metadata/raptor.json +33 -1
- package/package.json +20 -4
- package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsPlural.js +1 -0
- package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsSingular.js +1 -0
- package/scopedImports/@salesforce-label-LightningErrorMessage.validitySelectAtleastOne.js +1 -0
- package/scopedImports/@salesforce-label-LightningMap.titleWithAddress.js +1 -0
- package/scopedImports/@salesforce-label-LightningModalBase.cancelandclose.js +1 -0
- package/src/lightning/ariaObserver/__component__/ariaObserver.spec.js +9 -0
- package/src/lightning/ariaObserver/__docs__/ariaObserver.md +142 -0
- package/src/lightning/ariaObserver/ariaObserver.js +24 -35
- package/src/lightning/baseFormattedText/baseFormattedText.html +6 -1
- package/src/lightning/baseFormattedText/baseFormattedText.js +5 -0
- package/src/lightning/buttonMenu/keyboard.js +0 -10
- package/src/lightning/card/card.html +6 -0
- package/src/lightning/checkboxGroup/checkboxGroup.html +2 -2
- package/src/lightning/checkboxGroup/checkboxGroup.js +6 -1
- package/src/lightning/colorPickerCustom/colorPickerCustom.js +20 -1
- package/src/lightning/datatable/__docs__/datatable.md +55 -0
- package/src/lightning/datatable/__examples__/basic/basic.html +1 -1
- package/src/lightning/datatable/__examples__/withInfiniteLoading/fetchDataHelper.js +21 -0
- package/src/lightning/datatable/__examples__/withInfiniteLoading/withInfiniteLoading.html +13 -0
- package/src/lightning/datatable/__examples__/withInfiniteLoading/withInfiniteLoading.js +42 -0
- package/src/lightning/datatable/autoWidthStrategy.js +170 -61
- package/src/lightning/datatable/{resizer.js → columnResizer.js} +0 -0
- package/src/lightning/datatable/columnWidthManager.js +226 -44
- package/src/lightning/datatable/columns-shared.js +1 -1
- package/src/lightning/datatable/datatable.js +104 -33
- package/src/lightning/datatable/errors.js +20 -9
- package/src/lightning/datatable/fixedWidthStrategy.js +43 -8
- package/src/lightning/datatable/headerActions.js +77 -49
- package/src/lightning/datatable/infiniteLoading.js +100 -28
- package/src/lightning/datatable/inlineEdit.js +505 -379
- package/src/lightning/datatable/inlineEditShared.js +24 -0
- package/src/lightning/datatable/keyboard.js +162 -127
- package/src/lightning/datatable/renderManager.js +208 -133
- package/src/lightning/datatable/{datatableResizeObserver.js → resizeObserver.js} +46 -29
- package/src/lightning/datatable/resizeSensor.js +8 -0
- package/src/lightning/datatable/rowLevelActions.js +17 -13
- package/src/lightning/datatable/rowNumber.js +54 -20
- package/src/lightning/datatable/rowSelection.js +760 -0
- package/src/lightning/datatable/rowSelectionShared.js +79 -0
- package/src/lightning/datatable/rows.js +17 -6
- package/src/lightning/datatable/state.js +16 -2
- package/src/lightning/datatable/templates/div/div.css +4 -0
- package/src/lightning/datatable/templates/div/div.html +128 -117
- package/src/lightning/datatable/templates/table/table.html +5 -0
- package/src/lightning/datatable/utils.js +14 -0
- package/src/lightning/datatable/widthManagerShared.js +27 -3
- package/src/lightning/datatable/wrapText.js +77 -47
- package/src/lightning/dualListbox/dualListbox.html +1 -1
- package/src/lightning/dualListbox/dualListbox.js +42 -0
- package/src/lightning/formattedDateTime/__docs__/formattedDateTime.md +36 -3
- package/src/lightning/formattedDateTime/__examples__/datetime/datetime.html +2 -2
- package/src/lightning/formattedDateTime/__examples__/datetime/datetime.js +3 -1
- package/src/lightning/formattedDateTime/__examples__/time/time.html +1 -1
- package/src/lightning/formattedDateTime/__examples__/time/time.js +3 -1
- package/src/lightning/formattedDateTime/formattedDateTime.js +1 -0
- package/src/lightning/input/input.html +2 -5
- package/src/lightning/inputUtils/validity.js +12 -1
- package/src/lightning/pillContainer/__docs__/pillContainer.md +45 -1
- package/src/lightning/positionLibrary/positionLibrary.js +31 -43
- package/src/lightning/primitiveCellActions/primitiveCellActions.js +69 -12
- package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +13 -11
- package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +13 -8
- package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.html +17 -14
- package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.js +167 -98
- package/src/lightning/primitiveDatatableIeditTypeFactory/primitiveDatatableIeditTypeFactory.js +94 -69
- package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.html +4 -4
- package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.js +4 -4
- package/src/lightning/primitiveHeaderActions/primitiveHeaderActions.js +99 -37
- package/src/lightning/progressIndicator/progressIndicator.js +1 -1
- package/src/lightning/progressStep/progressStep.js +1 -1
- package/src/lightning/spinner/spinner.html +1 -1
- package/src/lightning/spinner/spinner.js +12 -0
- package/src/lightning/staticMap/staticMap.html +1 -0
- package/src/lightning/staticMap/staticMap.js +39 -2
- package/src/lightning/utils/classSet.js +4 -1
- package/src/lightning/utilsPrivate/phonify.js +1 -1
- package/scopedImports/@salesforce-label-LightningModalBase.close.js +0 -1
- package/src/lightning/datatable/inlineEdit-shared.js +0 -14
- package/src/lightning/datatable/selector-shared.js +0 -38
- package/src/lightning/datatable/selector.js +0 -527
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveRowClassNames,
|
|
3
|
+
getRows,
|
|
4
|
+
getRowByKey,
|
|
5
|
+
getRowsTotal,
|
|
6
|
+
getRowIndexByKey,
|
|
7
|
+
rowKeyExists,
|
|
8
|
+
} from './rows';
|
|
9
|
+
import {
|
|
10
|
+
getColumns,
|
|
11
|
+
getStateColumnIndex,
|
|
12
|
+
SELECTABLE_ROW_CHECKBOX,
|
|
13
|
+
} from './columns';
|
|
14
|
+
import { isNonNegativeInteger } from './utils';
|
|
15
|
+
import {
|
|
16
|
+
getCurrentSelectionLength,
|
|
17
|
+
isSelectedRow,
|
|
18
|
+
isDisabledRow,
|
|
19
|
+
getRowSelectionInputType,
|
|
20
|
+
getMaxRowSelection,
|
|
21
|
+
getSelectedRowsKeys,
|
|
22
|
+
} from './rowSelectionShared';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
getCurrentSelectionLength,
|
|
26
|
+
isSelectedRow,
|
|
27
|
+
isDisabledRow,
|
|
28
|
+
getRowSelectionInputType,
|
|
29
|
+
getMaxRowSelection,
|
|
30
|
+
getSelectedRowsKeys,
|
|
31
|
+
} from './rowSelectionShared';
|
|
32
|
+
|
|
33
|
+
const MAX_ROW_SELECTION_DEFAULT = undefined;
|
|
34
|
+
|
|
35
|
+
/************************** EVENT HANDLERS **************************/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Marks all possible rows as selected depending on the max-row-selection value.
|
|
39
|
+
* Fires the `rowselection` event with the new set of selected rows.
|
|
40
|
+
*
|
|
41
|
+
* @param {CustomEvent} event - `selectallrows`
|
|
42
|
+
*/
|
|
43
|
+
export function handleSelectAllRows(event) {
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
markAllRowsSelected(this.state);
|
|
46
|
+
this.fireSelectedRowsChange(this.getSelectedRows());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Marks all rows as de-selected. Does not need to account for max-row-selection.
|
|
51
|
+
* Fires the `rowselection` event with the new set of selected rows.
|
|
52
|
+
* @param {CustomEvent} event - `deselectallrows`
|
|
53
|
+
*/
|
|
54
|
+
export function handleDeselectAllRows(event) {
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
markAllRowsDeselected(this.state);
|
|
57
|
+
this.fireSelectedRowsChange(this.getSelectedRows());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handles selection of row(s)
|
|
62
|
+
* 1. Marks the relevant rows as selected
|
|
63
|
+
* Depends on whether a single row or multiple rows (interval) were selected
|
|
64
|
+
* 2. Records the last selected row's row key value in the state
|
|
65
|
+
* 3. Fires the `rowselection` event with the new selected rows in the details object
|
|
66
|
+
*
|
|
67
|
+
* @param {CustomEvent} event - `selectrow` event
|
|
68
|
+
*/
|
|
69
|
+
export function handleSelectRow(event) {
|
|
70
|
+
event.stopPropagation();
|
|
71
|
+
const { rowKeyValue, isMultiple } = event.detail;
|
|
72
|
+
let fromRowKey = rowKeyValue;
|
|
73
|
+
|
|
74
|
+
if (isMultiple) {
|
|
75
|
+
fromRowKey = getLastRowSelection(this.state) || rowKeyValue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
markSelectedRowsInterval(this.state, fromRowKey, rowKeyValue);
|
|
79
|
+
setLastRowSelection(this.state, rowKeyValue);
|
|
80
|
+
this.fireSelectedRowsChange(this.getSelectedRows());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Handles the de-selection of row(s)
|
|
85
|
+
* 1. Marks the relevant rows as de-selected
|
|
86
|
+
* Depends on whether a single row or multiple rows (interval) was de-selected
|
|
87
|
+
* 2. Records the last selected row's row key value in the state
|
|
88
|
+
* 3. Fires the `rowselection` event with the new selected rows in the details object
|
|
89
|
+
*
|
|
90
|
+
* @param {CustomEvent} event - `deselectrow` event
|
|
91
|
+
*/
|
|
92
|
+
export function handleDeselectRow(event) {
|
|
93
|
+
event.stopPropagation();
|
|
94
|
+
const { rowKeyValue, isMultiple } = event.detail;
|
|
95
|
+
let fromRowKey = rowKeyValue;
|
|
96
|
+
|
|
97
|
+
if (isMultiple) {
|
|
98
|
+
fromRowKey = getLastRowSelection(this.state) || rowKeyValue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
markDeselectedRowsInterval(this.state, fromRowKey, rowKeyValue);
|
|
102
|
+
setLastRowSelection(this.state, rowKeyValue);
|
|
103
|
+
this.fireSelectedRowsChange(this.getSelectedRows());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Handles the `rowselection` event
|
|
108
|
+
*/
|
|
109
|
+
export function handleRowSelectionChange() {
|
|
110
|
+
updateBulkSelectionState(this.state);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/************************** ROW SELECTION **************************/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Marks all rows as selected.
|
|
117
|
+
* Retrieve all rows from the state object. Iterate over these rows; for each:
|
|
118
|
+
* 1. If the index is less than the specified max-row-selection value or
|
|
119
|
+
* if max-row-selection is not specified at all, set `isSelected` and
|
|
120
|
+
* `ariaSelected` to true and resolve `classnames` to reflect that the
|
|
121
|
+
* row is selected on each row object.
|
|
122
|
+
* 2. If max-row-selection has been reached, mark the remaining rows
|
|
123
|
+
* to reflect that they are not selected and disable them.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} state - datatable's state object
|
|
126
|
+
*/
|
|
127
|
+
export function markAllRowsSelected(state) {
|
|
128
|
+
const rows = getRows(state);
|
|
129
|
+
const maxRowSelection = getMaxRowSelection(state);
|
|
130
|
+
|
|
131
|
+
resetSelectedRowsKeys(state);
|
|
132
|
+
rows.forEach((row, index) => {
|
|
133
|
+
if (index < maxRowSelection || maxRowSelection === undefined) {
|
|
134
|
+
setRowSelectedAttributes(true, row);
|
|
135
|
+
addKeyToSelectedRowKeys(state, row.key);
|
|
136
|
+
} else {
|
|
137
|
+
row.isDisabled = true;
|
|
138
|
+
setRowSelectedAttributes(false, row);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Marks all rows as de-selected.
|
|
145
|
+
* Retrieve all rows from the state object. Iterate over these rows; for each row
|
|
146
|
+
* set `isSelected` and `ariaSelected` to false and enable the row. Also resolve
|
|
147
|
+
* the `classnames` for the row to reflect that it is not selected.
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} state - datatable's state object
|
|
150
|
+
* @returns
|
|
151
|
+
*/
|
|
152
|
+
export function markAllRowsDeselected(state) {
|
|
153
|
+
const rows = getRows(state);
|
|
154
|
+
|
|
155
|
+
resetSelectedRowsKeys(state);
|
|
156
|
+
rows.forEach((row) => {
|
|
157
|
+
row.isDisabled = false;
|
|
158
|
+
setRowSelectedAttributes(false, row);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Marks rows as selected for all rows within the interval.
|
|
164
|
+
* An interval is created when the user had initially selected a cell,
|
|
165
|
+
* then does a Shift + click on a different row selection checkbox.
|
|
166
|
+
* By doing so, all the rows (and checkboxes) between those two rows
|
|
167
|
+
* will all be selected.
|
|
168
|
+
*
|
|
169
|
+
* Note that this function is also used not only for intervals but also
|
|
170
|
+
* for single row selections. In that case, the startRowKey and
|
|
171
|
+
* endRowKey will both have the same value.
|
|
172
|
+
*
|
|
173
|
+
* This does not handle the case when the header checkbox that selects
|
|
174
|
+
* all rows of the table is clicked. That is handled by - `handleSelectAllRows`
|
|
175
|
+
*
|
|
176
|
+
* @param {Object} state - datatable's state object
|
|
177
|
+
* @param {String} startRowKey - row key value of the first row that was selected (start of the interval)
|
|
178
|
+
* @param {String} endRowKey - row key value of the last row that was selected (end of the interval)
|
|
179
|
+
*/
|
|
180
|
+
function markSelectedRowsInterval(state, startRowKey, endRowKey) {
|
|
181
|
+
const rows = getRows(state);
|
|
182
|
+
const { start, end } = getRowIntervalIndexes(state, startRowKey, endRowKey);
|
|
183
|
+
const maxRowSelection = getMaxRowSelection(state) || getRowsTotal(state);
|
|
184
|
+
let i = start,
|
|
185
|
+
maxSelectionReached;
|
|
186
|
+
|
|
187
|
+
while (i <= end && !maxSelectionReached) {
|
|
188
|
+
markRowSelected(state, rows[i].key);
|
|
189
|
+
maxSelectionReached =
|
|
190
|
+
getCurrentSelectionLength(state) >= maxRowSelection;
|
|
191
|
+
i++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Marks rows as de-selected for all rows within the interval.
|
|
197
|
+
* An interval for de-selection is created when the user ended selection or
|
|
198
|
+
* de-selects a row and then does a Shift + click on a row that was previously selected.
|
|
199
|
+
* By doing so, all the rows (and checkboxes) between the two rows will be de-selected
|
|
200
|
+
*
|
|
201
|
+
* Note that this function is also used not only for intervals but also
|
|
202
|
+
* for single row de-selections. In that case, the startRowKey and
|
|
203
|
+
* endRowKey will both have the same value.
|
|
204
|
+
*
|
|
205
|
+
* This does not handle the case when the header checkbox is clicked to de-select all rows
|
|
206
|
+
* That is handledd by - `handleDeselectAllRows`
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} state - datatable's state object
|
|
209
|
+
* @param {String} startRowKey - row key value of the first row that was selected (start of the interval)
|
|
210
|
+
* @param {String} endRowKey - row key value of the last row that was selected (end of the interval)
|
|
211
|
+
*/
|
|
212
|
+
function markDeselectedRowsInterval(state, startRowKey, endRowKey) {
|
|
213
|
+
const rows = getRows(state);
|
|
214
|
+
const { start, end } = getRowIntervalIndexes(state, startRowKey, endRowKey);
|
|
215
|
+
|
|
216
|
+
for (let i = start; i <= end; i++) {
|
|
217
|
+
markRowDeselected(state, rows[i].key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Marks a row with the specified row key value as selected. This is done by:
|
|
223
|
+
* 1. Sets `isSelected`, `ariaSelected` to true and resolves `classnames`
|
|
224
|
+
* to that which reflect that the row is selected, on the row object.
|
|
225
|
+
* These are used by the template to render the appropriate values.
|
|
226
|
+
* 2. If max-row-selection > 1 (checkbox/multi-selection),
|
|
227
|
+
* a. If with this selection, the max-row-selection value is reached,
|
|
228
|
+
* disable all the other un-selected rows
|
|
229
|
+
* b. Add the row key value of that row to the state
|
|
230
|
+
* 3. If max-row-selection = 1 (radio button selector),
|
|
231
|
+
* a. If another row was previously selected before, de-select that row
|
|
232
|
+
* b. Add the row key value of that row to the state
|
|
233
|
+
*
|
|
234
|
+
* @param {Object} state - datatable's state object
|
|
235
|
+
* @param {String} rowKeyValue - row key value of row to mark selected
|
|
236
|
+
*/
|
|
237
|
+
export function markRowSelected(state, rowKeyValue) {
|
|
238
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
239
|
+
const maxRowSelection = getMaxRowSelection(state) || getRowsTotal(state);
|
|
240
|
+
const previousSelectionLength = getCurrentSelectionLength(state);
|
|
241
|
+
|
|
242
|
+
setRowSelectedAttributes(true, row);
|
|
243
|
+
|
|
244
|
+
if (maxRowSelection > 1) {
|
|
245
|
+
addKeyToSelectedRowKeys(state, row.key);
|
|
246
|
+
if (previousSelectionLength + 1 === maxRowSelection) {
|
|
247
|
+
markDeselectedRowDisabled(state);
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (previousSelectionLength === 1) {
|
|
251
|
+
const previousSelectedRow = getRowByKey(
|
|
252
|
+
state,
|
|
253
|
+
Object.keys(state.selectedRowsKeys)[0]
|
|
254
|
+
);
|
|
255
|
+
setRowSelectedAttributes(false, previousSelectedRow);
|
|
256
|
+
resetSelectedRowsKeys(state);
|
|
257
|
+
}
|
|
258
|
+
addKeyToSelectedRowKeys(state, row.key);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Marks a row with the specified row key value as de-selected. This is done by:
|
|
264
|
+
* 1. Sets `isSelected`, `ariaSelected` to false and resolves `classnames`
|
|
265
|
+
* to that which reflect that the row is not selected, on the row object.
|
|
266
|
+
* These are used by the template to render the appropriate values.
|
|
267
|
+
* 2. The row key value of this row is removed from list of selected row keys
|
|
268
|
+
* 3. Before the current de-selection, if the remaining unselected rows were
|
|
269
|
+
* disabled (max-row-selection was reached), enable all unselected rows
|
|
270
|
+
* so that they can be selected.
|
|
271
|
+
*
|
|
272
|
+
* @param {Object} state - datatable's state object
|
|
273
|
+
* @param {String} rowKeyValue - row key value of row to mark selected
|
|
274
|
+
*/
|
|
275
|
+
export function markRowDeselected(state, rowKeyValue) {
|
|
276
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
277
|
+
const maxRowSelection = getMaxRowSelection(state);
|
|
278
|
+
|
|
279
|
+
setRowSelectedAttributes(false, row);
|
|
280
|
+
removeKeyFromSelectedRowKeys(state, row.key);
|
|
281
|
+
|
|
282
|
+
if (getCurrentSelectionLength(state) === maxRowSelection - 1) {
|
|
283
|
+
markDeselectedRowEnabled(state);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Iterates over the row key values passed in and sets the relevant
|
|
289
|
+
* values on the row object to reflect that it is selected.
|
|
290
|
+
* Sets `isSelected`, `ariaSelected` and `classnames` on the row object
|
|
291
|
+
* which are used by the template to render the appropriate values.
|
|
292
|
+
*
|
|
293
|
+
* @param {Object} state - datatable's state object
|
|
294
|
+
* @param {Array} keys - a list of row key values to be marked selected
|
|
295
|
+
*/
|
|
296
|
+
function markRowsSelectedByKeys(state, keys) {
|
|
297
|
+
keys.forEach((rowKeyValue) => {
|
|
298
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
299
|
+
setRowSelectedAttributes(true, row);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Iterates over the row key values passed in and un-sets the relevant
|
|
305
|
+
* values on the row object to reflect that it is not selected.
|
|
306
|
+
* Sets `isSelected`, `ariaSelected` to false and resolves `classnames`
|
|
307
|
+
* to one which reflects that the row is not selected on the row object.
|
|
308
|
+
* These are used by the template to render the appropriate values.
|
|
309
|
+
*
|
|
310
|
+
* @param {Object} state - datatable's state object
|
|
311
|
+
* @param {Array} keys - a list of row key values to be marked selected
|
|
312
|
+
*/
|
|
313
|
+
function markRowsDeselectedByKeys(state, keys) {
|
|
314
|
+
keys.forEach((rowKeyValue) => {
|
|
315
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
316
|
+
setRowSelectedAttributes(false, row);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Marks all deselected rows as disabled. This prevents the user
|
|
322
|
+
* from selecting any more rows.
|
|
323
|
+
* This is typically called once the selection has reached the
|
|
324
|
+
* max-row-selection value and no more rows are allowed to be selected.
|
|
325
|
+
*
|
|
326
|
+
* @param {Object} state - datatable's state object
|
|
327
|
+
*/
|
|
328
|
+
export function markDeselectedRowDisabled(state) {
|
|
329
|
+
const rows = getRows(state);
|
|
330
|
+
rows.forEach((row) => {
|
|
331
|
+
if (!isSelectedRow(state, row.key)) {
|
|
332
|
+
row.isDisabled = true;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Marks all the deselected rows as enabled. This allows the user
|
|
339
|
+
* to select more rows.
|
|
340
|
+
* This is typically called when the maximum number of rows were
|
|
341
|
+
* previously selected but a row was deselected, now allowing
|
|
342
|
+
* any other row to be selected - for this, all rows should be enabled
|
|
343
|
+
*
|
|
344
|
+
* @param {Object} state - datatable's state object
|
|
345
|
+
*/
|
|
346
|
+
export function markDeselectedRowEnabled(state) {
|
|
347
|
+
const rows = getRows(state);
|
|
348
|
+
rows.forEach((row) => {
|
|
349
|
+
if (!isSelectedRow(state, row.key)) {
|
|
350
|
+
row.isDisabled = false;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/************************** SELECTED ROW KEYS **************************/
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* This is called when the `selected-rows` attribute is set on the datatable
|
|
359
|
+
* The 'value' parameter should be an Array of key-field values. If it is not an array,
|
|
360
|
+
* we throw an error stating so and all rows are de-selected.
|
|
361
|
+
*
|
|
362
|
+
* If the 'value' param is valid, we filter the 'value' array for only the valid keys.
|
|
363
|
+
* If the number of valid keys exceeds the max-row-selection value, we use only the
|
|
364
|
+
* number of keys from the valid list as that of the max-row-selection value.
|
|
365
|
+
*
|
|
366
|
+
* Compute the differences between the currently selected rows vs
|
|
367
|
+
* the newly selected rows to find out which rows need to be additionally
|
|
368
|
+
* selected and de-selected.
|
|
369
|
+
* Based on the above computation, mark rows as selected or de-selected and
|
|
370
|
+
* set the new `selectedRowsKeys` in the state object.
|
|
371
|
+
*
|
|
372
|
+
* If we select the max number of rows allowed and if max-row-selection > 1 (multi-select),
|
|
373
|
+
* disable all the other rows.
|
|
374
|
+
* If the previous selection had reached the max limit and the new selection
|
|
375
|
+
* is less than the limit, re-enable the de-selected rows to allow for new selection.
|
|
376
|
+
*
|
|
377
|
+
* @param {Object} state - datatable's state object
|
|
378
|
+
* @param {Array} value - key-field values of rows to set as selected
|
|
379
|
+
*/
|
|
380
|
+
export function setSelectedRowsKeys(state, value) {
|
|
381
|
+
if (Array.isArray(value)) {
|
|
382
|
+
const maxRowSelection = getMaxRowSelection(state);
|
|
383
|
+
const previousSelectionLength = getCurrentSelectionLength(state);
|
|
384
|
+
let selectedRows = filterValidKeys(state, value);
|
|
385
|
+
if (selectedRows.length > maxRowSelection) {
|
|
386
|
+
// eslint-disable-next-line no-console
|
|
387
|
+
console.warn(`The number of keys in selectedRows for lightning:datatable
|
|
388
|
+
exceeds the limit defined by maxRowSelection.`);
|
|
389
|
+
selectedRows = selectedRows.slice(0, maxRowSelection);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Convert the selectedRows Array to an Object that state.selectedRowKeys expects
|
|
393
|
+
// ['a', 'b'] -> { a : true, b : true}
|
|
394
|
+
const normalizedSelectedRowsKeys =
|
|
395
|
+
normalizeSelectedRowsKey(selectedRows);
|
|
396
|
+
|
|
397
|
+
// Compute differences between currently selected rows and
|
|
398
|
+
// newly selected row keys. The diff will tell which new rows
|
|
399
|
+
// need to be selected and which already selected rows need to
|
|
400
|
+
// be deselected
|
|
401
|
+
const selectionOperations = getSelectedDiff(state, selectedRows);
|
|
402
|
+
const deselectionOperations = getDeselectedDiff(
|
|
403
|
+
state,
|
|
404
|
+
normalizedSelectedRowsKeys
|
|
405
|
+
);
|
|
406
|
+
markRowsSelectedByKeys(state, selectionOperations);
|
|
407
|
+
markRowsDeselectedByKeys(state, deselectionOperations);
|
|
408
|
+
state.selectedRowsKeys = normalizedSelectedRowsKeys;
|
|
409
|
+
|
|
410
|
+
// If we select the max number of rows allowed and if max-row-selection > 1 (multi-select),
|
|
411
|
+
// disable all the other rows to prevent further selection
|
|
412
|
+
if (selectedRows.length === maxRowSelection && maxRowSelection > 1) {
|
|
413
|
+
markDeselectedRowDisabled(state);
|
|
414
|
+
} else if (
|
|
415
|
+
selectedRows.length < maxRowSelection &&
|
|
416
|
+
previousSelectionLength === maxRowSelection
|
|
417
|
+
) {
|
|
418
|
+
// If the previous selection had reached the max limit and the new selection
|
|
419
|
+
// is less than the limit, re-enable the de-selected rows to allow for new selection.
|
|
420
|
+
markDeselectedRowEnabled(state);
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
// eslint-disable-next-line no-console
|
|
424
|
+
console.error(`The "selectedRows" passed into "lightning:datatable"
|
|
425
|
+
must be an Array with the keys of the selected rows. We receive instead ${value}`);
|
|
426
|
+
markAllRowsDeselected(state);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function syncSelectedRowsKeys(state, selectedRows) {
|
|
431
|
+
let changed = false;
|
|
432
|
+
const { selectedRowsKeys, keyField } = state;
|
|
433
|
+
|
|
434
|
+
if (Object.keys(selectedRowsKeys).length !== selectedRows.length) {
|
|
435
|
+
changed = true;
|
|
436
|
+
state.selectedRowsKeys = updateSelectedRowsKeysFromSelectedRows(
|
|
437
|
+
selectedRows,
|
|
438
|
+
keyField
|
|
439
|
+
);
|
|
440
|
+
} else {
|
|
441
|
+
changed = selectedRows.some((row) => !selectedRowsKeys[row[keyField]]);
|
|
442
|
+
if (changed) {
|
|
443
|
+
state.selectedRowsKeys = updateSelectedRowsKeysFromSelectedRows(
|
|
444
|
+
selectedRows,
|
|
445
|
+
keyField
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
updateBulkSelectionState(state);
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
ifChanged: (callback) => {
|
|
454
|
+
if (changed && typeof callback === 'function') {
|
|
455
|
+
callback(selectedRows);
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function updateSelectedRowsKeysFromSelectedRows(selectedRows, keyField) {
|
|
462
|
+
return selectedRows.reduce((selectedRowsKeys, row) => {
|
|
463
|
+
selectedRowsKeys[row[keyField]] = true;
|
|
464
|
+
return selectedRowsKeys;
|
|
465
|
+
}, {});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function addKeyToSelectedRowKeys(state, key) {
|
|
469
|
+
state.selectedRowsKeys[key] = true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function removeKeyFromSelectedRowKeys(state, key) {
|
|
473
|
+
// not using delete this.state.selectedRowsKeys[key]
|
|
474
|
+
// because that causes perf issues
|
|
475
|
+
state.selectedRowsKeys[key] = false;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function normalizeSelectedRowsKey(value) {
|
|
479
|
+
return value.reduce((map, key) => {
|
|
480
|
+
map[key] = true;
|
|
481
|
+
return map;
|
|
482
|
+
}, {});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function filterValidKeys(state, keys) {
|
|
486
|
+
return keys.filter((key) => rowKeyExists(state, key));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function resetSelectedRowsKeys(state) {
|
|
490
|
+
state.selectedRowsKeys = {};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/************************** LAST SELECTED ROW KEYS **************************/
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Returns the row key value of the row that was last selected
|
|
497
|
+
* Returns undefined if the row key value is invalid
|
|
498
|
+
*
|
|
499
|
+
* @param {Object} state - the datatable state.
|
|
500
|
+
* @return {String | undefined } the row key or undefined.
|
|
501
|
+
*/
|
|
502
|
+
function getLastRowSelection(state) {
|
|
503
|
+
const lastSelectedRowKey = state.lastSelectedRowKey;
|
|
504
|
+
const keyIsValid =
|
|
505
|
+
lastSelectedRowKey !== undefined &&
|
|
506
|
+
getRowIndexByKey(state, lastSelectedRowKey) !== undefined;
|
|
507
|
+
|
|
508
|
+
return keyIsValid ? lastSelectedRowKey : undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function setLastRowSelection(state, rowKeyValue) {
|
|
512
|
+
state.lastSelectedRowKey = rowKeyValue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/************************** INPUT TYPES **************************/
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Computes whether or not the input type rendered for row selection needs to change
|
|
519
|
+
* The input type may need to change if:
|
|
520
|
+
* 1. The max-row-selection value was previously 1 and is now either
|
|
521
|
+
* greater than 1 or is undefined OR
|
|
522
|
+
* 2. The max-row-selection value was previously greater than 1 or undefined
|
|
523
|
+
* and is now 1 OR
|
|
524
|
+
* 3. Previous max-row-selection value was 0 OR
|
|
525
|
+
* 4. New max-row-selection value is 0
|
|
526
|
+
*
|
|
527
|
+
* @param {Number} previousMaxRowSelection
|
|
528
|
+
* @param {Number} newMaxRowSelection
|
|
529
|
+
* @returns
|
|
530
|
+
*/
|
|
531
|
+
export function inputTypeNeedsToChange(
|
|
532
|
+
previousMaxRowSelection,
|
|
533
|
+
newMaxRowSelection
|
|
534
|
+
) {
|
|
535
|
+
return (
|
|
536
|
+
(previousMaxRowSelection === 1 &&
|
|
537
|
+
isMultiSelection(newMaxRowSelection)) ||
|
|
538
|
+
(isMultiSelection(previousMaxRowSelection) &&
|
|
539
|
+
newMaxRowSelection === 1) ||
|
|
540
|
+
previousMaxRowSelection === 0 ||
|
|
541
|
+
newMaxRowSelection === 0
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function updateRowSelectionInputType(state) {
|
|
546
|
+
const type = getRowSelectionInputType(state);
|
|
547
|
+
const rows = getRows(state);
|
|
548
|
+
|
|
549
|
+
rows.forEach((row) => {
|
|
550
|
+
row.inputType = type;
|
|
551
|
+
row.isDisabled = isDisabledRow(state, row.key);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/************************** MAX ROW SELECTION **************************/
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Sets maxRowSelection to the provided value,
|
|
559
|
+
* only keeping up to maxRowSelection values selected.
|
|
560
|
+
*
|
|
561
|
+
* Use input type checkbox if maxRowSelection > 1
|
|
562
|
+
* and input type is radio if maxRowSelection = 1.
|
|
563
|
+
* Invalid values are set to default and an error is logged
|
|
564
|
+
*
|
|
565
|
+
* @param {Object} state - the datatable state.
|
|
566
|
+
* @param {Number | String} - value to set for maxRowSelection
|
|
567
|
+
*/
|
|
568
|
+
export function setMaxRowSelection(state, value) {
|
|
569
|
+
const previousSelectedRowsKeys = getSelectedRowsKeys(state);
|
|
570
|
+
markAllRowsDeselected(state);
|
|
571
|
+
if (isNonNegativeInteger(value)) {
|
|
572
|
+
const previousMaxRowSelection = getMaxRowSelection(state);
|
|
573
|
+
state.maxRowSelection = Number(value);
|
|
574
|
+
const newMaxRowSelection = getMaxRowSelection(state);
|
|
575
|
+
// reselect up to maxRowSelection rows
|
|
576
|
+
const numberOfRows = Math.min(
|
|
577
|
+
previousSelectedRowsKeys.length,
|
|
578
|
+
newMaxRowSelection
|
|
579
|
+
);
|
|
580
|
+
for (let i = 0; i < numberOfRows; i++) {
|
|
581
|
+
markRowSelected(state, previousSelectedRowsKeys[i]);
|
|
582
|
+
}
|
|
583
|
+
if (
|
|
584
|
+
inputTypeNeedsToChange(
|
|
585
|
+
previousMaxRowSelection,
|
|
586
|
+
getMaxRowSelection(state)
|
|
587
|
+
)
|
|
588
|
+
) {
|
|
589
|
+
updateRowSelectionInputType(state);
|
|
590
|
+
updateBulkSelectionState(state);
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
state.maxRowSelection = MAX_ROW_SELECTION_DEFAULT;
|
|
594
|
+
// eslint-disable-next-line no-console
|
|
595
|
+
console.error(
|
|
596
|
+
`The maxRowSelection value passed into lightning:datatable
|
|
597
|
+
should be a positive integer. We receive instead (${value}).`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/************************** BULK SELECTION STATE **************************/
|
|
603
|
+
|
|
604
|
+
export function updateBulkSelectionState(state) {
|
|
605
|
+
const selectBoxesColumnIndex = getSelectBoxesColumnIndex(state);
|
|
606
|
+
if (selectBoxesColumnIndex >= 0) {
|
|
607
|
+
state.columns[selectBoxesColumnIndex] = Object.assign(
|
|
608
|
+
{},
|
|
609
|
+
state.columns[selectBoxesColumnIndex],
|
|
610
|
+
{
|
|
611
|
+
bulkSelection: getBulkSelectionState(state),
|
|
612
|
+
isBulkSelectionDisabled: isBulkSelectionDisabled(state),
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function getBulkSelectionState(state) {
|
|
619
|
+
const selected = getCurrentSelectionLength(state);
|
|
620
|
+
const total = getMaxRowSelection(state) || getRowsTotal(state);
|
|
621
|
+
if (selected === 0) {
|
|
622
|
+
return 'none';
|
|
623
|
+
} else if (selected === total) {
|
|
624
|
+
return 'all';
|
|
625
|
+
}
|
|
626
|
+
return 'some';
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function isBulkSelectionDisabled(state) {
|
|
630
|
+
return getRowsTotal(state) === 0 || getMaxRowSelection(state) === 0;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/************************** HELPER FUNCTIONS **************************/
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Gets the interval of row indexes based on the start and end row key values
|
|
637
|
+
* Retrieves the index of the row with startRowKey and the same with endRowKey
|
|
638
|
+
* Returns an object that contains the start index which is the lower index value of the two
|
|
639
|
+
* and the end index which is the higher value of the two.
|
|
640
|
+
*
|
|
641
|
+
* @param {Object} state - datatable's state object
|
|
642
|
+
* @param {String} startRowKey - row key value of the first row that was selected (start of the interval)
|
|
643
|
+
* @param {String} endRowKey - row key value of the last row that was selected (end of the interval)
|
|
644
|
+
* @returns {Object} - object with start index and end index
|
|
645
|
+
*/
|
|
646
|
+
function getRowIntervalIndexes(state, startRowKey, endRowKey) {
|
|
647
|
+
const start =
|
|
648
|
+
startRowKey === 'HEADER' ? 0 : getRowIndexByKey(state, startRowKey);
|
|
649
|
+
const end = getRowIndexByKey(state, endRowKey);
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
start: Math.min(start, end),
|
|
653
|
+
end: Math.max(start, end),
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Sets aria-selected to 'true' on the cell identified by the rowKeyValue and colKeyValue.
|
|
659
|
+
* This will reflect on the td or th or corresponding element in the role-based table.
|
|
660
|
+
*
|
|
661
|
+
* Note: This change is volatile, and will be reset (lost) in the next index regeneration.
|
|
662
|
+
*
|
|
663
|
+
* @param {Object} state - the state of the datatable
|
|
664
|
+
* @param {String} rowKeyValue - the row key of the cell to mark selected
|
|
665
|
+
* @param {String} colKeyValue - the col key of the cell to mark selected
|
|
666
|
+
*/
|
|
667
|
+
export function setAriaSelectedOnCell(state, rowKeyValue, colKeyValue) {
|
|
668
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
669
|
+
const colIndex = getStateColumnIndex(state, colKeyValue);
|
|
670
|
+
|
|
671
|
+
if (row && colIndex) {
|
|
672
|
+
row.cells[colIndex].ariaSelected = 'true';
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Sets aria-selected to 'false' on the cell identified by the rowKeyValue and colKeyValue.
|
|
678
|
+
* This aria-selected attribute will be removed from the cell (if it was previously added))
|
|
679
|
+
* on the td or th or the corresponding element in the role-based table.
|
|
680
|
+
*
|
|
681
|
+
* Note: This change is volatile, and will be reset (lost) in the next index regeneration.
|
|
682
|
+
*
|
|
683
|
+
* @param {Object} state - the state of the datatable
|
|
684
|
+
* @param {String} rowKeyValue - the row key of the cell to select
|
|
685
|
+
* @param {String} colKeyValue - the col key of the cell to select
|
|
686
|
+
*/
|
|
687
|
+
export function unsetAriaSelectedOnCell(state, rowKeyValue, colKeyValue) {
|
|
688
|
+
const row = getRowByKey(state, rowKeyValue);
|
|
689
|
+
const colIndex = getStateColumnIndex(state, colKeyValue);
|
|
690
|
+
|
|
691
|
+
if (row && colIndex) {
|
|
692
|
+
row.cells[colIndex].ariaSelected = false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Sets `isSelected`, `ariaSelected` to true | false and resolves `classnames`
|
|
698
|
+
* to one which reflects the selected value of the row on the row object.
|
|
699
|
+
* These are used by the template to render the appropriate values.
|
|
700
|
+
*
|
|
701
|
+
* @param {Boolean} selectedValue - is the row selected or not
|
|
702
|
+
* @param {Object} row - the row on which to set the selected attributes
|
|
703
|
+
*/
|
|
704
|
+
function setRowSelectedAttributes(selectedValue, row) {
|
|
705
|
+
row.isSelected = selectedValue;
|
|
706
|
+
row.ariaSelected = selectedValue;
|
|
707
|
+
row.classnames = resolveRowClassNames(row);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function getSelectedDiff(state, value) {
|
|
711
|
+
const selectedRowsKeys = state.selectedRowsKeys;
|
|
712
|
+
return value.filter((key) => !selectedRowsKeys[key]);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function getDeselectedDiff(state, value) {
|
|
716
|
+
const currentSelectedRowsKeys = state.selectedRowsKeys;
|
|
717
|
+
return Object.keys(currentSelectedRowsKeys).filter(
|
|
718
|
+
(key) => currentSelectedRowsKeys[key] && !value[key]
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function getSelectBoxesColumnIndex(state) {
|
|
723
|
+
const columns = getColumns(state) || [];
|
|
724
|
+
let selectBoxColumnIndex = -1;
|
|
725
|
+
|
|
726
|
+
columns.some((column, index) => {
|
|
727
|
+
if (column.type === SELECTABLE_ROW_CHECKBOX) {
|
|
728
|
+
selectBoxColumnIndex = index;
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return false;
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
return selectBoxColumnIndex;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Represents whether the select all rows checkbox on the header
|
|
740
|
+
* should be visible or not.
|
|
741
|
+
* If max-row-selection = 1, then the select all rows checkbox
|
|
742
|
+
* should not be visible/rendered.
|
|
743
|
+
*
|
|
744
|
+
* @param {Object} state - datatable's state object
|
|
745
|
+
* @returns
|
|
746
|
+
*/
|
|
747
|
+
export function getHideSelectAllCheckbox(state) {
|
|
748
|
+
return getMaxRowSelection(state) === 1;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Represents whether the datatable should enable multiselection or not
|
|
753
|
+
* depending on the max-row-selection value passed in
|
|
754
|
+
*
|
|
755
|
+
* @param {Number} value - max-row-selection value
|
|
756
|
+
* @returns
|
|
757
|
+
*/
|
|
758
|
+
function isMultiSelection(value) {
|
|
759
|
+
return value > 1 || value === undefined;
|
|
760
|
+
}
|