lightning-base-components 1.13.6-alpha → 1.13.10-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 +104 -2
- package/package.json +37 -1
- package/scopedImports/@salesforce-label-LightningAlert.defaultLabel.js +1 -0
- package/scopedImports/@salesforce-label-LightningConfirm.defaultLabel.js +1 -0
- package/scopedImports/@salesforce-label-LightningDateTimePicker.selectDateFor.js +1 -0
- package/scopedImports/@salesforce-label-LightningInteractiveDialogBase.cancel.js +1 -0
- package/scopedImports/@salesforce-label-LightningInteractiveDialogBase.ok.js +1 -0
- package/scopedImports/@salesforce-label-LightningLookup.recentItems.js +1 -0
- package/scopedImports/@salesforce-label-LightningModalBase.close.js +1 -0
- package/scopedImports/@salesforce-label-LightningModalBase.waitstate.js +1 -0
- package/scopedImports/@salesforce-label-LightningPrompt.defaultLabel.js +1 -0
- package/src/lightning/baseCombobox/baseCombobox.html +50 -24
- package/src/lightning/baseCombobox/baseCombobox.js +53 -28
- package/src/lightning/card/card.html +7 -1
- package/src/lightning/card/card.js +30 -2
- package/src/lightning/card/utils.js +14 -0
- package/src/lightning/combobox/combobox.css +12 -0
- package/src/lightning/combobox/combobox.html +1 -0
- package/src/lightning/datatable/__docs__/datatable.md +40 -13
- package/src/lightning/datatable/columnWidthManager.js +8 -4
- package/src/lightning/datatable/columns-shared.js +8 -7
- package/src/lightning/datatable/columns.js +38 -4
- package/src/lightning/datatable/datatable.js +932 -727
- package/src/lightning/datatable/datatableResizeObserver.js +1 -1
- package/src/lightning/datatable/inlineEdit.js +15 -3
- package/src/lightning/datatable/keyboard.js +1078 -935
- package/src/lightning/datatable/resizer.js +92 -109
- package/src/lightning/datatable/rows.js +245 -59
- package/src/lightning/datatable/sort.js +83 -28
- package/src/lightning/datatable/{normalizer.js → state.js} +16 -28
- package/src/lightning/datatable/templates/div/div.css +53 -0
- package/src/lightning/datatable/templates/div/div.html +272 -0
- package/src/lightning/datatable/{datatable.css → templates/table/table.css} +0 -0
- package/src/lightning/datatable/templates/table/table.html +260 -0
- package/src/lightning/datatable/widthManagerShared.js +1 -1
- package/src/lightning/datepicker/datepicker.html +3 -3
- package/src/lightning/datepicker/datepicker.js +6 -2
- package/src/lightning/datetimepicker/datetimepicker.html +3 -4
- package/src/lightning/datetimepicker/datetimepicker.js +0 -2
- package/src/lightning/formattedRichText/__docs__/formattedRichText.md +1 -0
- package/src/lightning/helptext/helptext.js +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/asset_audit.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/attach.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/historical_adherence.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/med_rec_recommendation.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/med_rec_statement_recommendation.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/medication_dispense.html +11 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/medication_reconciliation.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/report_type.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/story.html +2 -4
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/tour.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/travel_mode.html +2 -2
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/unified_health_score.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/standard/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/templates.js +26 -1
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/asset_audit.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/collection_alt.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/contract_doc.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/einstein.html +2 -1
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/entitlement.html +7 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/signature.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/tour.html +9 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/truck.html +10 -0
- package/src/lightning/iconSvgTemplates/buildTemplates/utility/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/asset_audit.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/attach.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/historical_adherence.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/med_rec_recommendation.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/med_rec_statement_recommendation.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/medication_dispense.html +11 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/medication_reconciliation.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/report_type.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/story.html +2 -4
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/travel_mode.html +2 -2
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/unified_health_score.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/standard/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/templates.js +26 -1
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/asset_audit.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/collection_alt.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/contract_doc.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/einstein.html +2 -1
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/entitlement.html +7 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/signature.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/truck.html +10 -0
- package/src/lightning/iconSvgTemplatesRtl/buildTemplates/utility/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/asset_audit.html +7 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/attach.html +7 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/historical_adherence.html +9 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/med_rec_recommendation.html +8 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/med_rec_statement_recommendation.html +7 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/medication_dispense.html +11 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/medication_reconciliation.html +7 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/report_type.html +9 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/story.html +2 -4
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/travel_mode.html +2 -2
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/unified_health_score.html +7 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/standard/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesStandard/buildTemplates/templates.js +15 -1
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/asset_audit.html +7 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/attach.html +7 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/historical_adherence.html +9 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/med_rec_recommendation.html +8 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/med_rec_statement_recommendation.html +7 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/medication_dispense.html +11 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/medication_reconciliation.html +7 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/report_type.html +9 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/story.html +2 -4
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/travel_mode.html +2 -2
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/unified_health_score.html +7 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/standard/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesStandardRtl/buildTemplates/templates.js +15 -1
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/templates.js +12 -1
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/asset_audit.html +9 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/collection_alt.html +8 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/contract_doc.html +8 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/einstein.html +2 -1
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/entitlement.html +7 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/signature.html +9 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/truck.html +10 -0
- package/src/lightning/iconSvgTemplatesUtility/buildTemplates/utility/workforce_engagement.html +8 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/templates.js +12 -1
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/asset_audit.html +9 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/collection_alt.html +8 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/contract_doc.html +8 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/contract_payment.html +10 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/einstein.html +2 -1
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/entitlement.html +7 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/field_sales.html +8 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/signature.html +9 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/tour.html +9 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/tour_check.html +8 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/truck.html +10 -0
- package/src/lightning/iconSvgTemplatesUtilityRtl/buildTemplates/utility/workforce_engagement.html +8 -0
- package/src/lightning/input/__docs__/input.md +4 -0
- package/src/lightning/input/input.js +11 -6
- package/src/lightning/pill/link.html +1 -1
- package/src/lightning/pill/pill.js +18 -0
- package/src/lightning/pill/plainLink.html +2 -0
- package/src/lightning/pillContainer/barePillContainer.html +5 -5
- package/src/lightning/pillContainer/pillContainer.js +5 -4
- package/src/lightning/pillContainer/standardPillContainer.html +5 -5
- package/src/lightning/positionLibrary/__component__/positionLibraryBounding.spec.js +8 -6
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.html +1 -1
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.js +6 -9
- package/src/lightning/positionLibrary/direction.js +17 -5
- package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.js +20 -0
- package/src/lightning/primitiveDatatableIeditTypeFactory/primitiveDatatableIeditTypeFactory.js +10 -0
- package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.js +17 -3
- package/src/lightning/primitiveDatatableTooltip/primitiveDatatableTooltip.js +1 -0
- package/src/lightning/primitiveHeaderFactory/nonsortableHeader.html +5 -4
- package/src/lightning/primitiveHeaderFactory/primitiveHeaderFactory.js +255 -94
- package/src/lightning/primitiveHeaderFactory/selectableHeader.html +25 -23
- package/src/lightning/primitiveHeaderFactory/sortableHeader.html +13 -9
- package/src/lightning/progressIndicator/progressIndicator.js +30 -9
- package/src/lightning/progressRing/progressRing.html +6 -0
- package/src/lightning/progressRing/progressRing.js +98 -3
- package/src/lightning/progressStep/progressStep.js +6 -3
- package/src/lightning/timepicker/timepicker.html +1 -0
- package/src/lightning/utilsPrivate/aria.js +30 -0
- package/src/lightning/utilsPrivate/contentMutation.js +24 -2
- package/src/lightning/utilsPrivate/utilsPrivate.js +1 -1
- package/src/lightning/datatable/datatable.html +0 -237
- package/src/lightning/datatable/keys.js +0 -32
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { isCustomerColumn } from './columns';
|
|
2
|
-
import { generateColKeyValue } from './keys';
|
|
1
|
+
import { isCustomerColumn, generateColKeyValue } from './columns';
|
|
3
2
|
import {
|
|
4
3
|
hasTreeDataType,
|
|
5
4
|
getStateTreeColumn,
|
|
@@ -7,15 +6,33 @@ import {
|
|
|
7
6
|
} from './tree';
|
|
8
7
|
import { isRTL, getShadowActiveElements } from 'lightning/utilsPrivate';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
// Indicator/flag for a header row
|
|
10
|
+
const HEADER_ROW = 'HEADER';
|
|
11
|
+
|
|
12
|
+
// SLDS Class for Focus
|
|
13
|
+
const FOCUS_CLASS = 'slds-has-focus';
|
|
14
|
+
|
|
15
|
+
// Keyboard Navigation Modes
|
|
16
|
+
const NAVIGATION_MODE = 'NAVIGATION';
|
|
17
|
+
const ACTION_MODE = 'ACTION';
|
|
18
|
+
|
|
19
|
+
// Pixel Values
|
|
20
|
+
const TOP_MARGIN = 80;
|
|
21
|
+
const BOTTOM_MARGIN = 80;
|
|
22
|
+
const SCROLL_OFFSET = 20;
|
|
23
|
+
|
|
24
|
+
// Key Code Values
|
|
25
|
+
const ARROW_RIGHT = 39;
|
|
26
|
+
const ARROW_LEFT = 37;
|
|
27
|
+
const ARROW_DOWN = 40;
|
|
28
|
+
const ARROW_UP = 38;
|
|
29
|
+
const ENTER = 13;
|
|
30
|
+
const ESCAPE = 27;
|
|
31
|
+
const TAB = 9;
|
|
32
|
+
const SPACE = 32;
|
|
33
|
+
|
|
34
|
+
// Navigation Direction
|
|
35
|
+
const NAVIGATION_DIR = (() => {
|
|
19
36
|
if (isRTL()) {
|
|
20
37
|
return {
|
|
21
38
|
RIGHT: -1,
|
|
@@ -37,618 +54,696 @@ export const NAVIGATION_DIR = (() => {
|
|
|
37
54
|
};
|
|
38
55
|
})();
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
// Selectors
|
|
58
|
+
const SELECTORS = {
|
|
59
|
+
headerRow: {
|
|
60
|
+
default: `thead > :nth-child(1)`,
|
|
61
|
+
roleBased: `[role="grid"] > [role="rowgroup"]:nth-child(1) > [role="row"]`,
|
|
62
|
+
},
|
|
63
|
+
dataRowRowGroup: {
|
|
64
|
+
default: `tbody`,
|
|
65
|
+
roleBased: `[role="grid"] > [role="rowgroup"]:nth-child(2)`,
|
|
66
|
+
},
|
|
67
|
+
cell: {
|
|
68
|
+
default: ['td', 'th'],
|
|
69
|
+
roleBased: ['rowheader', 'gridcell', 'columnheader'],
|
|
70
|
+
},
|
|
71
|
+
};
|
|
44
72
|
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
keyboardMode: NAVIGATION_MODE,
|
|
48
|
-
rowMode: false,
|
|
49
|
-
activeCell: undefined,
|
|
50
|
-
tabindex: 0,
|
|
51
|
-
cellToFocusNext: null,
|
|
52
|
-
cellClicked: false,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
73
|
+
/***************************** KEYDOWN HANDLERS *****************************/
|
|
55
74
|
|
|
56
75
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
76
|
+
* Handler for the `privatecellkeydown` event that is fired by
|
|
77
|
+
* lightning-primitive-datatable-cell.
|
|
78
|
+
* This component is extended by primitive-cell-factory, primitive-cell-checkbox
|
|
79
|
+
* and primitive-header-factory.
|
|
80
|
+
*
|
|
81
|
+
* Typically this handler is invoked when the user is in ACTION mode and the
|
|
82
|
+
* user keys down on a cell that contains actionable items (ex. edit button, links,
|
|
83
|
+
* email, buttons).
|
|
84
|
+
*
|
|
85
|
+
* @param {Event} event - Custom DOM event (privatecellkeydown) sent by the cell
|
|
62
86
|
*/
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
return state;
|
|
69
|
-
};
|
|
87
|
+
export function handleKeydownOnCell(event) {
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
reactToKeyboardInActionMode(this.template, this.state, event);
|
|
90
|
+
}
|
|
70
91
|
|
|
71
92
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
93
|
+
* Handler for keydown on the <table> element or the corresponding [role="grid"]
|
|
94
|
+
* on the role-based table.
|
|
95
|
+
*
|
|
96
|
+
* This handler is invoked whenever a keydown occurs on the table. However, we
|
|
97
|
+
* only react to the keyboard here if the user is in Navigation mode OR in Action
|
|
98
|
+
* mode when the cell does not have actionable items (like buttons, links etc).
|
|
99
|
+
*
|
|
100
|
+
* The Action mode keydowns are filtered out here. If a keydown occurs on an actionable
|
|
101
|
+
* element, the target element will not be the cell element (td/th, role=gridcell etc).
|
|
102
|
+
* The target element in that case will likely be the components extending
|
|
103
|
+
* primitiveDatatableCell (primitive-cell-factory/primitive-cell-checkbox/primitive-header-factory)
|
|
104
|
+
* Those events are handled by `handleKeydownOnCell()` and the remaining are
|
|
105
|
+
* handled by this function.
|
|
106
|
+
*
|
|
107
|
+
* @param {*} event
|
|
77
108
|
*/
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
currentColKeyValue === colKeyValue
|
|
87
|
-
);
|
|
109
|
+
export function handleKeydownOnTable(event) {
|
|
110
|
+
const targetTagName = event.target.tagName.toLowerCase();
|
|
111
|
+
const targetRole = event.target.getAttribute('role');
|
|
112
|
+
|
|
113
|
+
// Checks if the keydown happened on a cell element and not
|
|
114
|
+
// on an actionable element when in Action Mode.
|
|
115
|
+
if (isCellElement(targetTagName, targetRole)) {
|
|
116
|
+
reactToKeyboardInNavMode(this.template, this.state, event);
|
|
88
117
|
}
|
|
89
|
-
|
|
90
|
-
};
|
|
118
|
+
}
|
|
91
119
|
|
|
92
120
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* which is calculated from previously focused cell, if so we sync to that
|
|
96
|
-
* If active cell is still valid we keep it the same
|
|
121
|
+
* Changes the datatable state based on the keyboard event sent from the cell component.
|
|
122
|
+
* The result of those changes may trigger a re-render on the table
|
|
97
123
|
*
|
|
124
|
+
* @param {node} element - the custom element root `this.template`
|
|
98
125
|
* @param {object} state - datatable state
|
|
99
|
-
* @
|
|
126
|
+
* @param {event} event - custom DOM event sent by the cell
|
|
127
|
+
* @returns {object} - mutated state
|
|
100
128
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
function reactToKeyboardInActionMode(element, state, event) {
|
|
130
|
+
switch (event.detail.keyCode) {
|
|
131
|
+
case ARROW_LEFT:
|
|
132
|
+
return reactToArrowLeft(element, state, event);
|
|
133
|
+
case ARROW_RIGHT:
|
|
134
|
+
return reactToArrowRight(element, state, event);
|
|
135
|
+
case ARROW_UP:
|
|
136
|
+
return reactToArrowUp(element, state, event);
|
|
137
|
+
case ARROW_DOWN:
|
|
138
|
+
return reactToArrowDown(element, state, event);
|
|
139
|
+
case ENTER:
|
|
140
|
+
case SPACE:
|
|
141
|
+
return reactToEnter(element, state, event);
|
|
142
|
+
case ESCAPE:
|
|
143
|
+
return reactToEscape(element, state, event);
|
|
144
|
+
case TAB:
|
|
145
|
+
return reactToTab(element, state, event);
|
|
146
|
+
default:
|
|
147
|
+
return state;
|
|
110
148
|
}
|
|
111
|
-
|
|
112
|
-
};
|
|
149
|
+
}
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
151
|
+
function reactToKeyboardInNavMode(element, state, event) {
|
|
152
|
+
const syntheticEvent = {
|
|
153
|
+
detail: {
|
|
154
|
+
rowKeyValue: state.activeCell.rowKeyValue,
|
|
155
|
+
colKeyValue: state.activeCell.colKeyValue,
|
|
156
|
+
keyCode: event.keyCode,
|
|
157
|
+
shiftKey: event.shiftKey,
|
|
158
|
+
},
|
|
159
|
+
preventDefault: () => {},
|
|
160
|
+
stopPropagation: () => {},
|
|
161
|
+
};
|
|
117
162
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
// We need event.preventDefault so that actions like arrow up or down
|
|
164
|
+
// does not scroll the table but instead sets focus on the right cells
|
|
165
|
+
switch (event.keyCode) {
|
|
166
|
+
case ARROW_LEFT:
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
return reactToArrowLeft(element, state, syntheticEvent);
|
|
169
|
+
case ARROW_RIGHT:
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
return reactToArrowRight(element, state, syntheticEvent);
|
|
172
|
+
case ARROW_UP:
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
return reactToArrowUp(element, state, syntheticEvent);
|
|
175
|
+
case ARROW_DOWN:
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
return reactToArrowDown(element, state, syntheticEvent);
|
|
178
|
+
case ENTER:
|
|
179
|
+
case SPACE:
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
return reactToEnter(element, state, syntheticEvent);
|
|
182
|
+
case ESCAPE:
|
|
183
|
+
// td, th or div[role=gridcell/rowheader] is the active element in the
|
|
184
|
+
// action mode if cell doesn't have action elements; hence this can be
|
|
185
|
+
// reached and we should react to escape as exiting from action mode
|
|
186
|
+
syntheticEvent.detail.keyEvent = event;
|
|
187
|
+
return reactToEscape(element, state, syntheticEvent);
|
|
188
|
+
case TAB:
|
|
189
|
+
return reactToTab(element, state, syntheticEvent);
|
|
190
|
+
default:
|
|
191
|
+
return state;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function moveFromCellToRow(element, state) {
|
|
196
|
+
setBlurActiveCell(element, state);
|
|
197
|
+
setRowNavigationMode(state);
|
|
198
|
+
setFocusActiveRow(element, state);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function reactToArrowLeft(element, state, event) {
|
|
202
|
+
const { rowKeyValue, colKeyValue } = event.detail;
|
|
203
|
+
const { colIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
204
|
+
const { columns } = state;
|
|
205
|
+
|
|
206
|
+
// Move from navigation mode to row mode when user
|
|
207
|
+
// arrows left when in nav mode and on the first column
|
|
208
|
+
if (colIndex === 0 && canBeRowNavigationMode(state)) {
|
|
209
|
+
moveFromCellToRow(element, state);
|
|
210
|
+
} else {
|
|
211
|
+
const nextColIndex = getNextIndexLeft(state, colIndex);
|
|
212
|
+
|
|
213
|
+
if (nextColIndex === undefined) {
|
|
214
|
+
return;
|
|
141
215
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
216
|
+
|
|
217
|
+
setBlurActiveCell(element, state);
|
|
218
|
+
|
|
219
|
+
// update activeCell
|
|
220
|
+
state.activeCell = {
|
|
221
|
+
rowKeyValue,
|
|
222
|
+
colKeyValue: generateColKeyValue(
|
|
223
|
+
columns[nextColIndex],
|
|
224
|
+
nextColIndex
|
|
225
|
+
),
|
|
145
226
|
};
|
|
227
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.LEFT);
|
|
146
228
|
}
|
|
147
|
-
}
|
|
229
|
+
}
|
|
148
230
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
stillValidActiveCell(state)
|
|
158
|
-
) {
|
|
159
|
-
// if the previous focused is there and valid, dont set the prevActiveFocusedCell
|
|
160
|
-
state.cellToFocusNext = null;
|
|
231
|
+
function reactToArrowRight(element, state, event) {
|
|
232
|
+
const { rowKeyValue, colKeyValue } = event.detail;
|
|
233
|
+
const { colIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
234
|
+
const nextColIndex = getNextIndexRight(state, colIndex);
|
|
235
|
+
const { columns } = state;
|
|
236
|
+
|
|
237
|
+
if (nextColIndex === undefined) {
|
|
238
|
+
return;
|
|
161
239
|
}
|
|
162
|
-
};
|
|
163
240
|
|
|
164
|
-
|
|
165
|
-
* reset celltofocusnext to null (used after render)
|
|
166
|
-
* @param {object} state - datatable state
|
|
167
|
-
*/
|
|
168
|
-
export const resetCellToFocusFromPrev = function (state) {
|
|
169
|
-
state.cellToFocusNext = null;
|
|
170
|
-
};
|
|
241
|
+
setBlurActiveCell(element, state);
|
|
171
242
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* @param {object} state - datatable state
|
|
180
|
-
*/
|
|
181
|
-
function setNextActiveCellFromPrev(state) {
|
|
182
|
-
const { rowIndex, colIndex } = state.cellToFocusNext;
|
|
183
|
-
let nextRowIndex = rowIndex;
|
|
184
|
-
let nextColIndex = colIndex;
|
|
185
|
-
const rowsCount = state.rows ? state.rows.length : 0;
|
|
186
|
-
const colsCount = state.columns.length ? state.columns.length : 0;
|
|
243
|
+
// update activeCell
|
|
244
|
+
state.activeCell = {
|
|
245
|
+
rowKeyValue,
|
|
246
|
+
colKeyValue: generateColKeyValue(columns[nextColIndex], nextColIndex),
|
|
247
|
+
};
|
|
248
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.RIGHT);
|
|
249
|
+
}
|
|
187
250
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
251
|
+
function reactToArrowUp(element, state, event) {
|
|
252
|
+
const { rowKeyValue, colKeyValue, keyEvent } = event.detail;
|
|
253
|
+
const { rowIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
254
|
+
const nextRowIndex = getNextIndexUp(state, rowIndex);
|
|
255
|
+
const { rows } = state;
|
|
256
|
+
|
|
257
|
+
if (nextRowIndex === undefined) {
|
|
258
|
+
return;
|
|
191
259
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
260
|
+
|
|
261
|
+
if (state.hideTableHeader && nextRowIndex === -1) {
|
|
262
|
+
return;
|
|
195
263
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
nextColIndex
|
|
200
|
-
);
|
|
201
|
-
if (nextActiveCell) {
|
|
202
|
-
state.activeCell = nextActiveCell;
|
|
203
|
-
} else {
|
|
204
|
-
setDefaultActiveCell(state);
|
|
264
|
+
|
|
265
|
+
if (keyEvent) {
|
|
266
|
+
keyEvent.stopPropagation();
|
|
205
267
|
}
|
|
206
|
-
state.keyboardMode = 'NAVIGATION';
|
|
207
|
-
}
|
|
208
268
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
269
|
+
setBlurActiveCell(element, state);
|
|
270
|
+
|
|
271
|
+
// update activeCell
|
|
272
|
+
state.activeCell = {
|
|
273
|
+
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : HEADER_ROW,
|
|
274
|
+
colKeyValue,
|
|
275
|
+
};
|
|
276
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function reactToArrowDown(element, state, event) {
|
|
280
|
+
const { rowKeyValue, colKeyValue, keyEvent } = event.detail;
|
|
281
|
+
const { rowIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
282
|
+
const nextRowIndex = getNextIndexDown(state, rowIndex);
|
|
283
|
+
const { rows } = state;
|
|
284
|
+
|
|
285
|
+
if (nextRowIndex === undefined) {
|
|
286
|
+
return;
|
|
225
287
|
}
|
|
226
|
-
};
|
|
227
288
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
* as consequence of this change
|
|
231
|
-
* datatable is gonna re-render the row affected with the new tabindex value
|
|
232
|
-
*
|
|
233
|
-
* @param {object} state - datatable state
|
|
234
|
-
* @param {number} rowIndex - the row index
|
|
235
|
-
* @param {number} [index = 0] - the value for the tabindex
|
|
236
|
-
*/
|
|
237
|
-
export const updateTabIndexRow = function (state, rowIndex, index = 0) {
|
|
238
|
-
if (!isHeaderRow(rowIndex)) {
|
|
239
|
-
// TODO what to do when rowIndex is header row
|
|
240
|
-
state.rows[rowIndex].tabIndex = index;
|
|
289
|
+
if (state.hideTableHeader && nextRowIndex === -1) {
|
|
290
|
+
return;
|
|
241
291
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
* @param {object} state - datatable state
|
|
246
|
-
* @param {number} [index = 0] - the value for the tabindex
|
|
247
|
-
* @returns {object} state - mutated state
|
|
248
|
-
*/
|
|
249
|
-
export const updateTabIndexActiveCell = function (state, index = 0) {
|
|
250
|
-
if (state.activeCell && !stillValidActiveCell(state)) {
|
|
251
|
-
syncActiveCell(state);
|
|
292
|
+
|
|
293
|
+
if (keyEvent) {
|
|
294
|
+
keyEvent.stopPropagation();
|
|
252
295
|
}
|
|
253
296
|
|
|
254
|
-
|
|
255
|
-
|
|
297
|
+
setBlurActiveCell(element, state);
|
|
298
|
+
|
|
299
|
+
// update activeCell
|
|
300
|
+
state.activeCell = {
|
|
301
|
+
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : HEADER_ROW,
|
|
302
|
+
colKeyValue,
|
|
303
|
+
};
|
|
304
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function reactToEnter(element, state, event) {
|
|
308
|
+
if (state.keyboardMode === NAVIGATION_MODE) {
|
|
309
|
+
state.keyboardMode = ACTION_MODE;
|
|
256
310
|
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
257
|
-
updateTabIndex(state, rowIndex, colIndex, index);
|
|
258
|
-
}
|
|
259
|
-
return state;
|
|
260
|
-
};
|
|
261
311
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* @param {object} state - datatable state
|
|
266
|
-
* @param {number} [index = 0] - the value for the tabindex
|
|
267
|
-
* @returns {object} state - mutated state
|
|
268
|
-
*/
|
|
269
|
-
export const updateTabIndexActiveRow = function (state, index = 0) {
|
|
270
|
-
if (state.activeCell && !stillValidActiveCell(state)) {
|
|
271
|
-
syncActiveCell(state);
|
|
272
|
-
}
|
|
312
|
+
const actionsMap = {};
|
|
313
|
+
actionsMap[SPACE] = 'space';
|
|
314
|
+
actionsMap[ENTER] = 'enter';
|
|
273
315
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
316
|
+
if (event.detail.keyEvent) {
|
|
317
|
+
event.detail.keyEvent.preventDefault();
|
|
318
|
+
}
|
|
319
|
+
setModeActiveCell(element, state, {
|
|
320
|
+
action: actionsMap[event.detail.keyCode],
|
|
321
|
+
});
|
|
322
|
+
updateTabIndex(state, rowIndex, colIndex, -1);
|
|
278
323
|
}
|
|
279
|
-
|
|
280
|
-
};
|
|
324
|
+
}
|
|
281
325
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
* @param {object} state - state object
|
|
291
|
-
* @returns {object} state - mutated state
|
|
292
|
-
*/
|
|
293
|
-
export function updateRowNavigationMode(hadTreeDataTypePreviously, state) {
|
|
294
|
-
if (!hasTreeDataType(state)) {
|
|
295
|
-
state.rowMode = false;
|
|
296
|
-
} else if (state.rowMode === false && !hadTreeDataTypePreviously) {
|
|
297
|
-
state.rowMode = true;
|
|
326
|
+
function reactToEscape(element, state, event) {
|
|
327
|
+
if (state.keyboardMode === ACTION_MODE) {
|
|
328
|
+
// When the table is in action mode this event shouldn't bubble
|
|
329
|
+
// because if the table in inside a modal it should prevent the modal closes
|
|
330
|
+
event.detail.keyEvent.stopPropagation();
|
|
331
|
+
state.keyboardMode = NAVIGATION_MODE;
|
|
332
|
+
setModeActiveCell(element, state);
|
|
333
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.RESET);
|
|
298
334
|
}
|
|
299
|
-
return state;
|
|
300
335
|
}
|
|
301
336
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
* @param {object} state - datatable state
|
|
306
|
-
* @param {string} rowKeyValue - the row key value
|
|
307
|
-
* @param {string} colKeyValue - the column key value
|
|
308
|
-
* @returns {object} - {rowIndex, colIndex}
|
|
309
|
-
*/
|
|
310
|
-
export const getIndexesByKeys = function (state, rowKeyValue, colKeyValue) {
|
|
311
|
-
if (rowKeyValue === 'HEADER') {
|
|
312
|
-
return {
|
|
313
|
-
rowIndex: -1,
|
|
314
|
-
colIndex: state.headerIndexes[colKeyValue],
|
|
315
|
-
};
|
|
316
|
-
}
|
|
337
|
+
function reactToTab(element, state, event) {
|
|
338
|
+
event.preventDefault();
|
|
339
|
+
event.stopPropagation();
|
|
317
340
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
};
|
|
322
|
-
};
|
|
341
|
+
const { shiftKey } = event.detail;
|
|
342
|
+
const direction = getTabDirection(shiftKey);
|
|
343
|
+
const isExitCell = isActiveCellAnExitCell(state, direction);
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
345
|
+
// if in ACTION mode
|
|
346
|
+
if (state.keyboardMode === ACTION_MODE) {
|
|
347
|
+
// if not on last or first cell, tab through each cell of the grid
|
|
348
|
+
if (isExitCell === false) {
|
|
349
|
+
// prevent default key event in action mode when actually moving within the grid
|
|
350
|
+
if (event.detail.keyEvent) {
|
|
351
|
+
event.detail.keyEvent.preventDefault();
|
|
352
|
+
}
|
|
353
|
+
// tab in proper direction based on shift key press
|
|
354
|
+
if (direction === 'BACKWARD') {
|
|
355
|
+
reactToTabBackward(element, state);
|
|
356
|
+
} else {
|
|
357
|
+
reactToTabForward(element, state);
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
// exit ACTION mode
|
|
361
|
+
state.keyboardMode = NAVIGATION_MODE;
|
|
362
|
+
setModeActiveCell(element, state);
|
|
363
|
+
state.isExitingActionMode = true;
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
state.isExitingActionMode = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
337
369
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
setTimeout(() => {
|
|
342
|
-
const cellElement = getCellElementByIndexes(
|
|
343
|
-
element,
|
|
344
|
-
rowIndex,
|
|
345
|
-
colIndex
|
|
346
|
-
);
|
|
347
|
-
if (cellElement) {
|
|
348
|
-
if (direction) {
|
|
349
|
-
cellElement.resetCurrentInputIndex(direction, keyboardMode);
|
|
350
|
-
}
|
|
351
|
-
cellElement.addFocusStyles();
|
|
352
|
-
cellElement.parentElement.classList.add('slds-has-focus');
|
|
353
|
-
cellElement.parentElement.focus();
|
|
354
|
-
cellElement.setMode(keyboardMode, info);
|
|
370
|
+
export function reactToTabForward(element, state) {
|
|
371
|
+
const { nextRowIndex, nextColIndex } = getNextIndexOnTab(state, 'FORWARD');
|
|
372
|
+
const { columns, rows } = state;
|
|
355
373
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
) {
|
|
366
|
-
scrollableY.scrollTop += SCROLL_OFFSET;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
resolve();
|
|
370
|
-
}, 0);
|
|
374
|
+
setBlurActiveCell(element, state);
|
|
375
|
+
|
|
376
|
+
// update activeCell
|
|
377
|
+
state.activeCell = {
|
|
378
|
+
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : HEADER_ROW,
|
|
379
|
+
colKeyValue: generateColKeyValue(columns[nextColIndex], nextColIndex),
|
|
380
|
+
};
|
|
381
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.TAB_FORWARD, {
|
|
382
|
+
action: 'tab',
|
|
371
383
|
});
|
|
372
|
-
}
|
|
384
|
+
}
|
|
373
385
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
* @param {node} element - the custom element template `this.template`
|
|
378
|
-
* @param {object} state - datatable state
|
|
379
|
-
*/
|
|
380
|
-
export const addFocusStylesToActiveCell = function (element, state) {
|
|
381
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
386
|
+
export function reactToTabBackward(element, state) {
|
|
387
|
+
const { nextRowIndex, nextColIndex } = getNextIndexOnTab(state, 'BACKWARD');
|
|
388
|
+
const { columns, rows } = state;
|
|
382
389
|
|
|
383
|
-
|
|
390
|
+
setBlurActiveCell(element, state);
|
|
384
391
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
392
|
+
// update activeCell
|
|
393
|
+
state.activeCell = {
|
|
394
|
+
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : HEADER_ROW,
|
|
395
|
+
colKeyValue: generateColKeyValue(columns[nextColIndex], nextColIndex),
|
|
396
|
+
};
|
|
397
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.TAB_BACKWARD, {
|
|
398
|
+
action: 'tab',
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function getTabDirection(shiftKey) {
|
|
403
|
+
return shiftKey ? 'BACKWARD' : 'FORWARD';
|
|
404
|
+
}
|
|
389
405
|
|
|
390
406
|
/**
|
|
391
|
-
*
|
|
392
|
-
* - blur the activeCell
|
|
393
|
-
* - update the tabindex to -1
|
|
394
|
-
* @param {node} element - the custom element root `this.template`
|
|
395
|
-
* @param {object} state - datatable state
|
|
396
|
-
*/
|
|
397
|
-
export const setBlurActiveCell = function (element, state) {
|
|
398
|
-
if (state.activeCell) {
|
|
399
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
400
|
-
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
401
|
-
setTimeout(() => {
|
|
402
|
-
const cellElement = getCellElementByIndexes(
|
|
403
|
-
element,
|
|
404
|
-
rowIndex,
|
|
405
|
-
colIndex
|
|
406
|
-
);
|
|
407
|
-
// we need to check because of the tree,
|
|
408
|
-
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
409
|
-
if (cellElement) {
|
|
410
|
-
if (document.activeElement === cellElement) {
|
|
411
|
-
cellElement.blur();
|
|
412
|
-
}
|
|
413
|
-
cellElement.removeFocusStyles(true);
|
|
414
|
-
cellElement.parentElement.classList.remove('slds-has-focus');
|
|
415
|
-
}
|
|
416
|
-
}, 0);
|
|
417
|
-
updateTabIndex(state, rowIndex, colIndex, -1);
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
/**
|
|
421
|
-
* It set the focus to the current activeCell, this operation imply multiple changes
|
|
422
|
-
* - update the tabindex of the activeCell
|
|
423
|
-
* - set the current keyboard mode
|
|
424
|
-
* - set the focus to the cell
|
|
425
|
-
* @param {node} element - the custom element root `this.template`
|
|
407
|
+
* Retrieve the next index values for row & column when tab is pressed
|
|
426
408
|
* @param {object} state - datatable state
|
|
409
|
+
* @param {string} direction - 'FORWARD' or 'BACKWARD'
|
|
410
|
+
* @returns {object} - nextRowIndex, nextColIndex values, isExitCell boolean
|
|
427
411
|
*/
|
|
428
|
-
|
|
429
|
-
const { rowIndex } = getIndexesActiveCell(state);
|
|
412
|
+
function getNextIndexOnTab(state, direction) {
|
|
413
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
430
414
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
415
|
+
// decide which function to use based on the value of direction
|
|
416
|
+
const nextTabFunc = {
|
|
417
|
+
FORWARD: getNextIndexOnTabForward,
|
|
418
|
+
BACKWARD: getNextIndexOnTabBackward,
|
|
419
|
+
};
|
|
436
420
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const parentRect = scrollingParent.getBoundingClientRect();
|
|
440
|
-
const findMeRect = row.getBoundingClientRect();
|
|
441
|
-
if (findMeRect.top < parentRect.top + TOP_MARGIN) {
|
|
442
|
-
scrollableY.scrollTop -= SCROLL_OFFSET;
|
|
443
|
-
} else if (findMeRect.bottom > parentRect.bottom - BOTTOM_MARGIN) {
|
|
444
|
-
scrollableY.scrollTop += SCROLL_OFFSET;
|
|
445
|
-
}
|
|
446
|
-
}, 0);
|
|
447
|
-
};
|
|
421
|
+
return nextTabFunc[direction](state, rowIndex, colIndex);
|
|
422
|
+
}
|
|
448
423
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
export const setBlurActiveRow = function (element, state) {
|
|
457
|
-
if (state.activeCell) {
|
|
458
|
-
const { rowIndex } = getIndexesActiveCell(state);
|
|
459
|
-
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
460
|
-
setTimeout(() => {
|
|
461
|
-
const row = getRowElementByIndexes(element, rowIndex);
|
|
462
|
-
if (document.activeElement === row) {
|
|
463
|
-
row.blur();
|
|
464
|
-
}
|
|
465
|
-
}, 0);
|
|
466
|
-
updateTabIndexRow(state, rowIndex, -1);
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
/**
|
|
470
|
-
* It changes the datable state based on the keyboard event sent from the cell component,
|
|
471
|
-
* the result of those change may trigger re-render on the table
|
|
472
|
-
* @param {node} element - the custom element root `this.template`
|
|
473
|
-
* @param {object} state - datatable state
|
|
474
|
-
* @param {event} event - custom DOM event sent by the cell
|
|
475
|
-
* @returns {object} - mutated state
|
|
476
|
-
*/
|
|
477
|
-
export const reactToKeyboard = function (element, state, event) {
|
|
478
|
-
switch (event.detail.keyCode) {
|
|
479
|
-
case ARROW_RIGHT:
|
|
480
|
-
return reactToArrowRight(element, state, event);
|
|
481
|
-
case ARROW_LEFT:
|
|
482
|
-
return reactToArrowLeft(element, state, event);
|
|
483
|
-
case ARROW_DOWN:
|
|
484
|
-
return reactToArrowDown(element, state, event);
|
|
485
|
-
case ARROW_UP:
|
|
486
|
-
return reactToArrowUp(element, state, event);
|
|
487
|
-
case ENTER:
|
|
488
|
-
case SPACE:
|
|
489
|
-
return reactToEnter(element, state, event);
|
|
490
|
-
case ESCAPE:
|
|
491
|
-
return reactToEscape(element, state, event);
|
|
492
|
-
case TAB:
|
|
493
|
-
return reactToTab(element, state, event);
|
|
494
|
-
default:
|
|
495
|
-
return state;
|
|
424
|
+
function getNextIndexOnTabForward(state, rowIndex, colIndex) {
|
|
425
|
+
const columnsCount = state.columns.length;
|
|
426
|
+
if (columnsCount > colIndex + 1) {
|
|
427
|
+
return {
|
|
428
|
+
nextRowIndex: rowIndex,
|
|
429
|
+
nextColIndex: colIndex + 1,
|
|
430
|
+
};
|
|
496
431
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const mockEvent = {
|
|
501
|
-
detail: {
|
|
502
|
-
rowKeyValue: state.activeCell.rowKeyValue,
|
|
503
|
-
colKeyValue: state.activeCell.colKeyValue,
|
|
504
|
-
keyCode: event.keyCode,
|
|
505
|
-
shiftKey: event.shiftKey,
|
|
506
|
-
},
|
|
507
|
-
preventDefault: () => {},
|
|
508
|
-
stopPropagation: () => {},
|
|
432
|
+
return {
|
|
433
|
+
nextRowIndex: getNextIndexDownWrapped(state, rowIndex),
|
|
434
|
+
nextColIndex: 0,
|
|
509
435
|
};
|
|
436
|
+
}
|
|
510
437
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
case ARROW_DOWN:
|
|
519
|
-
event.preventDefault();
|
|
520
|
-
return reactToArrowDown(element, state, mockEvent);
|
|
521
|
-
case ARROW_UP:
|
|
522
|
-
event.preventDefault();
|
|
523
|
-
return reactToArrowUp(element, state, mockEvent);
|
|
524
|
-
case ENTER:
|
|
525
|
-
case SPACE:
|
|
526
|
-
event.preventDefault();
|
|
527
|
-
return reactToEnter(element, state, mockEvent);
|
|
528
|
-
case ESCAPE:
|
|
529
|
-
// td, th is the active element in the action mode if cell doesnt have action elements
|
|
530
|
-
// hence this can be reached and we should react to escape as exiting from action mode
|
|
531
|
-
mockEvent.detail.keyEvent = event;
|
|
532
|
-
return reactToEscape(element, state, mockEvent);
|
|
533
|
-
case TAB:
|
|
534
|
-
// event.preventDefault();
|
|
535
|
-
return reactToTab(element, state, mockEvent);
|
|
536
|
-
default:
|
|
537
|
-
return state;
|
|
438
|
+
function getNextIndexOnTabBackward(state, rowIndex, colIndex) {
|
|
439
|
+
const columnsCount = state.columns.length;
|
|
440
|
+
if (colIndex > 0) {
|
|
441
|
+
return {
|
|
442
|
+
nextRowIndex: rowIndex,
|
|
443
|
+
nextColIndex: colIndex - 1,
|
|
444
|
+
};
|
|
538
445
|
}
|
|
446
|
+
return {
|
|
447
|
+
nextRowIndex: getNextIndexUpWrapped(state, rowIndex),
|
|
448
|
+
nextColIndex: columnsCount - 1,
|
|
449
|
+
};
|
|
539
450
|
}
|
|
540
451
|
|
|
541
|
-
|
|
452
|
+
/**
|
|
453
|
+
* This set of keyboard actions is specific to tree-grid.
|
|
454
|
+
*
|
|
455
|
+
* When the user first tabs into the tree-grid, the user is set in row mode
|
|
456
|
+
* and the entire row is highlighted.
|
|
457
|
+
*
|
|
458
|
+
* Keyboard Interaction Model:
|
|
459
|
+
* Arrow Up: Moves focus to the row above
|
|
460
|
+
* Arrow Down: Moves focus to the row below
|
|
461
|
+
* Arrow Right: Expands the row to reveal nested items if any
|
|
462
|
+
* Pressing the right arrow again will set focus on a cell
|
|
463
|
+
* and will remove the user from row mode and place them in navigation mode
|
|
464
|
+
* Arrow Left: If cell is expanded, this will collapse the expanded row
|
|
465
|
+
*
|
|
466
|
+
* @param {*} datatable - The datatable component/instance
|
|
467
|
+
* @param {*} state - The datatable state object
|
|
468
|
+
* @param {*} event - The keydown event
|
|
469
|
+
* @returns Mutated state
|
|
470
|
+
*/
|
|
471
|
+
export function reactToKeyboardOnRow(datatable, state, event) {
|
|
472
|
+
// TODO: Adapt this selector to also work in a role-based table once tree-grid is also migrated
|
|
542
473
|
if (
|
|
543
474
|
isRowNavigationMode(state) &&
|
|
544
475
|
event.target.localName.indexOf('tr') !== -1
|
|
545
476
|
) {
|
|
546
|
-
const element =
|
|
477
|
+
const element = datatable.template;
|
|
547
478
|
switch (event.detail.keyCode) {
|
|
548
|
-
case ARROW_RIGHT:
|
|
549
|
-
return reactToArrowRightOnRow.call(dt, element, state, event);
|
|
550
479
|
case ARROW_LEFT:
|
|
551
|
-
return reactToArrowLeftOnRow.call(
|
|
552
|
-
|
|
553
|
-
|
|
480
|
+
return reactToArrowLeftOnRow.call(
|
|
481
|
+
datatable,
|
|
482
|
+
element,
|
|
483
|
+
state,
|
|
484
|
+
event
|
|
485
|
+
);
|
|
486
|
+
case ARROW_RIGHT:
|
|
487
|
+
return reactToArrowRightOnRow.call(
|
|
488
|
+
datatable,
|
|
489
|
+
element,
|
|
490
|
+
state,
|
|
491
|
+
event
|
|
492
|
+
);
|
|
554
493
|
case ARROW_UP:
|
|
555
|
-
return reactToArrowUpOnRow.call(
|
|
494
|
+
return reactToArrowUpOnRow.call(
|
|
495
|
+
datatable,
|
|
496
|
+
element,
|
|
497
|
+
state,
|
|
498
|
+
event
|
|
499
|
+
);
|
|
500
|
+
case ARROW_DOWN:
|
|
501
|
+
return reactToArrowDownOnRow.call(
|
|
502
|
+
datatable,
|
|
503
|
+
element,
|
|
504
|
+
state,
|
|
505
|
+
event
|
|
506
|
+
);
|
|
556
507
|
default:
|
|
557
508
|
return state;
|
|
558
509
|
}
|
|
559
510
|
}
|
|
560
511
|
return state;
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
function isRowNavigationMode(state) {
|
|
564
|
-
return state.keyboardMode === 'NAVIGATION' && state.rowMode === true;
|
|
565
512
|
}
|
|
566
513
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
514
|
+
function reactToArrowLeftOnRow(element, state, event) {
|
|
515
|
+
const { rowKeyValue, rowHasChildren, rowExpanded, rowLevel } = event.detail;
|
|
516
|
+
// check if row needs to be collapsed
|
|
517
|
+
// if not go to parent and focus there
|
|
518
|
+
if (rowHasChildren && rowExpanded) {
|
|
519
|
+
fireRowToggleEvent.call(this, rowKeyValue, rowExpanded);
|
|
520
|
+
} else if (rowLevel > 1) {
|
|
521
|
+
const treeColumn = getStateTreeColumn(state);
|
|
522
|
+
if (treeColumn) {
|
|
523
|
+
const colKeyValue = treeColumn.colKeyValue;
|
|
524
|
+
const { rowIndex } = getIndexesByKeys(
|
|
525
|
+
state,
|
|
526
|
+
rowKeyValue,
|
|
527
|
+
colKeyValue
|
|
528
|
+
);
|
|
529
|
+
const parentIndex = getRowParent(state, rowLevel, rowIndex);
|
|
530
|
+
if (parentIndex !== -1) {
|
|
531
|
+
const rows = state.rows;
|
|
532
|
+
setBlurActiveRow(element, state);
|
|
533
|
+
// update activeCell for the row
|
|
534
|
+
state.activeCell = {
|
|
535
|
+
rowKeyValue: rows[parentIndex].key,
|
|
536
|
+
colKeyValue,
|
|
537
|
+
};
|
|
538
|
+
setFocusActiveRow(element, state);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
570
541
|
}
|
|
571
542
|
}
|
|
572
543
|
|
|
573
|
-
|
|
574
|
-
state
|
|
544
|
+
function moveFromRowToCell(element, state) {
|
|
545
|
+
setBlurActiveRow(element, state);
|
|
546
|
+
unsetRowNavigationMode(state);
|
|
547
|
+
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
575
548
|
}
|
|
576
549
|
|
|
577
|
-
|
|
578
|
-
|
|
550
|
+
function reactToArrowRightOnRow(element, state, event) {
|
|
551
|
+
const { rowKeyValue, rowHasChildren, rowExpanded } = event.detail;
|
|
552
|
+
// check if row needs to be expanded
|
|
553
|
+
// expand row if has children and is collapsed
|
|
554
|
+
// otherwise make this.state.rowMode = false
|
|
555
|
+
// move tabindex 0 to first cell in the row and focus there
|
|
556
|
+
if (rowHasChildren && !rowExpanded) {
|
|
557
|
+
fireRowToggleEvent.call(this, rowKeyValue, rowExpanded);
|
|
558
|
+
} else {
|
|
559
|
+
moveFromRowToCell(element, state);
|
|
560
|
+
}
|
|
579
561
|
}
|
|
580
562
|
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
}
|
|
563
|
+
function reactToArrowUpOnRow(element, state, event) {
|
|
564
|
+
// move tabindex 0 one row down
|
|
565
|
+
const { rowKeyValue, keyEvent } = event.detail;
|
|
566
|
+
const treeColumn = getStateTreeColumn(state);
|
|
584
567
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
return element.querySelector(
|
|
588
|
-
`thead > :nth-child(1) >
|
|
589
|
-
:nth-child(${colIndex + 1}) > :first-child`
|
|
590
|
-
);
|
|
591
|
-
}
|
|
568
|
+
keyEvent.stopPropagation();
|
|
569
|
+
keyEvent.preventDefault();
|
|
592
570
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
571
|
+
if (treeColumn) {
|
|
572
|
+
const colKeyValue = treeColumn.colKeyValue;
|
|
573
|
+
const { rowIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
574
|
+
const prevRowIndex = getNextIndexUpWrapped(state, rowIndex);
|
|
575
|
+
const { rows } = state;
|
|
576
|
+
if (prevRowIndex !== -1) {
|
|
577
|
+
setBlurActiveRow(element, state);
|
|
578
|
+
// update activeCell for the row
|
|
579
|
+
state.activeCell = {
|
|
580
|
+
rowKeyValue: rows[prevRowIndex].key,
|
|
581
|
+
colKeyValue,
|
|
582
|
+
};
|
|
583
|
+
setFocusActiveRow(element, state);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
597
586
|
}
|
|
598
587
|
|
|
599
|
-
function
|
|
600
|
-
|
|
601
|
-
|
|
588
|
+
function reactToArrowDownOnRow(element, state, event) {
|
|
589
|
+
// move tabindex 0 one row down
|
|
590
|
+
const { rowKeyValue, keyEvent } = event.detail;
|
|
591
|
+
const treeColumn = getStateTreeColumn(state);
|
|
592
|
+
|
|
593
|
+
keyEvent.stopPropagation();
|
|
594
|
+
keyEvent.preventDefault();
|
|
595
|
+
|
|
596
|
+
if (treeColumn) {
|
|
597
|
+
const colKeyValue = treeColumn.colKeyValue;
|
|
598
|
+
const { rowIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
599
|
+
const nextRowIndex = getNextIndexDownWrapped(state, rowIndex);
|
|
600
|
+
const { rows } = state;
|
|
601
|
+
if (nextRowIndex !== -1) {
|
|
602
|
+
setBlurActiveRow(element, state);
|
|
603
|
+
// update activeCell for the row
|
|
604
|
+
state.activeCell = {
|
|
605
|
+
rowKeyValue: rows[nextRowIndex].key,
|
|
606
|
+
colKeyValue,
|
|
607
|
+
};
|
|
608
|
+
setFocusActiveRow(element, state);
|
|
609
|
+
}
|
|
602
610
|
}
|
|
603
|
-
return element.querySelector(`tbody > tr:nth-child(${rowIndex + 1})`);
|
|
604
611
|
}
|
|
605
612
|
|
|
606
|
-
|
|
607
|
-
if (state.keyboardMode === 'NAVIGATION') {
|
|
608
|
-
state.keyboardMode = 'ACTION';
|
|
609
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
613
|
+
/***************************** ACTIVE CELL *****************************/
|
|
610
614
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
615
|
+
function getDefaultActiveCell(state) {
|
|
616
|
+
const { columns, rows } = state;
|
|
617
|
+
if (columns.length > 0) {
|
|
618
|
+
let colIndex;
|
|
619
|
+
const existCustomerColumn = columns.some((column, index) => {
|
|
620
|
+
colIndex = index;
|
|
621
|
+
return isCustomerColumn(column);
|
|
622
|
+
});
|
|
614
623
|
|
|
615
|
-
if (
|
|
616
|
-
|
|
624
|
+
if (!existCustomerColumn) {
|
|
625
|
+
colIndex = 0;
|
|
617
626
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
rowKeyValue: rows.length > 0 ? rows[0].key : HEADER_ROW,
|
|
630
|
+
colKeyValue: generateColKeyValue(columns[colIndex], colIndex),
|
|
631
|
+
};
|
|
622
632
|
}
|
|
633
|
+
|
|
634
|
+
return undefined;
|
|
623
635
|
}
|
|
624
636
|
|
|
625
|
-
function
|
|
626
|
-
|
|
627
|
-
// When the table is in action mode this event shouldn't bubble
|
|
628
|
-
// because if the table in inside a modal it should prevent the model closes
|
|
629
|
-
event.detail.keyEvent.stopPropagation();
|
|
630
|
-
state.keyboardMode = 'NAVIGATION';
|
|
631
|
-
setModeActiveCell(element, state);
|
|
632
|
-
setFocusActiveCell(element, state, NAVIGATION_DIR.RESET);
|
|
633
|
-
}
|
|
637
|
+
function setDefaultActiveCell(state) {
|
|
638
|
+
state.activeCell = getDefaultActiveCell(state);
|
|
634
639
|
}
|
|
635
640
|
|
|
636
641
|
/**
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* @
|
|
642
|
+
* Given a datatable template and state, returns an LWC component reference that represents
|
|
643
|
+
* the currently active cell in the table.
|
|
644
|
+
*
|
|
645
|
+
* @param {Object} element - A reference to the datatable's template
|
|
646
|
+
* @param {Object} state - A reference to the datatable's state
|
|
641
647
|
*/
|
|
642
|
-
function
|
|
648
|
+
export function getActiveCellElement(element, state) {
|
|
643
649
|
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
650
|
+
return getCellElementByIndexes(element, rowIndex, colIndex, state);
|
|
651
|
+
}
|
|
644
652
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
653
|
+
/**
|
|
654
|
+
* Returns if the pair rowKeyValue, colKeyValue are the current activeCell values
|
|
655
|
+
*
|
|
656
|
+
* @param {object} state - datatable state
|
|
657
|
+
* @param {string} rowKeyValue - the unique row key value
|
|
658
|
+
* @param {string} colKeyValue {string} - the unique col key value
|
|
659
|
+
* @returns {boolean} - true if rowKeyValue, colKeyValue are the current activeCell values.
|
|
660
|
+
*/
|
|
661
|
+
export function isActiveCell(state, rowKeyValue, colKeyValue) {
|
|
662
|
+
if (state.activeCell) {
|
|
663
|
+
const {
|
|
664
|
+
rowKeyValue: currentRowKeyValue,
|
|
665
|
+
colKeyValue: currentColKeyValue,
|
|
666
|
+
} = state.activeCell;
|
|
667
|
+
return (
|
|
668
|
+
currentRowKeyValue === rowKeyValue &&
|
|
669
|
+
currentColKeyValue === colKeyValue
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Updates the current activeCell in the state with the new rowKeyValue, colKeyValue
|
|
677
|
+
* @param {object} state - datatable state
|
|
678
|
+
* @param {string} rowKeyValue - the unique row key value
|
|
679
|
+
* @param {string} colKeyValue {string} - the unique col key value
|
|
680
|
+
* @returns {object} state - mutated datatable state
|
|
681
|
+
*/
|
|
682
|
+
export function updateActiveCell(state, rowKeyValue, colKeyValue) {
|
|
683
|
+
state.activeCell = {
|
|
684
|
+
rowKeyValue,
|
|
685
|
+
colKeyValue,
|
|
649
686
|
};
|
|
687
|
+
return state;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* It check if in the current (data, columns) the activeCell still valid.
|
|
692
|
+
* When data changed the activeCell could be removed, then we check if there is cellToFocusNext
|
|
693
|
+
* which is calculated from previously focused cell, if so we sync to that
|
|
694
|
+
* If active cell is still valid we keep it the same
|
|
695
|
+
*
|
|
696
|
+
* @param {object} state - datatable state
|
|
697
|
+
* @returns {object} state - mutated datatable state
|
|
698
|
+
*/
|
|
699
|
+
export function syncActiveCell(state) {
|
|
700
|
+
if (!state.activeCell || !stillValidActiveCell(state)) {
|
|
701
|
+
if (state.activeCell && state.cellToFocusNext) {
|
|
702
|
+
// there is previously focused cell
|
|
703
|
+
setNextActiveCellFromPrev(state);
|
|
704
|
+
} else {
|
|
705
|
+
// there is no active cell or there is no previously focused cell
|
|
706
|
+
setDefaultActiveCell(state);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return state;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Sets the next active if there is a previously focused active cell
|
|
714
|
+
* Logic is:
|
|
715
|
+
* if the rowIndex is existing one - cell = (rowIndex, 0)
|
|
716
|
+
* if the rowIndex is > the number of rows (focused was last row or more) = (lastRow, lastColumn)
|
|
717
|
+
* for columns
|
|
718
|
+
* same as above except if the colIndex is > the number of cols (means no data) = set it to null??
|
|
719
|
+
* @param {object} state - datatable state
|
|
720
|
+
*/
|
|
721
|
+
function setNextActiveCellFromPrev(state) {
|
|
722
|
+
const { rowIndex, colIndex } = state.cellToFocusNext;
|
|
723
|
+
let nextRowIndex = rowIndex;
|
|
724
|
+
let nextColIndex = colIndex;
|
|
725
|
+
const rowsCount = state.rows ? state.rows.length : 0;
|
|
726
|
+
const colsCount = state.columns.length ? state.columns.length : 0;
|
|
650
727
|
|
|
651
|
-
|
|
728
|
+
if (nextRowIndex > rowsCount - 1) {
|
|
729
|
+
// row index not existing after update to new 5 > 5-1, 6 > 5-1,
|
|
730
|
+
nextRowIndex = rowsCount - 1;
|
|
731
|
+
}
|
|
732
|
+
if (nextColIndex > colsCount - 1) {
|
|
733
|
+
// col index not existing after update to new
|
|
734
|
+
nextColIndex = colsCount - 1;
|
|
735
|
+
}
|
|
736
|
+
const nextActiveCell = getCellFromIndexes(
|
|
737
|
+
state,
|
|
738
|
+
nextRowIndex,
|
|
739
|
+
nextColIndex
|
|
740
|
+
);
|
|
741
|
+
if (nextActiveCell) {
|
|
742
|
+
state.activeCell = nextActiveCell;
|
|
743
|
+
} else {
|
|
744
|
+
setDefaultActiveCell(state);
|
|
745
|
+
}
|
|
746
|
+
state.keyboardMode = NAVIGATION_MODE;
|
|
652
747
|
}
|
|
653
748
|
|
|
654
749
|
/**
|
|
@@ -660,7 +755,7 @@ function getNextTabIndex(state, direction) {
|
|
|
660
755
|
export function isActiveCellAnExitCell(state, direction) {
|
|
661
756
|
// get next tab index values
|
|
662
757
|
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
663
|
-
const { nextRowIndex, nextColIndex } =
|
|
758
|
+
const { nextRowIndex, nextColIndex } = getNextIndexOnTab(state, direction);
|
|
664
759
|
// is it an exit cell?
|
|
665
760
|
if (
|
|
666
761
|
// if first cell and moving backward
|
|
@@ -677,41 +772,11 @@ export function isActiveCellAnExitCell(state, direction) {
|
|
|
677
772
|
return false;
|
|
678
773
|
}
|
|
679
774
|
|
|
680
|
-
function
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const direction = getTabDirection(shiftKey);
|
|
686
|
-
const isExitCell = isActiveCellAnExitCell(state, direction);
|
|
687
|
-
|
|
688
|
-
// if in ACTION mode
|
|
689
|
-
if (state.keyboardMode === 'ACTION') {
|
|
690
|
-
// if not on last or first cell, tab through each cell of the grid
|
|
691
|
-
if (isExitCell === false) {
|
|
692
|
-
// prevent default key event in action mode when actually moving within the grid
|
|
693
|
-
if (event.detail.keyEvent) {
|
|
694
|
-
event.detail.keyEvent.preventDefault();
|
|
695
|
-
}
|
|
696
|
-
// tab in proper direction based on shift key press
|
|
697
|
-
if (direction === 'BACKWARD') {
|
|
698
|
-
reactToTabBackward(element, state);
|
|
699
|
-
} else {
|
|
700
|
-
reactToTabForward(element, state);
|
|
701
|
-
}
|
|
702
|
-
} else {
|
|
703
|
-
// exit ACTION mode
|
|
704
|
-
state.keyboardMode = 'NAVIGATION';
|
|
705
|
-
setModeActiveCell(element, state);
|
|
706
|
-
state.isExiting = true;
|
|
707
|
-
}
|
|
708
|
-
} else {
|
|
709
|
-
state.isExiting = true;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function getTabDirection(shiftKey) {
|
|
714
|
-
return shiftKey ? 'BACKWARD' : 'FORWARD';
|
|
775
|
+
export function getIndexesActiveCell(state) {
|
|
776
|
+
const {
|
|
777
|
+
activeCell: { rowKeyValue, colKeyValue },
|
|
778
|
+
} = state;
|
|
779
|
+
return getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
715
780
|
}
|
|
716
781
|
|
|
717
782
|
function setModeActiveCell(element, state, info) {
|
|
@@ -721,260 +786,440 @@ function setModeActiveCell(element, state, info) {
|
|
|
721
786
|
}
|
|
722
787
|
}
|
|
723
788
|
|
|
724
|
-
|
|
725
|
-
* Given a datatable template and state, returns an LWC component reference that represents
|
|
726
|
-
* the currently active cell in the table.
|
|
727
|
-
*
|
|
728
|
-
* @param {Object} element - A reference to the datatable's template
|
|
729
|
-
* @param {Object} state - A reference to the datatable's state
|
|
730
|
-
*/
|
|
731
|
-
export function getActiveCellElement(element, state) {
|
|
732
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
733
|
-
return getCellElementByIndexes(element, rowIndex, colIndex);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
export function getIndexesActiveCell(state) {
|
|
789
|
+
function stillValidActiveCell(state) {
|
|
737
790
|
const {
|
|
738
791
|
activeCell: { rowKeyValue, colKeyValue },
|
|
739
792
|
} = state;
|
|
740
|
-
|
|
793
|
+
if (rowKeyValue === HEADER_ROW) {
|
|
794
|
+
return state.headerIndexes[colKeyValue] !== undefined;
|
|
795
|
+
}
|
|
796
|
+
return !!(
|
|
797
|
+
state.indexes[rowKeyValue] && state.indexes[rowKeyValue][colKeyValue]
|
|
798
|
+
);
|
|
741
799
|
}
|
|
742
800
|
|
|
743
|
-
|
|
744
|
-
const { rowKeyValue, colKeyValue } = event.detail;
|
|
745
|
-
const { colIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
746
|
-
const nextColIndex = getNextIndexRight(state, colIndex);
|
|
747
|
-
const { columns } = state;
|
|
748
|
-
|
|
749
|
-
if (nextColIndex === undefined) {
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
801
|
+
/***************************** FOCUS MANAGEMENT *****************************/
|
|
752
802
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
803
|
+
/**
|
|
804
|
+
* It set the focus to the current activeCell, this operation imply multiple changes
|
|
805
|
+
* - update the tabindex of the activeCell
|
|
806
|
+
* - set the current keyboard mode
|
|
807
|
+
* - set the focus to the cell
|
|
808
|
+
* @param {node} element - the custom element template `this.template`
|
|
809
|
+
* @param {object} state - datatable state
|
|
810
|
+
* @param {int} direction - direction (-1 left, 1 right and 0 for no direction) its used to know which actionable element to activate.
|
|
811
|
+
* @param {object} info - extra information when setting the cell mode.
|
|
812
|
+
*/
|
|
813
|
+
export function setFocusActiveCell(element, state, direction, info) {
|
|
814
|
+
const { keyboardMode } = state;
|
|
815
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
761
816
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
817
|
+
updateTabIndex(state, rowIndex, colIndex);
|
|
818
|
+
return new Promise((resolve) => {
|
|
819
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
820
|
+
setTimeout(() => {
|
|
821
|
+
const cellElement = getCellElementByIndexes(
|
|
822
|
+
element,
|
|
823
|
+
rowIndex,
|
|
824
|
+
colIndex,
|
|
825
|
+
state
|
|
826
|
+
);
|
|
827
|
+
if (cellElement) {
|
|
828
|
+
if (direction) {
|
|
829
|
+
cellElement.resetCurrentInputIndex(direction, keyboardMode);
|
|
830
|
+
}
|
|
831
|
+
cellElement.addFocusStyles();
|
|
832
|
+
cellElement.parentElement.classList.add(FOCUS_CLASS);
|
|
833
|
+
cellElement.parentElement.focus();
|
|
834
|
+
cellElement.setMode(keyboardMode, info);
|
|
769
835
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
836
|
+
const scrollableY = element.querySelector('.slds-scrollable_y');
|
|
837
|
+
const scrollingParent = scrollableY.parentElement;
|
|
838
|
+
const parentRect = scrollingParent.getBoundingClientRect();
|
|
839
|
+
const findMeRect = cellElement.getBoundingClientRect();
|
|
840
|
+
if (findMeRect.top < parentRect.top + TOP_MARGIN) {
|
|
841
|
+
scrollableY.scrollTop -= SCROLL_OFFSET;
|
|
842
|
+
} else if (
|
|
843
|
+
findMeRect.bottom >
|
|
844
|
+
parentRect.bottom - BOTTOM_MARGIN
|
|
845
|
+
) {
|
|
846
|
+
scrollableY.scrollTop += SCROLL_OFFSET;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
resolve();
|
|
850
|
+
}, 0);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
773
853
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
};
|
|
784
|
-
|
|
854
|
+
/**
|
|
855
|
+
* It blur to the current activeCell, this operation imply multiple changes
|
|
856
|
+
* - blur the activeCell
|
|
857
|
+
* - update the tabindex to -1
|
|
858
|
+
* @param {node} element - the custom element root `this.template`
|
|
859
|
+
* @param {object} state - datatable state
|
|
860
|
+
*/
|
|
861
|
+
export function setBlurActiveCell(element, state) {
|
|
862
|
+
if (state.activeCell) {
|
|
863
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
864
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
865
|
+
setTimeout(() => {
|
|
866
|
+
const cellElement = getCellElementByIndexes(
|
|
867
|
+
element,
|
|
868
|
+
rowIndex,
|
|
869
|
+
colIndex,
|
|
870
|
+
state
|
|
871
|
+
);
|
|
872
|
+
// we need to check because of the tree,
|
|
873
|
+
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
874
|
+
if (cellElement) {
|
|
875
|
+
if (document.activeElement === cellElement) {
|
|
876
|
+
cellElement.blur();
|
|
877
|
+
}
|
|
878
|
+
cellElement.removeFocusStyles(true);
|
|
879
|
+
cellElement.parentElement.classList.remove(FOCUS_CLASS);
|
|
880
|
+
}
|
|
881
|
+
}, 0);
|
|
882
|
+
updateTabIndex(state, rowIndex, colIndex, -1);
|
|
785
883
|
}
|
|
786
884
|
}
|
|
787
885
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
886
|
+
/**
|
|
887
|
+
* Sets the row and col index of cell to focus next if
|
|
888
|
+
* there is state.activecell
|
|
889
|
+
* datatable has focus
|
|
890
|
+
* there is state.indexes
|
|
891
|
+
* there is no previously set state.cellToFocusNext
|
|
892
|
+
* Indexes are calculated as to what to focus on next
|
|
893
|
+
* @param {object} state - datatable state
|
|
894
|
+
* @param {object} template - datatable element
|
|
895
|
+
*/
|
|
896
|
+
export function setCellToFocusFromPrev(state, template) {
|
|
897
|
+
if (
|
|
898
|
+
state.activeCell &&
|
|
899
|
+
datatableHasFocus(state, template) &&
|
|
900
|
+
state.indexes &&
|
|
901
|
+
!state.cellToFocusNext
|
|
902
|
+
) {
|
|
903
|
+
let { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
904
|
+
colIndex = 0; // default point to the first column
|
|
905
|
+
if (state.rows && rowIndex === state.rows.length - 1) {
|
|
906
|
+
// if it is last row, make it point to its previous row
|
|
907
|
+
rowIndex = state.rows.length - 1;
|
|
908
|
+
colIndex = state.columns ? state.columns.length - 1 : 0;
|
|
909
|
+
}
|
|
910
|
+
state.cellToFocusNext = {
|
|
911
|
+
rowIndex,
|
|
912
|
+
colIndex,
|
|
913
|
+
};
|
|
798
914
|
}
|
|
799
915
|
}
|
|
800
916
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
rowKeyValue,
|
|
814
|
-
colKeyValue
|
|
815
|
-
);
|
|
816
|
-
const parentIndex = getRowParent(state, rowLevel, rowIndex);
|
|
817
|
-
if (parentIndex !== -1) {
|
|
818
|
-
const rows = state.rows;
|
|
819
|
-
setBlurActiveRow(element, state);
|
|
820
|
-
// update activeCell for the row
|
|
821
|
-
state.activeCell = {
|
|
822
|
-
rowKeyValue: rows[parentIndex].key,
|
|
823
|
-
colKeyValue,
|
|
824
|
-
};
|
|
825
|
-
setFocusActiveRow(element, state);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
917
|
+
/**
|
|
918
|
+
* if the current new active still is valid (exists) then set the celltofocusnext to null
|
|
919
|
+
* @param {object} state - datatable state
|
|
920
|
+
*/
|
|
921
|
+
export function updateCellToFocusFromPrev(state) {
|
|
922
|
+
if (
|
|
923
|
+
state.activeCell &&
|
|
924
|
+
state.cellToFocusNext &&
|
|
925
|
+
stillValidActiveCell(state)
|
|
926
|
+
) {
|
|
927
|
+
// if the previous focus is there and valid, don't set the prevActiveFocusedCell
|
|
928
|
+
state.cellToFocusNext = null;
|
|
828
929
|
}
|
|
829
930
|
}
|
|
830
931
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
932
|
+
/**
|
|
933
|
+
* reset celltofocusnext to null (used after render)
|
|
934
|
+
* @param {object} state - datatable state
|
|
935
|
+
*/
|
|
936
|
+
export function resetCellToFocusFromPrev(state) {
|
|
937
|
+
state.cellToFocusNext = null;
|
|
938
|
+
}
|
|
835
939
|
|
|
836
|
-
|
|
837
|
-
|
|
940
|
+
/**
|
|
941
|
+
* It adds and the focus classes to the th/td or div[role=gridcell/rowheader].
|
|
942
|
+
*
|
|
943
|
+
* @param {node} element - the custom element template `this.template`
|
|
944
|
+
* @param {object} state - datatable state
|
|
945
|
+
*/
|
|
946
|
+
export function addFocusStylesToActiveCell(element, state) {
|
|
947
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
838
948
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
rowKeyValue: rows[nextRowIndex].key,
|
|
849
|
-
colKeyValue,
|
|
850
|
-
};
|
|
851
|
-
setFocusActiveRow(element, state);
|
|
852
|
-
}
|
|
949
|
+
const cellElement = getCellElementByIndexes(
|
|
950
|
+
element,
|
|
951
|
+
rowIndex,
|
|
952
|
+
colIndex,
|
|
953
|
+
state
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
if (cellElement) {
|
|
957
|
+
cellElement.parentElement.classList.add(FOCUS_CLASS);
|
|
853
958
|
}
|
|
854
959
|
}
|
|
855
960
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
961
|
+
/**
|
|
962
|
+
* It set the focus to the current activeCell, this operation imply multiple changes
|
|
963
|
+
* - update the tabindex of the activeCell
|
|
964
|
+
* - set the current keyboard mode
|
|
965
|
+
* - set the focus to the cell
|
|
966
|
+
* @param {node} element - the custom element root `this.template`
|
|
967
|
+
* @param {object} state - datatable state
|
|
968
|
+
*/
|
|
969
|
+
function setFocusActiveRow(element, state) {
|
|
970
|
+
const { rowIndex } = getIndexesActiveCell(state);
|
|
861
971
|
|
|
862
|
-
|
|
863
|
-
|
|
972
|
+
updateTabIndexRow(state, rowIndex);
|
|
973
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
974
|
+
setTimeout(() => {
|
|
975
|
+
const row = getRowElementByIndexes(element, rowIndex, state);
|
|
976
|
+
row.focus();
|
|
864
977
|
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
state.activeCell = {
|
|
874
|
-
rowKeyValue: rows[prevRowIndex].key,
|
|
875
|
-
colKeyValue,
|
|
876
|
-
};
|
|
877
|
-
setFocusActiveRow(element, state);
|
|
978
|
+
const scrollableY = element.querySelector('.slds-scrollable_y');
|
|
979
|
+
const scrollingParent = scrollableY.parentElement;
|
|
980
|
+
const parentRect = scrollingParent.getBoundingClientRect();
|
|
981
|
+
const findMeRect = row.getBoundingClientRect();
|
|
982
|
+
if (findMeRect.top < parentRect.top + TOP_MARGIN) {
|
|
983
|
+
scrollableY.scrollTop -= SCROLL_OFFSET;
|
|
984
|
+
} else if (findMeRect.bottom > parentRect.bottom - BOTTOM_MARGIN) {
|
|
985
|
+
scrollableY.scrollTop += SCROLL_OFFSET;
|
|
878
986
|
}
|
|
987
|
+
}, 0);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* It blurs the active row, this operation implies multiple changes
|
|
992
|
+
* - blur the active row
|
|
993
|
+
* - update the tabindex to -1
|
|
994
|
+
* @param {node} element - the custom element root `this.template`
|
|
995
|
+
* @param {object} state - datatable state
|
|
996
|
+
*/
|
|
997
|
+
function setBlurActiveRow(element, state) {
|
|
998
|
+
if (state.activeCell) {
|
|
999
|
+
const { rowIndex } = getIndexesActiveCell(state);
|
|
1000
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
1001
|
+
setTimeout(() => {
|
|
1002
|
+
const row = getRowElementByIndexes(element, rowIndex, state);
|
|
1003
|
+
if (document.activeElement === row) {
|
|
1004
|
+
row.blur();
|
|
1005
|
+
}
|
|
1006
|
+
}, 0);
|
|
1007
|
+
updateTabIndexRow(state, rowIndex, -1);
|
|
879
1008
|
}
|
|
880
1009
|
}
|
|
881
1010
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1011
|
+
/**
|
|
1012
|
+
* This method is needed in IE11 where clicking on the cell (factory) makes the div or the span active element
|
|
1013
|
+
* It refocuses on the cell element td or th or div[role=gridcell/rowheader]
|
|
1014
|
+
* @param {object} template - datatable element
|
|
1015
|
+
* @param {object} state - datatable state
|
|
1016
|
+
* @param {boolean} needsRefocusOnCellElement - flag indicating whether or not to refocus on the cell td/th or div[role=gridcell/rowheader]
|
|
1017
|
+
*/
|
|
1018
|
+
export function refocusCellElement(template, state, needsRefocusOnCellElement) {
|
|
1019
|
+
if (needsRefocusOnCellElement) {
|
|
1020
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1021
|
+
const cellElement = getCellElementByIndexes(
|
|
1022
|
+
template,
|
|
1023
|
+
rowIndex,
|
|
1024
|
+
colIndex,
|
|
1025
|
+
state
|
|
1026
|
+
);
|
|
1027
|
+
if (cellElement) {
|
|
1028
|
+
cellElement.parentElement.focus();
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// setTimeout so that focusin happens and then we set state.cellClicked to true
|
|
1032
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
1033
|
+
setTimeout(() => {
|
|
1034
|
+
setCellClickedForFocus(state);
|
|
1035
|
+
}, 0);
|
|
1036
|
+
} else if (!datatableHasFocus(state, template)) {
|
|
1037
|
+
setCellClickedForFocus(state);
|
|
1038
|
+
}
|
|
886
1039
|
}
|
|
887
1040
|
|
|
888
|
-
function
|
|
889
|
-
|
|
890
|
-
unsetRowNavigationMode(state);
|
|
891
|
-
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
1041
|
+
export function datatableHasFocus(state, template) {
|
|
1042
|
+
return isFocusInside(template) || state.cellClicked;
|
|
892
1043
|
}
|
|
893
1044
|
|
|
894
|
-
|
|
895
|
-
const
|
|
896
|
-
|
|
1045
|
+
function isFocusInside(currentTarget) {
|
|
1046
|
+
const activeElements = getShadowActiveElements();
|
|
1047
|
+
return activeElements.some((element) => {
|
|
1048
|
+
return currentTarget.contains(element);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
897
1051
|
|
|
898
|
-
|
|
1052
|
+
export function handleDatatableFocusIn(event) {
|
|
1053
|
+
const { state } = this;
|
|
1054
|
+
state.isExitingActionMode = false;
|
|
899
1055
|
|
|
900
|
-
//
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1056
|
+
// workaround for delegatesFocus issue that focusin is called when not supposed to W-6220418
|
|
1057
|
+
if (isFocusInside(event.currentTarget)) {
|
|
1058
|
+
if (!state.rowMode && state.activeCell) {
|
|
1059
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1060
|
+
const cellElement = getCellElementByIndexes(
|
|
1061
|
+
this.template,
|
|
1062
|
+
rowIndex,
|
|
1063
|
+
colIndex,
|
|
1064
|
+
state
|
|
1065
|
+
);
|
|
1066
|
+
// we need to check because of the tree,
|
|
1067
|
+
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
1068
|
+
if (cellElement) {
|
|
1069
|
+
cellElement.addFocusStyles();
|
|
1070
|
+
cellElement.parentElement.classList.add(FOCUS_CLASS);
|
|
1071
|
+
cellElement.tabindex = 0;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
resetCellClickedForFocus(state);
|
|
1075
|
+
}
|
|
908
1076
|
}
|
|
909
1077
|
|
|
910
|
-
export function
|
|
911
|
-
const {
|
|
912
|
-
|
|
1078
|
+
export function handleDatatableFocusOut(event) {
|
|
1079
|
+
const { state } = this;
|
|
1080
|
+
// workarounds for delegatesFocus issues
|
|
1081
|
+
if (
|
|
1082
|
+
// needed for initial focus where relatedTarget is empty
|
|
1083
|
+
!event.relatedTarget ||
|
|
1084
|
+
// needed when clicked outside
|
|
1085
|
+
(event.relatedTarget &&
|
|
1086
|
+
!event.currentTarget.contains(event.relatedTarget)) ||
|
|
1087
|
+
// needed when datatable leaves focus and related target is still within datatable W-6185154
|
|
1088
|
+
(event.relatedTarget &&
|
|
1089
|
+
event.currentTarget.contains(event.relatedTarget) &&
|
|
1090
|
+
state.isExitingActionMode)
|
|
1091
|
+
) {
|
|
1092
|
+
if (state.activeCell && !state.rowMode) {
|
|
1093
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1094
|
+
const cellElement = getCellElementByIndexes(
|
|
1095
|
+
this.template,
|
|
1096
|
+
rowIndex,
|
|
1097
|
+
colIndex,
|
|
1098
|
+
state
|
|
1099
|
+
);
|
|
1100
|
+
// we need to check because of the tree,
|
|
1101
|
+
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
1102
|
+
if (cellElement) {
|
|
1103
|
+
cellElement.removeFocusStyles();
|
|
1104
|
+
cellElement.parentElement.classList.remove(FOCUS_CLASS);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
913
1109
|
|
|
914
|
-
|
|
1110
|
+
/**
|
|
1111
|
+
* This is needed to check if datatable has lost focus but cell has been clicked recently
|
|
1112
|
+
* @param {object} state - datatable state
|
|
1113
|
+
*/
|
|
1114
|
+
export function setCellClickedForFocus(state) {
|
|
1115
|
+
state.cellClicked = true;
|
|
1116
|
+
}
|
|
915
1117
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
action: 'tab',
|
|
923
|
-
});
|
|
1118
|
+
/**
|
|
1119
|
+
* Once the dt regains focus there is no need to set this
|
|
1120
|
+
* @param {object} state - datatable state
|
|
1121
|
+
*/
|
|
1122
|
+
function resetCellClickedForFocus(state) {
|
|
1123
|
+
state.cellClicked = false;
|
|
924
1124
|
}
|
|
925
1125
|
|
|
926
|
-
|
|
927
|
-
const { rowKeyValue, colKeyValue } = event.detail;
|
|
928
|
-
const { rowIndex } = getIndexesByKeys(state, rowKeyValue, colKeyValue);
|
|
929
|
-
const nextRowIndex = getNextIndexDown(state, rowIndex);
|
|
930
|
-
const { rows } = state;
|
|
1126
|
+
/***************************** TABINDEX MANAGEMENT *****************************/
|
|
931
1127
|
|
|
932
|
-
|
|
933
|
-
|
|
1128
|
+
/**
|
|
1129
|
+
* It update the tabIndex value of a cell in the state for the rowIndex, colIndex passed
|
|
1130
|
+
* as consequence of this change
|
|
1131
|
+
* datatable is gonna re-render the cell affected with the new tabindex value
|
|
1132
|
+
*
|
|
1133
|
+
* @param {object} state - datatable state
|
|
1134
|
+
* @param {number} rowIndex - the row index
|
|
1135
|
+
* @param {number} colIndex - the column index
|
|
1136
|
+
* @param {number} [index = 0] - the value for the tabindex
|
|
1137
|
+
*/
|
|
1138
|
+
export function updateTabIndex(state, rowIndex, colIndex, index = 0) {
|
|
1139
|
+
if (isHeaderRow(rowIndex)) {
|
|
1140
|
+
const { columns } = state;
|
|
1141
|
+
columns[colIndex].tabIndex = index;
|
|
1142
|
+
} else {
|
|
1143
|
+
state.rows[rowIndex].cells[colIndex].tabIndex = index;
|
|
934
1144
|
}
|
|
1145
|
+
}
|
|
935
1146
|
|
|
936
|
-
|
|
937
|
-
|
|
1147
|
+
/**
|
|
1148
|
+
* It updates the tabIndex value of a row in the state for the rowIndex passed
|
|
1149
|
+
* as consequence of this change
|
|
1150
|
+
* datatable is gonna re-render the row affected with the new tabindex value
|
|
1151
|
+
*
|
|
1152
|
+
* @param {object} state - datatable state
|
|
1153
|
+
* @param {number} rowIndex - the row index
|
|
1154
|
+
* @param {number} [index = 0] - the value for the tabindex
|
|
1155
|
+
*/
|
|
1156
|
+
export function updateTabIndexRow(state, rowIndex, index = 0) {
|
|
1157
|
+
if (!isHeaderRow(rowIndex)) {
|
|
1158
|
+
// TODO what to do when rowIndex is header row
|
|
1159
|
+
state.rows[rowIndex].tabIndex = index;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* It update the tabindex for the current activeCell.
|
|
1164
|
+
* @param {object} state - datatable state
|
|
1165
|
+
* @param {number} [index = 0] - the value for the tabindex
|
|
1166
|
+
* @returns {object} state - mutated state
|
|
1167
|
+
*/
|
|
1168
|
+
export function updateTabIndexActiveCell(state, index = 0) {
|
|
1169
|
+
if (state.activeCell && !stillValidActiveCell(state)) {
|
|
1170
|
+
syncActiveCell(state);
|
|
938
1171
|
}
|
|
939
1172
|
|
|
940
|
-
|
|
941
|
-
|
|
1173
|
+
// we need to check again because maybe there is no active cell after sync
|
|
1174
|
+
if (state.activeCell && !isRowNavigationMode(state)) {
|
|
1175
|
+
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1176
|
+
updateTabIndex(state, rowIndex, colIndex, index);
|
|
942
1177
|
}
|
|
943
|
-
|
|
944
|
-
setBlurActiveCell(element, state);
|
|
945
|
-
// update activeCell
|
|
946
|
-
state.activeCell = {
|
|
947
|
-
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : 'HEADER',
|
|
948
|
-
colKeyValue,
|
|
949
|
-
};
|
|
950
|
-
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
1178
|
+
return state;
|
|
951
1179
|
}
|
|
952
1180
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1181
|
+
/**
|
|
1182
|
+
* It updates the tabindex for the row of the current activeCell.
|
|
1183
|
+
* This happens in rowMode of NAVIGATION_MODE
|
|
1184
|
+
* @param {object} state - datatable state
|
|
1185
|
+
* @param {number} [index = 0] - the value for the tabindex
|
|
1186
|
+
* @returns {object} state - mutated state
|
|
1187
|
+
*/
|
|
1188
|
+
export function updateTabIndexActiveRow(state, index = 0) {
|
|
1189
|
+
if (state.activeCell && !stillValidActiveCell(state)) {
|
|
1190
|
+
syncActiveCell(state);
|
|
961
1191
|
}
|
|
962
1192
|
|
|
963
|
-
|
|
964
|
-
|
|
1193
|
+
// we need to check again because maybe there is no active cell after sync
|
|
1194
|
+
if (state.activeCell && isRowNavigationMode(state)) {
|
|
1195
|
+
const { rowIndex } = getIndexesActiveCell(state);
|
|
1196
|
+
updateTabIndexRow(state, rowIndex, index);
|
|
965
1197
|
}
|
|
1198
|
+
return state;
|
|
1199
|
+
}
|
|
966
1200
|
|
|
967
|
-
|
|
968
|
-
|
|
1201
|
+
/***************************** INDEX COMPUTATIONS *****************************/
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* It return the indexes { rowIndex, colIndex } of a cell based of the unique cell values
|
|
1205
|
+
* rowKeyValue, colKeyValue
|
|
1206
|
+
* @param {object} state - datatable state
|
|
1207
|
+
* @param {string} rowKeyValue - the row key value
|
|
1208
|
+
* @param {string} colKeyValue - the column key value
|
|
1209
|
+
* @returns {object} - {rowIndex, colIndex}
|
|
1210
|
+
*/
|
|
1211
|
+
export function getIndexesByKeys(state, rowKeyValue, colKeyValue) {
|
|
1212
|
+
if (rowKeyValue === HEADER_ROW) {
|
|
1213
|
+
return {
|
|
1214
|
+
rowIndex: -1,
|
|
1215
|
+
colIndex: state.headerIndexes[colKeyValue],
|
|
1216
|
+
};
|
|
969
1217
|
}
|
|
970
1218
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
rowKeyValue: nextRowIndex !== -1 ? rows[nextRowIndex].key : 'HEADER',
|
|
975
|
-
colKeyValue,
|
|
1219
|
+
return {
|
|
1220
|
+
rowIndex: state.indexes[rowKeyValue][colKeyValue][0],
|
|
1221
|
+
colIndex: state.indexes[rowKeyValue][colKeyValue][1],
|
|
976
1222
|
};
|
|
977
|
-
setFocusActiveCell(element, state, NAVIGATION_DIR.USE_CURRENT);
|
|
978
1223
|
}
|
|
979
1224
|
|
|
980
1225
|
function getNextIndexUp(state, rowIndex) {
|
|
@@ -1019,215 +1264,113 @@ function getNextIndexDownWrapped(state, rowIndex) {
|
|
|
1019
1264
|
return rowIndex + 1 < rowsCount ? rowIndex + 1 : -1;
|
|
1020
1265
|
}
|
|
1021
1266
|
|
|
1022
|
-
|
|
1023
|
-
const columnsCount = state.columns.length;
|
|
1024
|
-
if (columnsCount > colIndex + 1) {
|
|
1025
|
-
return {
|
|
1026
|
-
nextRowIndex: rowIndex,
|
|
1027
|
-
nextColIndex: colIndex + 1,
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
return {
|
|
1031
|
-
nextRowIndex: getNextIndexDownWrapped(state, rowIndex),
|
|
1032
|
-
nextColIndex: 0,
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1267
|
+
/***************************** ROW NAVIGATION MODE *****************************/
|
|
1035
1268
|
|
|
1036
|
-
function
|
|
1037
|
-
|
|
1038
|
-
if (colIndex > 0) {
|
|
1039
|
-
return {
|
|
1040
|
-
nextRowIndex: rowIndex,
|
|
1041
|
-
nextColIndex: colIndex - 1,
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
return {
|
|
1045
|
-
nextRowIndex: getNextIndexUpWrapped(state, rowIndex),
|
|
1046
|
-
nextColIndex: columnsCount - 1,
|
|
1047
|
-
};
|
|
1269
|
+
function canBeRowNavigationMode(state) {
|
|
1270
|
+
return state.keyboardMode === NAVIGATION_MODE && hasTreeDataType(state);
|
|
1048
1271
|
}
|
|
1049
1272
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const rows = state.rows;
|
|
1053
|
-
for (let i = parentIndex; i >= 0; i--) {
|
|
1054
|
-
if (rows[i].level === rowLevel - 1) {
|
|
1055
|
-
return i;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
return -1;
|
|
1273
|
+
function isRowNavigationMode(state) {
|
|
1274
|
+
return state.keyboardMode === NAVIGATION_MODE && state.rowMode === true;
|
|
1059
1275
|
}
|
|
1060
1276
|
|
|
1061
|
-
function
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
} = state;
|
|
1065
|
-
if (rowKeyValue === 'HEADER') {
|
|
1066
|
-
return state.headerIndexes[colKeyValue] !== undefined;
|
|
1277
|
+
function setRowNavigationMode(state) {
|
|
1278
|
+
if (hasTreeDataType(state) && state.keyboardMode === NAVIGATION_MODE) {
|
|
1279
|
+
state.rowMode = true;
|
|
1067
1280
|
}
|
|
1068
|
-
return !!(
|
|
1069
|
-
state.indexes[rowKeyValue] && state.indexes[rowKeyValue][colKeyValue]
|
|
1070
|
-
);
|
|
1071
1281
|
}
|
|
1072
1282
|
|
|
1073
|
-
function
|
|
1074
|
-
state.
|
|
1283
|
+
export function unsetRowNavigationMode(state) {
|
|
1284
|
+
state.rowMode = false;
|
|
1075
1285
|
}
|
|
1076
1286
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
};
|
|
1287
|
+
/**
|
|
1288
|
+
* If new set of columns doesnt have tree data, mark it to false, as it
|
|
1289
|
+
* could be true earlier
|
|
1290
|
+
* Else if it has tree data, check if rowMode is false
|
|
1291
|
+
* Earlier it didnt have tree data, set rowMode to true to start
|
|
1292
|
+
* if rowMode is false and earlier it has tree data, keep it false
|
|
1293
|
+
* if rowMode is true and it has tree data, keep it true
|
|
1294
|
+
* @param {boolean} hadTreeDataTypePreviously - state object
|
|
1295
|
+
* @param {object} state - state object
|
|
1296
|
+
* @returns {object} state - mutated state
|
|
1297
|
+
*/
|
|
1298
|
+
export function updateRowNavigationMode(hadTreeDataTypePreviously, state) {
|
|
1299
|
+
if (!hasTreeDataType(state)) {
|
|
1300
|
+
state.rowMode = false;
|
|
1301
|
+
} else if (state.rowMode === false && !hadTreeDataTypePreviously) {
|
|
1302
|
+
state.rowMode = true;
|
|
1094
1303
|
}
|
|
1304
|
+
return state;
|
|
1305
|
+
}
|
|
1095
1306
|
|
|
1096
|
-
|
|
1307
|
+
/***************************** HELPER FUNCTIONS *****************************/
|
|
1308
|
+
|
|
1309
|
+
function isCellElement(tagName, role) {
|
|
1310
|
+
return (
|
|
1311
|
+
SELECTORS.cell.default.includes(tagName) ||
|
|
1312
|
+
SELECTORS.cell.roleBased.includes(role)
|
|
1313
|
+
);
|
|
1097
1314
|
}
|
|
1098
1315
|
|
|
1099
|
-
function
|
|
1100
|
-
|
|
1101
|
-
if (columns.length > 0) {
|
|
1102
|
-
return {
|
|
1103
|
-
rowKeyValue: rowIndex === -1 ? 'HEADER' : rows[rowIndex].key,
|
|
1104
|
-
colKeyValue: generateColKeyValue(columns[colIndex], colIndex),
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
return undefined;
|
|
1316
|
+
function isHeaderRow(rowIndex) {
|
|
1317
|
+
return rowIndex === -1;
|
|
1108
1318
|
}
|
|
1109
1319
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1320
|
+
function getHeaderRow(isRenderModeRoleBased) {
|
|
1321
|
+
const selectors = SELECTORS.headerRow;
|
|
1322
|
+
return isRenderModeRoleBased ? selectors.roleBased : selectors.default;
|
|
1113
1323
|
}
|
|
1114
1324
|
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1325
|
+
function getDataRow(rowIndex, isRenderModeRoleBased) {
|
|
1326
|
+
const dataRowRowGroupSelector = isRenderModeRoleBased
|
|
1327
|
+
? SELECTORS.dataRowRowGroup.roleBased
|
|
1328
|
+
: SELECTORS.dataRowRowGroup.default;
|
|
1329
|
+
return `${dataRowRowGroupSelector} > :nth-child(${rowIndex + 1})`;
|
|
1121
1330
|
}
|
|
1122
1331
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
*/
|
|
1127
|
-
export const setCellClickedForFocus = function (state) {
|
|
1128
|
-
state.cellClicked = true;
|
|
1129
|
-
};
|
|
1332
|
+
export function getCellElementByIndexes(element, rowIndex, colIndex, state) {
|
|
1333
|
+
const isRenderModeRoleBased = state.renderModeRoleBased;
|
|
1334
|
+
let selector = '';
|
|
1130
1335
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
state.cellClicked = false;
|
|
1137
|
-
};
|
|
1336
|
+
if (isHeaderRow(rowIndex)) {
|
|
1337
|
+
selector = `${getHeaderRow(isRenderModeRoleBased)}
|
|
1338
|
+
> :nth-child(${colIndex + 1}) > :first-child`;
|
|
1339
|
+
return element.querySelector(selector);
|
|
1340
|
+
}
|
|
1138
1341
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
* @param {object} state - datatable state
|
|
1144
|
-
* @param {boolean} needsRefocusOnCellElement - flag indicating whether or not to refocus on the cell td/th
|
|
1145
|
-
*/
|
|
1146
|
-
export const refocusCellElement = function (
|
|
1147
|
-
template,
|
|
1148
|
-
state,
|
|
1149
|
-
needsRefocusOnCellElement
|
|
1150
|
-
) {
|
|
1151
|
-
if (needsRefocusOnCellElement) {
|
|
1152
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1153
|
-
const cellElement = getCellElementByIndexes(
|
|
1154
|
-
template,
|
|
1155
|
-
rowIndex,
|
|
1156
|
-
colIndex
|
|
1157
|
-
);
|
|
1158
|
-
if (cellElement) {
|
|
1159
|
-
cellElement.parentElement.focus();
|
|
1160
|
-
}
|
|
1342
|
+
selector = `${getDataRow(rowIndex, isRenderModeRoleBased)}
|
|
1343
|
+
> :nth-child(${colIndex + 1}) > :first-child`;
|
|
1344
|
+
return element.querySelector(selector);
|
|
1345
|
+
}
|
|
1161
1346
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}, 0);
|
|
1167
|
-
} else if (!datatableHasFocus(state, template)) {
|
|
1168
|
-
setCellClickedForFocus(state);
|
|
1347
|
+
function getRowElementByIndexes(element, rowIndex, state) {
|
|
1348
|
+
const isRenderModeRoleBased = state.renderModeRoleBased;
|
|
1349
|
+
if (isHeaderRow(rowIndex)) {
|
|
1350
|
+
return element.querySelector(getHeaderRow(isRenderModeRoleBased));
|
|
1169
1351
|
}
|
|
1170
|
-
};
|
|
1171
1352
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
(
|
|
1180
|
-
|
|
1181
|
-
// needed when datatable leaves focus and related target is still within datatable W-6185154
|
|
1182
|
-
(event.relatedTarget &&
|
|
1183
|
-
event.currentTarget.contains(event.relatedTarget) &&
|
|
1184
|
-
state.isExiting)
|
|
1185
|
-
) {
|
|
1186
|
-
if (state.activeCell && !state.rowMode) {
|
|
1187
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1188
|
-
const cellElement = getCellElementByIndexes(
|
|
1189
|
-
this.template,
|
|
1190
|
-
rowIndex,
|
|
1191
|
-
colIndex
|
|
1192
|
-
);
|
|
1193
|
-
// we need to check because of the tree,
|
|
1194
|
-
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
1195
|
-
if (cellElement) {
|
|
1196
|
-
cellElement.removeFocusStyles();
|
|
1197
|
-
cellElement.parentElement.classList.remove('slds-has-focus');
|
|
1198
|
-
}
|
|
1353
|
+
return element.querySelector(getDataRow(rowIndex, isRenderModeRoleBased));
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
export function getRowParent(state, rowLevel, rowIndex) {
|
|
1357
|
+
const parentIndex = rowIndex - 1;
|
|
1358
|
+
const rows = state.rows;
|
|
1359
|
+
for (let i = parentIndex; i >= 0; i--) {
|
|
1360
|
+
if (rows[i].level === rowLevel - 1) {
|
|
1361
|
+
return i;
|
|
1199
1362
|
}
|
|
1200
1363
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
function isFocusInside(currentTarget) {
|
|
1204
|
-
const activeElements = getShadowActiveElements();
|
|
1205
|
-
return activeElements.some((element) => {
|
|
1206
|
-
return currentTarget.contains(element);
|
|
1207
|
-
});
|
|
1364
|
+
return -1;
|
|
1208
1365
|
}
|
|
1209
1366
|
|
|
1210
|
-
|
|
1211
|
-
const {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
const { rowIndex, colIndex } = getIndexesActiveCell(state);
|
|
1218
|
-
const cellElement = getCellElementByIndexes(
|
|
1219
|
-
this.template,
|
|
1220
|
-
rowIndex,
|
|
1221
|
-
colIndex
|
|
1222
|
-
);
|
|
1223
|
-
// we need to check because of the tree,
|
|
1224
|
-
// at this point it may remove/change the rows/keys because opening or closing a row.
|
|
1225
|
-
if (cellElement) {
|
|
1226
|
-
cellElement.addFocusStyles();
|
|
1227
|
-
cellElement.parentElement.classList.add('slds-has-focus');
|
|
1228
|
-
cellElement.tabindex = 0;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
resetCellClickedForFocus(state);
|
|
1367
|
+
function getCellFromIndexes(state, rowIndex, colIndex) {
|
|
1368
|
+
const { columns, rows } = state;
|
|
1369
|
+
if (columns.length > 0) {
|
|
1370
|
+
return {
|
|
1371
|
+
rowKeyValue: rowIndex === -1 ? HEADER_ROW : rows[rowIndex].key,
|
|
1372
|
+
colKeyValue: generateColKeyValue(columns[colIndex], colIndex),
|
|
1373
|
+
};
|
|
1232
1374
|
}
|
|
1233
|
-
|
|
1375
|
+
return undefined;
|
|
1376
|
+
}
|