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
|
@@ -8,14 +8,16 @@ import {
|
|
|
8
8
|
reactToTabBackward,
|
|
9
9
|
reactToTabForward,
|
|
10
10
|
getActiveCellElement,
|
|
11
|
-
getCellElementByIndexes,
|
|
12
11
|
updateActiveCell,
|
|
12
|
+
isActiveCellEditable,
|
|
13
|
+
isValidCell,
|
|
13
14
|
} from './keyboard';
|
|
14
15
|
import {
|
|
15
16
|
updateRowsAndCellIndexes,
|
|
16
17
|
getRowByKey,
|
|
17
18
|
getKeyField,
|
|
18
19
|
getUserRowByCellKeys,
|
|
20
|
+
isCellEditable,
|
|
19
21
|
} from './rows';
|
|
20
22
|
import {
|
|
21
23
|
getColumnIndexByColumnKey,
|
|
@@ -25,56 +27,143 @@ import {
|
|
|
25
27
|
} from './columns';
|
|
26
28
|
import { setErrors } from './errors';
|
|
27
29
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
setAriaSelectedOnCell,
|
|
31
|
+
unsetAriaSelectedOnCell,
|
|
30
32
|
isSelectedRow,
|
|
31
33
|
getCurrentSelectionLength,
|
|
32
34
|
getSelectedRowsKeys,
|
|
33
|
-
} from './
|
|
35
|
+
} from './rowSelection';
|
|
34
36
|
import { isObjectLike } from './utils';
|
|
35
37
|
|
|
36
|
-
export {
|
|
38
|
+
export { getDirtyValueFromCell } from './inlineEditShared';
|
|
37
39
|
|
|
38
|
-
const
|
|
40
|
+
const IEDIT_PANEL_SELECTOR = '[data-iedit-panel="true"]';
|
|
41
|
+
const HIDE_PANEL_THRESHOLD = 5; // hide panel on scroll
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
/************************** EVENT HANDLERS **************************/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Event handler to open/start the inline edit flows that are triggered by datatable cells
|
|
47
|
+
*
|
|
48
|
+
* @param {CustomEvent} event - An object representing the event that was fired by the datatable cell for
|
|
49
|
+
* which to open the inline edit panel. Must be valid and truthy.
|
|
50
|
+
*/
|
|
51
|
+
export function handleEditCell(event) {
|
|
52
|
+
openInlineEdit(this, event.target);
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
56
|
+
* Handles the completion of inline edit.
|
|
57
|
+
* Closes and destroys the panel and processes completion of the edit
|
|
58
|
+
*
|
|
59
|
+
* @param {CustomEvent} event - `ieditfinished`
|
|
52
60
|
*/
|
|
53
|
-
export function
|
|
54
|
-
|
|
61
|
+
export function handleInlineEditFinish(event) {
|
|
62
|
+
stopPanelPositioning(this);
|
|
63
|
+
|
|
64
|
+
const { reason, rowKeyValue, colKeyValue } = event.detail;
|
|
65
|
+
|
|
66
|
+
processInlineEditFinish(this, reason, rowKeyValue, colKeyValue);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
/**
|
|
58
|
-
* Sets the
|
|
70
|
+
* Sets the `aria-selected` value on the cell based on the checked value
|
|
71
|
+
* If the mass update checkbox is checked, set aria-selected on those cells
|
|
72
|
+
* which are to be updated to true
|
|
73
|
+
* If not, set aria-selected to true on only the cell that is being edited
|
|
74
|
+
*/
|
|
75
|
+
export function handleMassCheckboxChange(event) {
|
|
76
|
+
const state = this.state;
|
|
77
|
+
if (event.detail.checked) {
|
|
78
|
+
setAriaSelectedOnAllSelectedRows(state);
|
|
79
|
+
} else {
|
|
80
|
+
unsetAriaSelectedOnAllSelectedRows(this.state);
|
|
81
|
+
setAriaSelectedOnCell(
|
|
82
|
+
state,
|
|
83
|
+
state.inlineEdit.rowKeyValue,
|
|
84
|
+
state.inlineEdit.colKeyValue
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handles management of the inline edit panel when user scrolls horizontally or vertically.
|
|
91
|
+
* On either horizontal or vertical scroll:
|
|
92
|
+
* - If the user scrolls past the pre-determined threshold,
|
|
93
|
+
* hide the inline edit panel and process the completion of inline edit.
|
|
94
|
+
* - If the user scrolls within the pre-determined threshold,
|
|
95
|
+
* keep the panel open but reposition it to align with the cell
|
|
59
96
|
*
|
|
60
|
-
* @param {
|
|
61
|
-
* @
|
|
62
|
-
* A special key is the { [keyField]: value } pair used to identify the row containing this changed values.
|
|
97
|
+
* @param {Event} event - `scroll`
|
|
98
|
+
* @returns
|
|
63
99
|
*/
|
|
64
|
-
export function
|
|
65
|
-
const
|
|
66
|
-
const dirtyValues = Array.isArray(value) ? value : [];
|
|
100
|
+
export function handleInlineEditPanelScroll(event) {
|
|
101
|
+
const { isPanelVisible, rowKeyValue, colKeyValue } = this.state.inlineEdit;
|
|
67
102
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
103
|
+
if (!isPanelVisible) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
71
106
|
|
|
72
|
-
|
|
107
|
+
let delta = 0;
|
|
108
|
+
const container = event.target;
|
|
73
109
|
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
// When user scrolls horizontally
|
|
111
|
+
if (container.classList.contains('slds-scrollable_x')) {
|
|
112
|
+
const scrollX = container.scrollLeft;
|
|
113
|
+
if (this.privateLastScrollX == null) {
|
|
114
|
+
this.privateLastScrollX = scrollX;
|
|
115
|
+
} else {
|
|
116
|
+
delta = Math.abs(this.privateLastScrollX - scrollX);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// When user scrolls vertically
|
|
120
|
+
const scrollY = container.scrollTop;
|
|
121
|
+
if (this.privateLastScrollY == null) {
|
|
122
|
+
this.privateLastScrollY = scrollY;
|
|
123
|
+
} else {
|
|
124
|
+
delta = Math.abs(this.privateLastScrollY - scrollY);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If user has scrolled past threshold,
|
|
129
|
+
// reset stored scroll values, hide panel and
|
|
130
|
+
// process inline edit completion
|
|
131
|
+
if (delta > HIDE_PANEL_THRESHOLD) {
|
|
132
|
+
this.privateLastScrollX = null;
|
|
133
|
+
this.privateLastScrollY = null;
|
|
134
|
+
stopPanelPositioning(this);
|
|
135
|
+
processInlineEditFinish(this, 'lost-focus', rowKeyValue, colKeyValue);
|
|
136
|
+
} else {
|
|
137
|
+
// we want to keep the panel attached to the cell before
|
|
138
|
+
// reaching the threshold and hiding the panel
|
|
139
|
+
repositionPanel(this);
|
|
140
|
+
}
|
|
76
141
|
}
|
|
77
142
|
|
|
143
|
+
/************************** EVENT DISPATCHER **************************/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Dispatches the `cellchange` event with the `draftValues` in the
|
|
147
|
+
* detail object.
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} dtInstance - datatable instance
|
|
150
|
+
* @param {Object} cellChange - object containing cell changes
|
|
151
|
+
*/
|
|
152
|
+
function dispatchCellChangeEvent(dtInstance, cellChange) {
|
|
153
|
+
dtInstance.dispatchEvent(
|
|
154
|
+
new CustomEvent('cellchange', {
|
|
155
|
+
detail: {
|
|
156
|
+
draftValues: getResolvedCellChanges(
|
|
157
|
+
dtInstance.state,
|
|
158
|
+
cellChange
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/************************** INLINE EDIT STATE MANAGEMENT **************************/
|
|
166
|
+
|
|
78
167
|
export function isInlineEditTriggered(state) {
|
|
79
168
|
return Object.keys(state.inlineEdit.dirtyValues).length > 0;
|
|
80
169
|
}
|
|
@@ -85,102 +174,129 @@ export function cancelInlineEdit(dt) {
|
|
|
85
174
|
updateRowsAndCellIndexes.call(dt);
|
|
86
175
|
}
|
|
87
176
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
177
|
+
export function closeInlineEdit(dt) {
|
|
178
|
+
const inlineEditState = dt.state.inlineEdit;
|
|
179
|
+
|
|
180
|
+
if (inlineEditState.isPanelVisible) {
|
|
181
|
+
processInlineEditFinish(
|
|
182
|
+
dt,
|
|
183
|
+
'lost-focus',
|
|
184
|
+
inlineEditState.rowKeyValue,
|
|
185
|
+
inlineEditState.colKeyValue
|
|
186
|
+
);
|
|
187
|
+
}
|
|
96
188
|
}
|
|
97
189
|
|
|
98
190
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
191
|
+
* Handles processing when the datatable has finished an inline edit flow.
|
|
192
|
+
* Evaluates if data from the inline edit panel should be saved or not.
|
|
193
|
+
* Data should be saved
|
|
194
|
+
* - if inline edit was not canceled by the user and
|
|
195
|
+
* - if in mass inline edit, the 'Apply' button is clicked (don't save when focus is lost) and
|
|
196
|
+
* - if the cell being edited is a valid cell
|
|
102
197
|
*
|
|
103
|
-
* If
|
|
104
|
-
*
|
|
198
|
+
* If the data should be saved, check that the value has changed or if mass edit is enabled.
|
|
199
|
+
* If so, one or more cells need to reflect the updated value.
|
|
200
|
+
* All changes to the cell(s) (`cellChange`) are stored in the following format:
|
|
201
|
+
* cellChange = {
|
|
202
|
+
* rowKeyValue1: {
|
|
203
|
+
* colKeyValue: 'changed value'
|
|
204
|
+
* },
|
|
205
|
+
* rowKeyValue2: {
|
|
206
|
+
* colKeyValue: 'changed value'
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
105
209
|
*
|
|
106
|
-
*
|
|
210
|
+
* The above cell changes are used to update state.inlineEdit.dirtyValues.
|
|
211
|
+
* The draft values are retrieved using the cell changes that were gathered here and
|
|
212
|
+
* the `cellchange` event is dispatched passing the draftValues in the detail object.
|
|
213
|
+
*
|
|
214
|
+
* If the user inline edit panel lost focus, the datatable should react accordingly.
|
|
215
|
+
*
|
|
216
|
+
* @param {Object} dt - datatable instance
|
|
217
|
+
* @param {string} reason - reason to finish the edit; valid reasons are: edit-canceled | lost-focus | tab-pressed | submit-action
|
|
218
|
+
* @param {string} rowKeyValue - row key of the edited cell
|
|
219
|
+
* @param {string} colKeyValue - column key of the edited cell
|
|
107
220
|
*/
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
221
|
+
function processInlineEditFinish(dt, reason, rowKeyValue, colKeyValue) {
|
|
222
|
+
const state = dt.state;
|
|
223
|
+
const inlineEditState = state.inlineEdit;
|
|
224
|
+
|
|
225
|
+
const shouldSaveData =
|
|
226
|
+
reason !== 'edit-canceled' &&
|
|
227
|
+
!(inlineEditState.massEditEnabled && reason === 'lost-focus') &&
|
|
228
|
+
isValidCell(dt.state, rowKeyValue, colKeyValue);
|
|
229
|
+
|
|
230
|
+
if (shouldSaveData) {
|
|
231
|
+
const panel = dt.template.querySelector(IEDIT_PANEL_SELECTOR);
|
|
232
|
+
const editValue = panel.value;
|
|
233
|
+
const isValidEditValue = panel.validity.valid;
|
|
234
|
+
const updateAllSelectedRows = panel.isMassEditChecked;
|
|
235
|
+
const currentValue = getCellValue(state, rowKeyValue, colKeyValue);
|
|
236
|
+
|
|
237
|
+
if (
|
|
238
|
+
isValidEditValue &&
|
|
239
|
+
(editValue !== currentValue || updateAllSelectedRows)
|
|
240
|
+
) {
|
|
241
|
+
const cellChange = {};
|
|
242
|
+
cellChange[rowKeyValue] = {};
|
|
243
|
+
cellChange[rowKeyValue][colKeyValue] = editValue;
|
|
244
|
+
|
|
245
|
+
if (updateAllSelectedRows) {
|
|
246
|
+
const selectedRowKeys = getSelectedRowsKeys(state);
|
|
247
|
+
selectedRowKeys.forEach((rowKey) => {
|
|
248
|
+
cellChange[rowKey] = {};
|
|
249
|
+
cellChange[rowKey][colKeyValue] = editValue;
|
|
250
|
+
});
|
|
124
251
|
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
252
|
|
|
129
|
-
|
|
130
|
-
* async function to await setting focus on an editable cell before opening inline-edit panel
|
|
131
|
-
* @param {Object} dt - The datatable instance
|
|
132
|
-
* @param {Object} cell - editable cell to be focused before open inline-edit panel
|
|
133
|
-
*/
|
|
134
|
-
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
135
|
-
async function setFocusAndOpenInlineEdit(dt, cell) {
|
|
136
|
-
await setFocusActiveCell(dt.template, dt.state, 0);
|
|
137
|
-
openInlineEdit(dt, cell);
|
|
138
|
-
}
|
|
253
|
+
updateDirtyValues(state, cellChange);
|
|
139
254
|
|
|
140
|
-
|
|
141
|
-
* Returns a reference to the first editable cell in the table. If no editable cells exist in the table
|
|
142
|
-
* then undefined is returned.
|
|
143
|
-
*
|
|
144
|
-
* @param {Object} dt - The datatable instance. Must be a truthy and valid datatable reference.
|
|
145
|
-
*/
|
|
146
|
-
function getFirstEditableCell(dt) {
|
|
147
|
-
const columns = getColumns(dt.state);
|
|
148
|
-
const editableColumns = getEditableColumns(columns);
|
|
255
|
+
dispatchCellChangeEvent(dt, cellChange);
|
|
149
256
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return cell;
|
|
168
|
-
}
|
|
257
|
+
// TODO: do we need to update all rows in the dt or just the one that was modified?
|
|
258
|
+
updateRowsAndCellIndexes.call(dt);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (reason !== 'lost-focus') {
|
|
263
|
+
switch (reason) {
|
|
264
|
+
case 'tab-pressed-next': {
|
|
265
|
+
reactToTabForward(dt.template, state);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case 'tab-pressed-prev': {
|
|
269
|
+
reactToTabBackward(dt.template, state);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
default: {
|
|
273
|
+
setFocusActiveCell(dt.template, state, 0);
|
|
169
274
|
}
|
|
170
275
|
}
|
|
171
276
|
}
|
|
172
277
|
|
|
173
|
-
|
|
278
|
+
unsetAriaSelectedOnAllSelectedRows(state);
|
|
279
|
+
unsetAriaSelectedOnCell(state, rowKeyValue, colKeyValue);
|
|
280
|
+
|
|
281
|
+
inlineEditState.isPanelVisible = false;
|
|
174
282
|
}
|
|
175
283
|
|
|
284
|
+
/************************** INLINE EDIT **************************/
|
|
285
|
+
|
|
176
286
|
/**
|
|
177
|
-
* Opens the inline edit panel for the given target element. This function is the endpoint of all
|
|
287
|
+
* Opens the inline edit panel for the given target element/cell. This function is the endpoint of all
|
|
178
288
|
* event-driven open inline edit flows but can also be used to open the inline edit panel in a direct
|
|
179
289
|
* programmatic fashion.
|
|
180
290
|
*
|
|
291
|
+
* - Open and position the inline edit panel relative to the cell it was opened from.
|
|
292
|
+
* - Retrieve and set the required inline edit properties in the state object.
|
|
293
|
+
* - Resolve typeAttributes from the column definition so that it can be passed down to the inline edit panel input
|
|
294
|
+
* - Set aria-selected to `true` on the cell which is being edited
|
|
295
|
+
* - Once the panel is open, set focus on the input element
|
|
296
|
+
*
|
|
181
297
|
* @param {Object} dt - The datatable instance. Must be a truthy and valid datatable reference.
|
|
182
|
-
* @param {Object} target - The LWC component instance representing the cell in the
|
|
183
|
-
* the inline edit panel is to be opened. Must be a truthy and valid reference.
|
|
298
|
+
* @param {Object} target - The LWC component instance (lightning-primitive-cell-factory) representing the cell in the
|
|
299
|
+
* datatable for which the inline edit panel is to be opened. Must be a truthy and valid reference.
|
|
184
300
|
*/
|
|
185
301
|
function openInlineEdit(dt, target) {
|
|
186
302
|
startPanelPositioning(dt, target.parentElement);
|
|
@@ -193,7 +309,7 @@ function openInlineEdit(dt, target) {
|
|
|
193
309
|
// in this case we will need to process the values before re-open the edit panel with the new values or we may lose the edition.
|
|
194
310
|
processInlineEditFinish(
|
|
195
311
|
dt,
|
|
196
|
-
'
|
|
312
|
+
'lost-focus',
|
|
197
313
|
inlineEdit.rowKeyValue,
|
|
198
314
|
inlineEdit.colKeyValue
|
|
199
315
|
);
|
|
@@ -201,6 +317,11 @@ function openInlineEdit(dt, target) {
|
|
|
201
317
|
|
|
202
318
|
const { rowKeyValue, colKeyValue } = target;
|
|
203
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
|
+
|
|
204
325
|
inlineEdit.isPanelVisible = true;
|
|
205
326
|
inlineEdit.rowKeyValue = rowKeyValue;
|
|
206
327
|
inlineEdit.colKeyValue = colKeyValue;
|
|
@@ -217,7 +338,7 @@ function openInlineEdit(dt, target) {
|
|
|
217
338
|
const typeAttributesFromColumnDef =
|
|
218
339
|
inlineEdit.columnDef && inlineEdit.columnDef.typeAttributes;
|
|
219
340
|
if (typeAttributesFromColumnDef) {
|
|
220
|
-
// when
|
|
341
|
+
// when the inline edit panel is opened resolve the typeAttributes if available
|
|
221
342
|
// then assign the resolved values to inlineEdit.resolvedTypeAttributes
|
|
222
343
|
inlineEdit.resolvedTypeAttributes = resolveNestedTypeAttributes(
|
|
223
344
|
state,
|
|
@@ -229,7 +350,7 @@ function openInlineEdit(dt, target) {
|
|
|
229
350
|
);
|
|
230
351
|
}
|
|
231
352
|
|
|
232
|
-
|
|
353
|
+
setAriaSelectedOnCell(state, rowKeyValue, colKeyValue);
|
|
233
354
|
|
|
234
355
|
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
235
356
|
setTimeout(() => {
|
|
@@ -248,9 +369,243 @@ function openInlineEdit(dt, target) {
|
|
|
248
369
|
// if panel can be edited, focus
|
|
249
370
|
panel.focus();
|
|
250
371
|
}
|
|
251
|
-
}, 0);
|
|
372
|
+
}, 0);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Attempts to open the inline edit panel for the datatable's currently active cell. If the active cell is not
|
|
377
|
+
* editable, then the panel is instead opened for the first editable cell in the table. Used to open inline edit
|
|
378
|
+
* in a direct, programmatic fashion.
|
|
379
|
+
*
|
|
380
|
+
* If there is no data in the table or there are no editable cells in the table then calling this function
|
|
381
|
+
* results in a no-op.
|
|
382
|
+
*
|
|
383
|
+
* @param {Object} dt - The datatable instance. Must be a truthy and valid datatable reference.
|
|
384
|
+
*/
|
|
385
|
+
export function openInlineEditOnActiveCell(dt) {
|
|
386
|
+
const hasData = dt.state.data && dt.state.data.length > 0;
|
|
387
|
+
if (hasData) {
|
|
388
|
+
if (!isActiveCellEditable(dt.state)) {
|
|
389
|
+
const firstEditableCell = getFirstEditableCell(dt);
|
|
390
|
+
if (firstEditableCell) {
|
|
391
|
+
updateActiveCell(
|
|
392
|
+
dt.state,
|
|
393
|
+
firstEditableCell.rowKeyValue,
|
|
394
|
+
firstEditableCell.colKeyValue
|
|
395
|
+
);
|
|
396
|
+
setFocusAndOpenInlineEdit(dt, dt.state.activeCell);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
setFocusAndOpenInlineEdit(dt, dt.state.activeCell);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Async function to await setting focus on an editable cell before opening inline-edit panel
|
|
406
|
+
*
|
|
407
|
+
* @param {Object} dt - The datatable instance
|
|
408
|
+
*/
|
|
409
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
410
|
+
async function setFocusAndOpenInlineEdit(dt) {
|
|
411
|
+
await setFocusActiveCell(dt.template, dt.state, 0);
|
|
412
|
+
const cell = getActiveCellElement(dt.template, dt.state);
|
|
413
|
+
openInlineEdit(dt, cell);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/************************** PANEL POSITIONING **************************/
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Begin positioning the inline edit panel based on the following constraints:
|
|
420
|
+
* Align to the 'top-left' edge of the inline edit panel to the `top-left` edge of the cell
|
|
421
|
+
*
|
|
422
|
+
* `align` refers to the alignment of the inline edit panel
|
|
423
|
+
* - horizontal - Left -> align left edge of panel
|
|
424
|
+
* - vertical - Top -> align top of panel
|
|
425
|
+
*
|
|
426
|
+
* `targetAlign` refers to the cell against which the panel should be aligned
|
|
427
|
+
* - horizontal - Left -> align panel to left edge of cell
|
|
428
|
+
* - vertical - Top -> align panel to top of the cell
|
|
429
|
+
*
|
|
430
|
+
* @param {Object} dt - datatable instance
|
|
431
|
+
* @param {HTMLElement} target - cell on which inline edit should open
|
|
432
|
+
*/
|
|
433
|
+
function startPanelPositioning(dt, target) {
|
|
434
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
435
|
+
requestAnimationFrame(() => {
|
|
436
|
+
// we need to discard previous binding otherwise the panel
|
|
437
|
+
// will retain previous alignment
|
|
438
|
+
stopPanelPositioning(dt);
|
|
439
|
+
|
|
440
|
+
dt.privatePositionRelationship = startPositioning(dt, {
|
|
441
|
+
target,
|
|
442
|
+
element: () =>
|
|
443
|
+
dt.template
|
|
444
|
+
.querySelector(IEDIT_PANEL_SELECTOR)
|
|
445
|
+
.getPositionedElement(),
|
|
446
|
+
align: {
|
|
447
|
+
horizontal: Direction.Left,
|
|
448
|
+
vertical: Direction.Top,
|
|
449
|
+
},
|
|
450
|
+
targetAlign: {
|
|
451
|
+
horizontal: Direction.Left,
|
|
452
|
+
vertical: Direction.Top,
|
|
453
|
+
},
|
|
454
|
+
autoFlip: true,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function stopPanelPositioning(dt) {
|
|
460
|
+
if (dt.privatePositionRelationship) {
|
|
461
|
+
stopPositioning(dt.privatePositionRelationship);
|
|
462
|
+
dt.privatePositionRelationship = null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Repositions the inline edit panel. this does not realign the element,
|
|
468
|
+
* so it doesn't fix alignment when size of panel changes
|
|
469
|
+
*
|
|
470
|
+
* @param {Object} dt - datatable instance
|
|
471
|
+
*/
|
|
472
|
+
function repositionPanel(dt) {
|
|
473
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
474
|
+
requestAnimationFrame(() => {
|
|
475
|
+
if (dt.privatePositionRelationship) {
|
|
476
|
+
dt.privatePositionRelationship.reposition();
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/************************** DIRTY/UNSAVED VALUES **************************/
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @param {Object} state - Datatable state object.
|
|
485
|
+
* @returns {Array} - An array of objects, each object describing the dirty values in the form { colName : dirtyValue }.
|
|
486
|
+
* A special key is the { [keyField]: value } pair used to identify the row containing this changed values.
|
|
487
|
+
* The returned array will be in the form - [{colName : dirtyValue, ... , [keyField]: value }, {...}, {...}]
|
|
488
|
+
*/
|
|
489
|
+
export function getDirtyValues(state) {
|
|
490
|
+
return getResolvedCellChanges(state, state.inlineEdit.dirtyValues);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Sets the dirty values in the datatable.
|
|
495
|
+
*
|
|
496
|
+
* @param {Object} state Datatable state object.
|
|
497
|
+
* @param {Array} value An array of objects, each object describing the dirty values in the form { colName : dirtyValue }.
|
|
498
|
+
* A special key is the { [keyField]: value } pair used to identify the row containing this changed values.
|
|
499
|
+
*/
|
|
500
|
+
export function setDirtyValues(state, value) {
|
|
501
|
+
const keyField = getKeyField(state);
|
|
502
|
+
const dirtyValues = Array.isArray(value) ? value : [];
|
|
503
|
+
|
|
504
|
+
state.inlineEdit.dirtyValues = dirtyValues.reduce((result, rowValues) => {
|
|
505
|
+
const changes = getCellChangesFromCustomer(state, rowValues);
|
|
506
|
+
delete changes[keyField];
|
|
507
|
+
|
|
508
|
+
result[rowValues[keyField]] = changes;
|
|
509
|
+
|
|
510
|
+
return result;
|
|
511
|
+
}, {});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Updates the dirty values specified in rowColKeyValues
|
|
516
|
+
*
|
|
517
|
+
* @param {Object} state - state of the datatable
|
|
518
|
+
* @param {Object} rowColKeyValues - An object in the form of { rowKeyValue: { colKeyValue1: value, ..., colKeyValueN: value } ... }
|
|
519
|
+
*/
|
|
520
|
+
function updateDirtyValues(state, rowColKeyValues) {
|
|
521
|
+
const dirtyValues = state.inlineEdit.dirtyValues;
|
|
522
|
+
|
|
523
|
+
Object.keys(rowColKeyValues).forEach((rowKey) => {
|
|
524
|
+
if (!Object.prototype.hasOwnProperty.call(dirtyValues, rowKey)) {
|
|
525
|
+
dirtyValues[rowKey] = {};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
Object.assign(dirtyValues[rowKey], rowColKeyValues[rowKey]);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Constructs and returns an object that contains the cell changes which can
|
|
534
|
+
* be referenced by the column key value. It follows this format:
|
|
535
|
+
* { <colKeyValue: "<editedValue>"> }; Ex. { "name-text-2": "My changes" }
|
|
536
|
+
*
|
|
537
|
+
* @param {Object} state - datatable's state object
|
|
538
|
+
* @param {Object} changes - internal representation of changes in a row
|
|
539
|
+
* @returns {Object} - changes in a column that can be referenced by the column key
|
|
540
|
+
*/
|
|
541
|
+
function getCellChangesFromCustomer(state, changes) {
|
|
542
|
+
return Object.keys(changes).reduce((result, externalColumnKey) => {
|
|
543
|
+
const columns = getColumns(state);
|
|
544
|
+
const columnIndex = getColumnIndexByColumnKey(state, externalColumnKey);
|
|
545
|
+
|
|
546
|
+
if (columnIndex >= 0) {
|
|
547
|
+
const colKey = columns[columnIndex].colKeyValue;
|
|
548
|
+
result[colKey] = changes[externalColumnKey];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return result;
|
|
552
|
+
}, {});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Retrieves the changes in cells in a particular column
|
|
557
|
+
* Returns an object where each item follows this format:
|
|
558
|
+
* { <columnName>: "<changes>"} -> Ex. { name: "My changes" }
|
|
559
|
+
*
|
|
560
|
+
* @param {Object} state - Datatable state
|
|
561
|
+
* @param {Object} changes - The internal representation of changes in a row
|
|
562
|
+
* @returns {Object} - the list of customer changes in a column
|
|
563
|
+
*/
|
|
564
|
+
function getCellChangesByColumn(state, changes) {
|
|
565
|
+
return Object.keys(changes).reduce((result, colKey) => {
|
|
566
|
+
const columns = getColumns(state);
|
|
567
|
+
const columnIndex = getStateColumnIndex(state, colKey);
|
|
568
|
+
const columnDef = columns[columnIndex];
|
|
569
|
+
|
|
570
|
+
result[columnDef.columnKey || columnDef.fieldName] = changes[colKey];
|
|
571
|
+
|
|
572
|
+
return result;
|
|
573
|
+
}, {});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Constructs an array of resolved cell changes made via inline edit
|
|
578
|
+
* Each array item consists of an identifier of the row and column in order to locate
|
|
579
|
+
* the cell in which the changes were made
|
|
580
|
+
*
|
|
581
|
+
* It follows this format: [{ <columnName>: "<changes>", <keyField>: "<keyFieldIdentifier>" }]
|
|
582
|
+
* Ex. [{ name: "My changes", id: "2" }]; where column name is 'name' and 'id' is the keyField
|
|
583
|
+
* The keyField can be used to identify the row.
|
|
584
|
+
*
|
|
585
|
+
* @param {Object} state - datatable state object
|
|
586
|
+
* @param {Object} changes - list of cell changes to be resolved
|
|
587
|
+
* @returns {Array} - array containing changes and identifiers of column and row where the changes
|
|
588
|
+
* should be applied
|
|
589
|
+
*/
|
|
590
|
+
function getResolvedCellChanges(state, changes) {
|
|
591
|
+
const keyField = getKeyField(state);
|
|
592
|
+
|
|
593
|
+
return Object.keys(changes).reduce((result, rowKey) => {
|
|
594
|
+
// Get the changes made by column
|
|
595
|
+
const cellChanges = getCellChangesByColumn(state, changes[rowKey]);
|
|
596
|
+
|
|
597
|
+
if (Object.keys(cellChanges).length > 0) {
|
|
598
|
+
// Add identifier for which row has change
|
|
599
|
+
cellChanges[keyField] = rowKey;
|
|
600
|
+
result.push(cellChanges);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return result;
|
|
604
|
+
}, []);
|
|
252
605
|
}
|
|
253
606
|
|
|
607
|
+
/************************** TYPE ATTRIBUTES RESOLUTION **************************/
|
|
608
|
+
|
|
254
609
|
/**
|
|
255
610
|
* Returns the resolved typeAttributes
|
|
256
611
|
*
|
|
@@ -261,7 +616,7 @@ function openInlineEdit(dt, target) {
|
|
|
261
616
|
* @param {object} typeAttributesFromColumnDef - values of typeAttributes from column definition
|
|
262
617
|
* @param {number} stateColIndex - state column index
|
|
263
618
|
*
|
|
264
|
-
* @
|
|
619
|
+
* @returns {Object} the resolved typeAttributes.
|
|
265
620
|
*/
|
|
266
621
|
export function resolveNestedTypeAttributes(
|
|
267
622
|
state,
|
|
@@ -307,7 +662,9 @@ export function resolveNestedTypeAttributes(
|
|
|
307
662
|
* fieldName: 'name'
|
|
308
663
|
* }
|
|
309
664
|
* }
|
|
310
|
-
* }
|
|
665
|
+
* }
|
|
666
|
+
* to be ...
|
|
667
|
+
* {
|
|
311
668
|
* editTypeAttributes: {
|
|
312
669
|
* value: 'resolvedValue'
|
|
313
670
|
* }
|
|
@@ -361,84 +718,36 @@ function resolveNestedTypeAttributesHelper(rowData, typeAttributesValue) {
|
|
|
361
718
|
return resolvedTypeAttributes;
|
|
362
719
|
}
|
|
363
720
|
|
|
364
|
-
|
|
365
|
-
stopPanelPositioning(this);
|
|
366
|
-
|
|
367
|
-
const { reason, rowKeyValue, colKeyValue } = event.detail;
|
|
368
|
-
|
|
369
|
-
processInlineEditFinish(this, reason, rowKeyValue, colKeyValue);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export function handleMassCheckboxChange(event) {
|
|
373
|
-
const state = this.state;
|
|
374
|
-
if (event.detail.checked) {
|
|
375
|
-
markAllSelectedRowsAsSelectedCell(state);
|
|
376
|
-
} else {
|
|
377
|
-
markAllSelectedRowsAsDeselectedCell(this.state);
|
|
378
|
-
markSelectedCell(
|
|
379
|
-
state,
|
|
380
|
-
state.inlineEdit.rowKeyValue,
|
|
381
|
-
state.inlineEdit.colKeyValue
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// hide panel on scroll
|
|
387
|
-
const HIDE_PANEL_THRESHOLD = 5;
|
|
388
|
-
export function handleInlineEditPanelScroll(event) {
|
|
389
|
-
const { isPanelVisible, rowKeyValue, colKeyValue } = this.state.inlineEdit;
|
|
390
|
-
|
|
391
|
-
if (!isPanelVisible) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
let delta = 0;
|
|
396
|
-
|
|
397
|
-
const container = event.target;
|
|
398
|
-
if (container.classList.contains('slds-scrollable_x')) {
|
|
399
|
-
const scrollX = container.scrollLeft;
|
|
400
|
-
if (this.privateLastScrollX == null) {
|
|
401
|
-
this.privateLastScrollX = scrollX;
|
|
402
|
-
} else {
|
|
403
|
-
delta = Math.abs(this.privateLastScrollX - scrollX);
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
const scrollY = container.scrollTop;
|
|
407
|
-
if (this.privateLastScrollY == null) {
|
|
408
|
-
this.privateLastScrollY = scrollY;
|
|
409
|
-
} else {
|
|
410
|
-
delta = Math.abs(this.privateLastScrollY - scrollY);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (delta > HIDE_PANEL_THRESHOLD) {
|
|
415
|
-
this.privateLastScrollX = null;
|
|
416
|
-
this.privateLastScrollY = null;
|
|
417
|
-
stopPanelPositioning(this);
|
|
418
|
-
processInlineEditFinish(this, 'loosed-focus', rowKeyValue, colKeyValue);
|
|
419
|
-
} else {
|
|
420
|
-
// we want to keep the panel attached to the cell before
|
|
421
|
-
// reaching the threshold and hiding the panel
|
|
422
|
-
repositionPanel(this);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
721
|
+
/************************** HELPER FUNCTIONS **************************/
|
|
425
722
|
|
|
426
723
|
/**
|
|
427
|
-
*
|
|
724
|
+
* Returns the row and column keys of the first editable cell in the table.
|
|
725
|
+
* If no editable cells exist in the table then undefined is returned.
|
|
428
726
|
*
|
|
429
|
-
* @param {Object}
|
|
430
|
-
* @param {Object} rowColKeyValues - An object in the form of { rowKeyValue: { colKeyValue1: value, ..., colKeyValueN: value } ... }
|
|
727
|
+
* @param {Object} dt - The datatable instance. Must be a truthy and valid datatable reference.
|
|
431
728
|
*/
|
|
432
|
-
function
|
|
433
|
-
const
|
|
729
|
+
function getFirstEditableCell(dt) {
|
|
730
|
+
const columns = getColumns(dt.state);
|
|
731
|
+
const editableColumns = getEditableColumns(columns);
|
|
434
732
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
733
|
+
if (editableColumns.length > 0) {
|
|
734
|
+
const rows = dt.state.rows;
|
|
735
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
736
|
+
for (let i = 0; i < editableColumns.length; i++) {
|
|
737
|
+
// Loop through the editable columns in order and examine the corresponding cells
|
|
738
|
+
// in the current row for editability, returning the first such cell that is editable
|
|
739
|
+
const editableColumn = editableColumns[i];
|
|
740
|
+
if (isCellEditable(rows[rowIndex], editableColumn)) {
|
|
741
|
+
return {
|
|
742
|
+
rowKeyValue: rows[rowIndex].key,
|
|
743
|
+
colKeyValue: editableColumn.colKeyValue,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
438
747
|
}
|
|
748
|
+
}
|
|
439
749
|
|
|
440
|
-
|
|
441
|
-
});
|
|
750
|
+
return undefined;
|
|
442
751
|
}
|
|
443
752
|
|
|
444
753
|
/**
|
|
@@ -448,7 +757,7 @@ function updateDirtyValues(state, rowColKeyValues) {
|
|
|
448
757
|
* @param {String} rowKeyValue - row key
|
|
449
758
|
* @param {String} colKeyValue - column key
|
|
450
759
|
*
|
|
451
|
-
* @
|
|
760
|
+
* @returns {Object} the value for the current cell.
|
|
452
761
|
*/
|
|
453
762
|
function getCellValue(state, rowKeyValue, colKeyValue) {
|
|
454
763
|
const row = getRowByKey(state, rowKeyValue);
|
|
@@ -458,214 +767,31 @@ function getCellValue(state, rowKeyValue, colKeyValue) {
|
|
|
458
767
|
}
|
|
459
768
|
|
|
460
769
|
/**
|
|
770
|
+
* Sets `aria-selected` to true on cells whose rows are selected
|
|
771
|
+
* and are in the same column as the cell being currently edited
|
|
461
772
|
*
|
|
462
|
-
* @param {Object} state -
|
|
463
|
-
* @param {Object} changes - The internal representation of changes in a row
|
|
464
|
-
* @returns {Object} - the list of customer changes in a row
|
|
465
|
-
*/
|
|
466
|
-
function getColumnsChangesForCustomer(state, changes) {
|
|
467
|
-
return Object.keys(changes).reduce((result, colKey) => {
|
|
468
|
-
const columns = getColumns(state);
|
|
469
|
-
const columnIndex = getStateColumnIndex(state, colKey);
|
|
470
|
-
const columnDef = columns[columnIndex];
|
|
471
|
-
|
|
472
|
-
result[columnDef.columnKey || columnDef.fieldName] = changes[colKey];
|
|
473
|
-
|
|
474
|
-
return result;
|
|
475
|
-
}, {});
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function getRowChangesFromCustomer(state, changes) {
|
|
479
|
-
return Object.keys(changes).reduce((result, externalColumnKey) => {
|
|
480
|
-
const columns = getColumns(state);
|
|
481
|
-
const columnIndex = getColumnIndexByColumnKey(state, externalColumnKey);
|
|
482
|
-
|
|
483
|
-
if (columnIndex >= 0) {
|
|
484
|
-
const colKey = columns[columnIndex].colKeyValue;
|
|
485
|
-
result[colKey] = changes[externalColumnKey];
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return result;
|
|
489
|
-
}, {});
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function getChangesForCustomer(state, changes) {
|
|
493
|
-
const keyField = getKeyField(state);
|
|
494
|
-
|
|
495
|
-
return Object.keys(changes).reduce((result, rowKey) => {
|
|
496
|
-
const rowChanges = getColumnsChangesForCustomer(state, changes[rowKey]);
|
|
497
|
-
|
|
498
|
-
if (Object.keys(rowChanges).length > 0) {
|
|
499
|
-
rowChanges[keyField] = rowKey;
|
|
500
|
-
|
|
501
|
-
result.push(rowChanges);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return result;
|
|
505
|
-
}, []);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function dispatchCellChangeEvent(dtInstance, cellChange) {
|
|
509
|
-
dtInstance.dispatchEvent(
|
|
510
|
-
new CustomEvent('cellchange', {
|
|
511
|
-
detail: {
|
|
512
|
-
draftValues: getChangesForCustomer(
|
|
513
|
-
dtInstance.state,
|
|
514
|
-
cellChange
|
|
515
|
-
),
|
|
516
|
-
},
|
|
517
|
-
})
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export function closeInlineEdit(dt) {
|
|
522
|
-
const inlineEditState = dt.state.inlineEdit;
|
|
523
|
-
|
|
524
|
-
if (inlineEditState.isPanelVisible) {
|
|
525
|
-
processInlineEditFinish(
|
|
526
|
-
dt,
|
|
527
|
-
'loosed-focus',
|
|
528
|
-
inlineEditState.rowKeyValue,
|
|
529
|
-
inlineEditState.colKeyValue
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function isValidCell(state, rowKeyValue, colKeyValue) {
|
|
535
|
-
const row = getRowByKey(state, rowKeyValue);
|
|
536
|
-
const colIndex = getStateColumnIndex(state, colKeyValue);
|
|
537
|
-
|
|
538
|
-
return row && row.cells[colIndex];
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* It will process when the datatable had finished an edition.
|
|
543
|
-
*
|
|
544
|
-
* @param {Object} dt - the datatable instance
|
|
545
|
-
* @param {string} reason - the reason to finish the edition. valid reasons are: edit-canceled | loosed-focus | tab-pressed | submit-action
|
|
546
|
-
* @param {string} rowKeyValue - the row key of the edited cell
|
|
547
|
-
* @param {string} colKeyValue - the column key of the edited cell
|
|
773
|
+
* @param {Object} state - datatable's state object
|
|
548
774
|
*/
|
|
549
|
-
function
|
|
550
|
-
const state = dt.state;
|
|
551
|
-
const inlineEditState = state.inlineEdit;
|
|
552
|
-
|
|
553
|
-
const shouldSaveData =
|
|
554
|
-
reason !== 'edit-canceled' &&
|
|
555
|
-
!(inlineEditState.massEditEnabled && reason === 'loosed-focus') &&
|
|
556
|
-
isValidCell(dt.state, rowKeyValue, colKeyValue);
|
|
557
|
-
|
|
558
|
-
if (shouldSaveData) {
|
|
559
|
-
const panel = dt.template.querySelector(PANEL_SEL);
|
|
560
|
-
const editValue = panel.value;
|
|
561
|
-
const isValidEditValue = panel.validity.valid;
|
|
562
|
-
const updateAllSelectedRows = panel.isMassEditChecked;
|
|
563
|
-
const currentValue = getCellValue(state, rowKeyValue, colKeyValue);
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
isValidEditValue &&
|
|
567
|
-
(editValue !== currentValue || updateAllSelectedRows)
|
|
568
|
-
) {
|
|
569
|
-
const cellChange = {};
|
|
570
|
-
cellChange[rowKeyValue] = {};
|
|
571
|
-
cellChange[rowKeyValue][colKeyValue] = editValue;
|
|
572
|
-
|
|
573
|
-
if (updateAllSelectedRows) {
|
|
574
|
-
const selectedRowKeys = getSelectedRowsKeys(state);
|
|
575
|
-
selectedRowKeys.forEach((rowKey) => {
|
|
576
|
-
cellChange[rowKey] = {};
|
|
577
|
-
cellChange[rowKey][colKeyValue] = editValue;
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
updateDirtyValues(state, cellChange);
|
|
582
|
-
|
|
583
|
-
dispatchCellChangeEvent(dt, cellChange);
|
|
584
|
-
|
|
585
|
-
// @todo: do we need to update all rows in the dt or just the one that was modified?
|
|
586
|
-
updateRowsAndCellIndexes.call(dt);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (reason !== 'loosed-focus') {
|
|
591
|
-
switch (reason) {
|
|
592
|
-
case 'tab-pressed-next': {
|
|
593
|
-
reactToTabForward(dt.template, state);
|
|
594
|
-
break;
|
|
595
|
-
}
|
|
596
|
-
case 'tab-pressed-prev': {
|
|
597
|
-
reactToTabBackward(dt.template, state);
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
default: {
|
|
601
|
-
setFocusActiveCell(dt.template, state, 0);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
markAllSelectedRowsAsDeselectedCell(state);
|
|
607
|
-
markDeselectedCell(state, rowKeyValue, colKeyValue);
|
|
608
|
-
|
|
609
|
-
inlineEditState.isPanelVisible = false;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function startPanelPositioning(dt, target) {
|
|
613
|
-
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
614
|
-
requestAnimationFrame(() => {
|
|
615
|
-
// we need to discard previous binding otherwise the panel
|
|
616
|
-
// will retain previous alignment
|
|
617
|
-
stopPanelPositioning(dt);
|
|
618
|
-
|
|
619
|
-
dt.privatePositionRelationship = startPositioning(dt, {
|
|
620
|
-
target,
|
|
621
|
-
element: () =>
|
|
622
|
-
dt.template.querySelector(PANEL_SEL).getPositionedElement(),
|
|
623
|
-
align: {
|
|
624
|
-
horizontal: Direction.Left,
|
|
625
|
-
vertical: Direction.Top,
|
|
626
|
-
},
|
|
627
|
-
targetAlign: {
|
|
628
|
-
horizontal: Direction.Left,
|
|
629
|
-
vertical: Direction.Top,
|
|
630
|
-
},
|
|
631
|
-
autoFlip: true,
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function stopPanelPositioning(dt) {
|
|
637
|
-
if (dt.privatePositionRelationship) {
|
|
638
|
-
stopPositioning(dt.privatePositionRelationship);
|
|
639
|
-
dt.privatePositionRelationship = null;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// reposition inline edit panel
|
|
644
|
-
// this does not realign the element, so it doesn't fix alignment
|
|
645
|
-
// when size of panel changes
|
|
646
|
-
function repositionPanel(dt) {
|
|
647
|
-
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
648
|
-
requestAnimationFrame(() => {
|
|
649
|
-
if (dt.privatePositionRelationship) {
|
|
650
|
-
dt.privatePositionRelationship.reposition();
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function markAllSelectedRowsAsSelectedCell(state) {
|
|
775
|
+
function setAriaSelectedOnAllSelectedRows(state) {
|
|
656
776
|
const { colKeyValue } = state.inlineEdit;
|
|
657
777
|
const selectedRowKeys = getSelectedRowsKeys(state);
|
|
658
778
|
|
|
659
779
|
selectedRowKeys.forEach((rowKeyValue) => {
|
|
660
|
-
|
|
780
|
+
setAriaSelectedOnCell(state, rowKeyValue, colKeyValue);
|
|
661
781
|
});
|
|
662
782
|
}
|
|
663
783
|
|
|
664
|
-
|
|
784
|
+
/**
|
|
785
|
+
* Sets `aria-selected` to false on cells whose rows are selected
|
|
786
|
+
* and are in the same column as the cell being currently edited
|
|
787
|
+
*
|
|
788
|
+
* @param {Object} state - datatable's state object
|
|
789
|
+
*/
|
|
790
|
+
function unsetAriaSelectedOnAllSelectedRows(state) {
|
|
665
791
|
const { colKeyValue } = state.inlineEdit;
|
|
666
792
|
const selectedRowKeys = getSelectedRowsKeys(state);
|
|
667
793
|
|
|
668
794
|
selectedRowKeys.forEach((rowKeyValue) => {
|
|
669
|
-
|
|
795
|
+
unsetAriaSelectedOnCell(state, rowKeyValue, colKeyValue);
|
|
670
796
|
});
|
|
671
797
|
}
|