@sap-ux/ui-components 1.8.2 → 1.8.4
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/dist/components/Icons.d.ts +140 -139
- package/dist/components/Icons.d.ts.map +1 -1
- package/dist/components/Icons.js +587 -582
- package/dist/components/Icons.js.map +1 -1
- package/dist/components/UIActionCallout/UIActionCallout.d.ts +61 -61
- package/dist/components/UIActionCallout/UIActionCallout.js +63 -63
- package/dist/components/UIActionCallout/UIActionCallout.scss +31 -31
- package/dist/components/UIActionCallout/index.d.ts +1 -1
- package/dist/components/UIActionCallout/index.js +17 -17
- package/dist/components/UIBreadcrumb/UIBreadcrumb.d.ts +23 -23
- package/dist/components/UIBreadcrumb/UIBreadcrumb.js +38 -38
- package/dist/components/UIBreadcrumb/index.d.ts +1 -1
- package/dist/components/UIBreadcrumb/index.js +17 -17
- package/dist/components/UIButton/UIActionButton.d.ts +23 -23
- package/dist/components/UIButton/UIActionButton.js +89 -89
- package/dist/components/UIButton/UIDefaultButton.d.ts +23 -23
- package/dist/components/UIButton/UIDefaultButton.js +229 -229
- package/dist/components/UIButton/UIIconButton.d.ts +37 -37
- package/dist/components/UIButton/UIIconButton.js +106 -106
- package/dist/components/UIButton/UISmallButton.d.ts +34 -34
- package/dist/components/UIButton/UISmallButton.js +115 -115
- package/dist/components/UIButton/UISplitButton.d.ts +36 -36
- package/dist/components/UIButton/UISplitButton.js +71 -71
- package/dist/components/UIButton/index.d.ts +7 -7
- package/dist/components/UIButton/index.js +21 -21
- package/dist/components/UICallout/UICallout.d.ts +37 -37
- package/dist/components/UICallout/UICallout.js +90 -90
- package/dist/components/UICallout/index.d.ts +1 -1
- package/dist/components/UICallout/index.js +17 -17
- package/dist/components/UICheckbox/UICheckbox.d.ts +34 -34
- package/dist/components/UICheckbox/UICheckbox.js +135 -135
- package/dist/components/UICheckbox/index.d.ts +1 -1
- package/dist/components/UICheckbox/index.js +17 -17
- package/dist/components/UIChoiceGroup/UIChoiceGroup.d.ts +28 -28
- package/dist/components/UIChoiceGroup/UIChoiceGroup.js +182 -182
- package/dist/components/UIChoiceGroup/index.d.ts +1 -1
- package/dist/components/UIChoiceGroup/index.js +17 -17
- package/dist/components/UIComboBox/Callout.scss +7 -7
- package/dist/components/UIComboBox/UIComboBox.d.ts +198 -198
- package/dist/components/UIComboBox/UIComboBox.js +516 -516
- package/dist/components/UIComboBox/UIComboBox.scss +268 -268
- package/dist/components/UIComboBox/_mixins.scss +32 -32
- package/dist/components/UIComboBox/index.d.ts +1 -1
- package/dist/components/UIComboBox/index.js +17 -17
- package/dist/components/UIContextualMenu/UIContextualMenu.d.ts +31 -31
- package/dist/components/UIContextualMenu/UIContextualMenu.js +123 -123
- package/dist/components/UIContextualMenu/UIContextualMenu.scss +211 -211
- package/dist/components/UIContextualMenu/UIHighlightMenuOption.d.ts +19 -19
- package/dist/components/UIContextualMenu/UIHighlightMenuOption.js +50 -50
- package/dist/components/UIContextualMenu/UIHighlightMenuOption.scss +23 -23
- package/dist/components/UIContextualMenu/_variables.scss +30 -30
- package/dist/components/UIContextualMenu/index.d.ts +2 -2
- package/dist/components/UIContextualMenu/index.js +18 -18
- package/dist/components/UIDatePicker/UIDatePicker.d.ts +49 -49
- package/dist/components/UIDatePicker/UIDatePicker.js +72 -72
- package/dist/components/UIDatePicker/UIDatePicker.scss +38 -38
- package/dist/components/UIDatePicker/index.d.ts +1 -1
- package/dist/components/UIDatePicker/index.js +17 -17
- package/dist/components/UIDialog/UIDialog.d.ts +116 -116
- package/dist/components/UIDialog/UIDialog.js +279 -279
- package/dist/components/UIDialog/index.d.ts +1 -1
- package/dist/components/UIDialog/index.js +17 -17
- package/dist/components/UIDropdown/UIDropdown.d.ts +90 -90
- package/dist/components/UIDropdown/UIDropdown.js +216 -216
- package/dist/components/UIDropdown/UIDropdown.scss +115 -115
- package/dist/components/UIDropdown/index.d.ts +2 -2
- package/dist/components/UIDropdown/index.js +18 -18
- package/dist/components/UIDropdown/utils.d.ts +8 -8
- package/dist/components/UIDropdown/utils.js +20 -20
- package/dist/components/UIFlexibleTable/RowActions.d.ts +16 -16
- package/dist/components/UIFlexibleTable/RowActions.js +73 -73
- package/dist/components/UIFlexibleTable/RowData.d.ts +16 -16
- package/dist/components/UIFlexibleTable/RowData.js +111 -111
- package/dist/components/UIFlexibleTable/UIFlexibleTable.d.ts +11 -11
- package/dist/components/UIFlexibleTable/UIFlexibleTable.js +280 -280
- package/dist/components/UIFlexibleTable/UIFlexibleTable.scss +433 -433
- package/dist/components/UIFlexibleTable/UIFlexibleTableActionButton.d.ts +20 -20
- package/dist/components/UIFlexibleTable/UIFlexibleTableActionButton.js +20 -20
- package/dist/components/UIFlexibleTable/UIFlexibleTableRow.d.ts +26 -26
- package/dist/components/UIFlexibleTable/UIFlexibleTableRow.js +165 -165
- package/dist/components/UIFlexibleTable/UIFlexibleTableRowActionButton.d.ts +20 -20
- package/dist/components/UIFlexibleTable/UIFlexibleTableRowActionButton.js +19 -19
- package/dist/components/UIFlexibleTable/UIFlexibleTableRowNoData.d.ts +15 -15
- package/dist/components/UIFlexibleTable/UIFlexibleTableRowNoData.js +28 -28
- package/dist/components/UIFlexibleTable/index.d.ts +5 -5
- package/dist/components/UIFlexibleTable/index.js +21 -21
- package/dist/components/UIFlexibleTable/types.d.ts +142 -142
- package/dist/components/UIFlexibleTable/types.js +14 -14
- package/dist/components/UIFlexibleTable/utils.d.ts +25 -25
- package/dist/components/UIFlexibleTable/utils.js +49 -49
- package/dist/components/UIFocusZone/UIFocusTrapZone.d.ts +22 -22
- package/dist/components/UIFocusZone/UIFocusTrapZone.js +33 -33
- package/dist/components/UIFocusZone/UIFocusZone.d.ts +23 -23
- package/dist/components/UIFocusZone/UIFocusZone.js +35 -35
- package/dist/components/UIFocusZone/index.d.ts +2 -2
- package/dist/components/UIFocusZone/index.js +18 -18
- package/dist/components/UIIcon/UIIcon.d.ts +24 -24
- package/dist/components/UIIcon/UIIcon.js +37 -37
- package/dist/components/UIIcon/UIIcon.scss +3 -3
- package/dist/components/UIIcon/index.d.ts +1 -1
- package/dist/components/UIIcon/index.js +17 -17
- package/dist/components/UIInput/UITextInput.d.ts +48 -48
- package/dist/components/UIInput/UITextInput.js +238 -238
- package/dist/components/UIInput/index.d.ts +1 -1
- package/dist/components/UIInput/index.js +17 -17
- package/dist/components/UILabel/UILabel.d.ts +30 -30
- package/dist/components/UILabel/UILabel.js +64 -64
- package/dist/components/UILabel/index.d.ts +1 -1
- package/dist/components/UILabel/index.js +17 -17
- package/dist/components/UILink/UILink.d.ts +25 -25
- package/dist/components/UILink/UILink.js +71 -71
- package/dist/components/UILink/index.d.ts +1 -1
- package/dist/components/UILink/index.js +17 -17
- package/dist/components/UIList/UIList.d.ts +31 -31
- package/dist/components/UIList/UIList.js +120 -120
- package/dist/components/UIList/UIList.scss +16 -16
- package/dist/components/UIList/index.d.ts +1 -1
- package/dist/components/UIList/index.js +17 -17
- package/dist/components/UILoader/UILoader.d.ts +27 -27
- package/dist/components/UILoader/UILoader.js +78 -78
- package/dist/components/UILoader/UILoader.scss +32 -32
- package/dist/components/UILoader/index.d.ts +1 -1
- package/dist/components/UILoader/index.js +17 -17
- package/dist/components/UIMessageBar/UIMessageBar.d.ts +25 -25
- package/dist/components/UIMessageBar/UIMessageBar.js +56 -56
- package/dist/components/UIMessageBar/index.d.ts +1 -1
- package/dist/components/UIMessageBar/index.js +17 -17
- package/dist/components/UIModal/UIModal.d.ts +23 -23
- package/dist/components/UIModal/UIModal.js +43 -43
- package/dist/components/UIModal/index.d.ts +1 -1
- package/dist/components/UIModal/index.js +17 -17
- package/dist/components/UIOverlay/UIOverlay.d.ts +22 -22
- package/dist/components/UIOverlay/UIOverlay.js +38 -38
- package/dist/components/UIOverlay/index.d.ts +1 -1
- package/dist/components/UIOverlay/index.js +17 -17
- package/dist/components/UIPersona/UIPersona.d.ts +27 -27
- package/dist/components/UIPersona/UIPersona.js +48 -48
- package/dist/components/UIPersona/index.d.ts +1 -1
- package/dist/components/UIPersona/index.js +17 -17
- package/dist/components/UISearchBox/UISearchBox.d.ts +22 -22
- package/dist/components/UISearchBox/UISearchBox.js +153 -153
- package/dist/components/UISearchBox/index.d.ts +3 -3
- package/dist/components/UISearchBox/index.js +17 -17
- package/dist/components/UISection/UISection.d.ts +34 -34
- package/dist/components/UISection/UISection.js +44 -44
- package/dist/components/UISection/UISection.scss +76 -76
- package/dist/components/UISection/UISections.d.ts +249 -249
- package/dist/components/UISection/UISections.js +707 -707
- package/dist/components/UISection/UISections.scss +62 -62
- package/dist/components/UISection/UISplitter.d.ts +96 -96
- package/dist/components/UISection/UISplitter.js +220 -220
- package/dist/components/UISection/UISplitter.scss +212 -212
- package/dist/components/UISection/_mixins.scss +14 -14
- package/dist/components/UISection/_variables.scss +1 -1
- package/dist/components/UISection/index.d.ts +3 -3
- package/dist/components/UISection/index.js +19 -19
- package/dist/components/UISeparator/UISeparator.d.ts +25 -25
- package/dist/components/UISeparator/UISeparator.js +65 -65
- package/dist/components/UISeparator/index.d.ts +1 -1
- package/dist/components/UISeparator/index.js +17 -17
- package/dist/components/UITable/UITable-helper.d.ts +79 -79
- package/dist/components/UITable/UITable-helper.js +259 -259
- package/dist/components/UITable/UITable.d.ts +212 -212
- package/dist/components/UITable/UITable.js +775 -775
- package/dist/components/UITable/UITable.scss +194 -194
- package/dist/components/UITable/index.d.ts +2 -2
- package/dist/components/UITable/index.js +18 -18
- package/dist/components/UITable/types.d.ts +77 -77
- package/dist/components/UITable/types.js +28 -28
- package/dist/components/UITabs/UITabs.d.ts +28 -28
- package/dist/components/UITabs/UITabs.js +70 -70
- package/dist/components/UITabs/index.d.ts +1 -1
- package/dist/components/UITabs/index.js +17 -17
- package/dist/components/UIToggle/UIToggle.d.ts +39 -39
- package/dist/components/UIToggle/UIToggle.js +181 -181
- package/dist/components/UIToggle/index.d.ts +1 -1
- package/dist/components/UIToggle/index.js +17 -17
- package/dist/components/UIToggleGroup/UIToggleGroup.d.ts +31 -31
- package/dist/components/UIToggleGroup/UIToggleGroup.js +136 -136
- package/dist/components/UIToggleGroup/UIToggleGroup.scss +13 -13
- package/dist/components/UIToggleGroup/UIToggleGroup.types.d.ts +22 -22
- package/dist/components/UIToggleGroup/UIToggleGroup.types.js +2 -2
- package/dist/components/UIToggleGroup/UIToggleGroupOption/UIToggleGroupOption.d.ts +25 -25
- package/dist/components/UIToggleGroup/UIToggleGroupOption/UIToggleGroupOption.js +77 -77
- package/dist/components/UIToggleGroup/UIToggleGroupOption/UIToggleGroupOption.scss +74 -74
- package/dist/components/UIToggleGroup/UIToggleGroupOption/UIToggleGroupOption.types.d.ts +10 -10
- package/dist/components/UIToggleGroup/UIToggleGroupOption/UIToggleGroupOption.types.js +2 -2
- package/dist/components/UIToggleGroup/UIToggleGroupOption/index.d.ts +2 -2
- package/dist/components/UIToggleGroup/UIToggleGroupOption/index.js +18 -18
- package/dist/components/UIToggleGroup/index.d.ts +2 -2
- package/dist/components/UIToggleGroup/index.js +18 -18
- package/dist/components/UIToolbar/UIToolbar.d.ts +36 -36
- package/dist/components/UIToolbar/UIToolbar.js +104 -104
- package/dist/components/UIToolbar/UIToolbar.scss +48 -48
- package/dist/components/UIToolbar/UIToolbarColumn.d.ts +7 -7
- package/dist/components/UIToolbar/UIToolbarColumn.js +33 -33
- package/dist/components/UIToolbar/UIToolbarDivider.d.ts +27 -27
- package/dist/components/UIToolbar/UIToolbarDivider.js +56 -56
- package/dist/components/UIToolbar/index.d.ts +3 -3
- package/dist/components/UIToolbar/index.js +19 -19
- package/dist/components/UITooltip/UITooltip.d.ts +29 -29
- package/dist/components/UITooltip/UITooltip.js +77 -77
- package/dist/components/UITooltip/UITooltipUtils.d.ts +17 -17
- package/dist/components/UITooltip/UITooltipUtils.js +45 -45
- package/dist/components/UITooltip/index.d.ts +2 -2
- package/dist/components/UITooltip/index.js +18 -18
- package/dist/components/UITranslationInput/UIFormattedText.d.ts +46 -46
- package/dist/components/UITranslationInput/UIFormattedText.js +121 -121
- package/dist/components/UITranslationInput/UILoadButton.d.ts +52 -52
- package/dist/components/UITranslationInput/UILoadButton.js +109 -109
- package/dist/components/UITranslationInput/UILoadButton.scss +14 -14
- package/dist/components/UITranslationInput/UITranslationButton.d.ts +14 -14
- package/dist/components/UITranslationInput/UITranslationButton.js +88 -88
- package/dist/components/UITranslationInput/UITranslationButton.types.d.ts +67 -67
- package/dist/components/UITranslationInput/UITranslationButton.types.js +21 -21
- package/dist/components/UITranslationInput/UITranslationInput.d.ts +25 -25
- package/dist/components/UITranslationInput/UITranslationInput.js +169 -169
- package/dist/components/UITranslationInput/UITranslationInput.scss +40 -40
- package/dist/components/UITranslationInput/UITranslationUtils.d.ts +64 -64
- package/dist/components/UITranslationInput/UITranslationUtils.js +168 -168
- package/dist/components/UITranslationInput/defaults.d.ts +2 -2
- package/dist/components/UITranslationInput/defaults.js +15 -15
- package/dist/components/UITranslationInput/index.d.ts +2 -2
- package/dist/components/UITranslationInput/index.js +18 -18
- package/dist/components/UITreeDropdown/UITreeDropdown.d.ts +265 -265
- package/dist/components/UITreeDropdown/UITreeDropdown.js +661 -661
- package/dist/components/UITreeDropdown/UITreeDropdown.scss +64 -64
- package/dist/components/UITreeDropdown/index.d.ts +1 -1
- package/dist/components/UITreeDropdown/index.js +17 -17
- package/dist/components/UIVerticalDivider/UIVerticalDivider.d.ts +23 -23
- package/dist/components/UIVerticalDivider/UIVerticalDivider.js +41 -41
- package/dist/components/UIVerticalDivider/index.d.ts +1 -1
- package/dist/components/UIVerticalDivider/index.js +17 -17
- package/dist/components/UIVirtualList/UIAutoSizer.d.ts +22 -22
- package/dist/components/UIVirtualList/UIAutoSizer.js +33 -33
- package/dist/components/UIVirtualList/UICellMeasurer.d.ts +23 -23
- package/dist/components/UIVirtualList/UICellMeasurer.js +33 -33
- package/dist/components/UIVirtualList/UIVirtualList.d.ts +43 -43
- package/dist/components/UIVirtualList/UIVirtualList.js +70 -70
- package/dist/components/UIVirtualList/index.d.ts +3 -3
- package/dist/components/UIVirtualList/index.js +19 -19
- package/dist/components/index.d.ts +37 -37
- package/dist/components/index.js +53 -53
- package/dist/helper/ValidationMessage/MessageWrapper.d.ts +17 -17
- package/dist/helper/ValidationMessage/MessageWrapper.js +34 -34
- package/dist/helper/ValidationMessage/MessageWrapper.scss +44 -44
- package/dist/helper/ValidationMessage/index.d.ts +2 -2
- package/dist/helper/ValidationMessage/index.js +18 -18
- package/dist/helper/ValidationMessage/utils.d.ts +31 -31
- package/dist/helper/ValidationMessage/utils.js +121 -121
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -18
- package/dist/styles/_mixins.scss +15 -15
- package/dist/styles/_typography.scss +72 -72
- package/dist/styles/_variables.scss +26 -26
- package/dist/styles/ui-components.scss +3 -3
- package/dist/utilities/DeepMerge.d.ts +10 -10
- package/dist/utilities/DeepMerge.js +48 -48
- package/dist/utilities/Focus.d.ts +4 -4
- package/dist/utilities/Focus.js +7 -7
- package/dist/utilities/Id.d.ts +2 -2
- package/dist/utilities/Id.js +5 -5
- package/dist/utilities/Keys.d.ts +2 -2
- package/dist/utilities/Keys.js +5 -5
- package/dist/utilities/index.d.ts +3 -3
- package/dist/utilities/index.js +19 -19
- package/package.json +11 -12
- package/storybook/183.d7ada8f4159c95c30527.manager.bundle.js +891 -0
- package/storybook/260.1c2260a4.iframe.bundle.js +292 -0
- package/storybook/275.02c0af0f1d3b6c70e72d.manager.bundle.js +10 -0
- package/storybook/453.6e0cb2b5c2ede7af85c5.manager.bundle.js +2 -0
- package/storybook/{458.a8cb7c68775e3858b342.manager.bundle.js → 458.05b97cebc6fa3809e87a.manager.bundle.js} +113 -113
- package/storybook/{594.4816ff3f4edb5b8b0949.manager.bundle.js → 594.4d8bf523158b8a4a1ca9.manager.bundle.js} +1 -1
- package/storybook/{637.173c8d06.iframe.bundle.js → 637.149b545a.iframe.bundle.js} +1 -1
- package/storybook/{966.bc8dd3d7eec539544b33.manager.bundle.js → 966.7154988e86ea89b1942c.manager.bundle.js} +3 -3
- package/storybook/{987.81102a5d201c9f5a1528.manager.bundle.js → 987.7aa55d838ce2874baae8.manager.bundle.js} +3 -3
- package/storybook/iframe.html +17 -17
- package/storybook/index.html +119 -119
- package/storybook/{main.73db1398c1704e93ba73.manager.bundle.js → main.bbce46728b43f51e7d37.manager.bundle.js} +1 -1
- package/storybook/main.fdca8543.iframe.bundle.js +99 -0
- package/storybook/project.json +1 -1
- package/storybook/{runtime~main.769376b3.iframe.bundle.js → runtime~main.a1df0937.iframe.bundle.js} +1 -1
- package/storybook/runtime~main.e938cbae9eda438313f6.manager.bundle.js +2 -0
- package/storybook/275.a8ff63b0ed4a5d620142.manager.bundle.js +0 -10
- package/storybook/453.af4e8cc44257a27c435f.manager.bundle.js +0 -2
- package/storybook/713.ec72f301.iframe.bundle.js +0 -292
- package/storybook/756.edf22bba3c06d7815c54.manager.bundle.js +0 -891
- package/storybook/main.4bf0e1ff.iframe.bundle.js +0 -99
- package/storybook/runtime~main.eabd5638d9f3e40b3501.manager.bundle.js +0 -2
|
@@ -1,662 +1,662 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.UITreeDropdown = exports.EdgePosition = exports.UIDirectionalHint = void 0;
|
|
7
|
-
const react_1 = __importDefault(require("react"));
|
|
8
|
-
const uuid_1 = __importDefault(require("uuid"));
|
|
9
|
-
const react_2 = require("@fluentui/react");
|
|
10
|
-
Object.defineProperty(exports, "UIDirectionalHint", { enumerable: true, get: function () { return react_2.DirectionalHint; } });
|
|
11
|
-
const UIInput_1 = require("../UIInput");
|
|
12
|
-
const UIContextualMenu_1 = require("../UIContextualMenu");
|
|
13
|
-
const UIIconButton_1 = require("../UIButton/UIIconButton");
|
|
14
|
-
const Icons_1 = require("../Icons");
|
|
15
|
-
const ValidationMessage_1 = require("../../helper/ValidationMessage");
|
|
16
|
-
require("./UITreeDropdown.scss");
|
|
17
|
-
const SELECTOR_CLASSES = {
|
|
18
|
-
callout: 'ui-tree-callout',
|
|
19
|
-
scrollArea: 'ms-ContextualMenu-container',
|
|
20
|
-
splitButton: 'ms-ContextualMenu-splitMenu'
|
|
21
|
-
};
|
|
22
|
-
var EdgePosition;
|
|
23
|
-
(function (EdgePosition) {
|
|
24
|
-
EdgePosition["First"] = "First";
|
|
25
|
-
EdgePosition["Last"] = "Last";
|
|
26
|
-
})(EdgePosition = exports.EdgePosition || (exports.EdgePosition = {}));
|
|
27
|
-
const KEYBOARD_KEYS = {
|
|
28
|
-
ArrowUp: 'ArrowUp',
|
|
29
|
-
ArrowDown: 'ArrowDown',
|
|
30
|
-
Enter: 'Enter',
|
|
31
|
-
Escape: 'Escape'
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* UITreeDropdown component.
|
|
35
|
-
*
|
|
36
|
-
* @exports
|
|
37
|
-
* @class UIVerticalDivider
|
|
38
|
-
* @extends {React.Component<UITreeDropdownProps, UITreeDropdownState>}
|
|
39
|
-
*/
|
|
40
|
-
class UITreeDropdown extends react_1.default.Component {
|
|
41
|
-
/**
|
|
42
|
-
* Initializes component properties.
|
|
43
|
-
*
|
|
44
|
-
* @param {UITreeDropdownProps} props
|
|
45
|
-
*/
|
|
46
|
-
constructor(props) {
|
|
47
|
-
super(props);
|
|
48
|
-
this.UITreeDropdownRef = react_1.default.createRef();
|
|
49
|
-
this.UITreeDropdownFocusZoneRef = react_1.default.createRef();
|
|
50
|
-
this.inputRef = react_1.default.createRef();
|
|
51
|
-
this.submenuRefs = {};
|
|
52
|
-
// Calculated offset for submenu positions
|
|
53
|
-
// It is added because root menu can have scrollbar - in result root menu's items width is smaller than root menu.
|
|
54
|
-
// In such case(when scrollbar) submenu is positioned near to expand/hovered item and position is not on the edge of root menu.
|
|
55
|
-
// Using offset/margin we can do corrections to position of submenu and place it one the edge of root menu.
|
|
56
|
-
this.submenuOffset = 0;
|
|
57
|
-
this.lastKeyDown = '';
|
|
58
|
-
this.componentDidMount = () => {
|
|
59
|
-
if (this.props.items.length) {
|
|
60
|
-
this.buildItems(this.props.items);
|
|
61
|
-
this.setState({ isDisabled: false });
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
this.componentDidUpdate = (prevProps) => {
|
|
65
|
-
if (this.props.items.length !== prevProps.items.length) {
|
|
66
|
-
this.setState({ isHidden: true });
|
|
67
|
-
this.setState({ isDisabled: this.props.items.length ? false : true });
|
|
68
|
-
this.buildItems(this.props.items);
|
|
69
|
-
}
|
|
70
|
-
if (this.props.value !== prevProps.value) {
|
|
71
|
-
this.setState({ value: this.props.value });
|
|
72
|
-
}
|
|
73
|
-
// Calculate size for submenu offset
|
|
74
|
-
this.calculateSubmenuOffset();
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Map the payload.
|
|
78
|
-
*
|
|
79
|
-
* @param {ItemsProps[]} items
|
|
80
|
-
*/
|
|
81
|
-
this.buildItems = (items) => {
|
|
82
|
-
if (this.state.items.length !== items.length) {
|
|
83
|
-
items = items.map(this.buildSubItems);
|
|
84
|
-
const mapedItems = this.mapValuesToContextMenu(items);
|
|
85
|
-
this.setState({
|
|
86
|
-
originalItems: mapedItems,
|
|
87
|
-
items: mapedItems
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
/**
|
|
92
|
-
* Sub items values and style.
|
|
93
|
-
*
|
|
94
|
-
* @param {ItemsProps} item
|
|
95
|
-
* @returns {ItemsProps}
|
|
96
|
-
*/
|
|
97
|
-
this.buildSubItems = (item) => {
|
|
98
|
-
if (item.children && item.children.length) {
|
|
99
|
-
item.children = item.children.map((el) => {
|
|
100
|
-
const regex = new RegExp(item.value, 'ig');
|
|
101
|
-
const value = el.value.search(regex) === -1 ? `${item.value}${this.state.valueSeparator}${el.value}` : el.value;
|
|
102
|
-
return {
|
|
103
|
-
...el,
|
|
104
|
-
split: false,
|
|
105
|
-
value
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
return item;
|
|
110
|
-
};
|
|
111
|
-
/**
|
|
112
|
-
* Map GD payload to ContextMenu payload.
|
|
113
|
-
*
|
|
114
|
-
* @param {ItemsProps[]} items
|
|
115
|
-
* @param {number} level
|
|
116
|
-
* @returns {IContextualMenuItem[]}
|
|
117
|
-
*/
|
|
118
|
-
this.mapValuesToContextMenu = (items, level = 0) => {
|
|
119
|
-
return items.map((item) => {
|
|
120
|
-
if (item.children && item.children.length) {
|
|
121
|
-
item.split = true;
|
|
122
|
-
const refId = this.getRefId(item.value, level);
|
|
123
|
-
if (!this.submenuRefs[refId]) {
|
|
124
|
-
this.submenuRefs[refId] = react_1.default.createRef();
|
|
125
|
-
}
|
|
126
|
-
item.subMenuProps = {
|
|
127
|
-
componentRef: this.submenuRefs[refId],
|
|
128
|
-
items: this.mapValuesToContextMenu(item.children, level + 1),
|
|
129
|
-
focusZoneProps: {
|
|
130
|
-
handleTabKey: react_2.FocusZoneTabbableElements.none,
|
|
131
|
-
onFocus: () => {
|
|
132
|
-
const openerItem = this.defaultSubmenuFocus?.parent;
|
|
133
|
-
if (openerItem && openerItem.item.value === item.value && openerItem.level === level) {
|
|
134
|
-
this.focusItemWithValue(this.state.value, this.defaultSubmenuFocus?.parent?.item.subMenuProps?.items);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
...item,
|
|
142
|
-
key: item.value,
|
|
143
|
-
text: item.label,
|
|
144
|
-
className: 'ui-tree-dropdown-list-item',
|
|
145
|
-
onClick: () => this.handleSelection(item.value),
|
|
146
|
-
onRenderContent: this.handleRenderContent
|
|
147
|
-
};
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Handle the selected value.
|
|
152
|
-
*
|
|
153
|
-
* @param {string} value
|
|
154
|
-
*/
|
|
155
|
-
this.handleSelection = (value) => {
|
|
156
|
-
this.setState({ hasSelected: true, value: value, valueChanged: false }, () => this.props.onParameterValueChange(value));
|
|
157
|
-
};
|
|
158
|
-
/**
|
|
159
|
-
* Handle the keypress value.
|
|
160
|
-
*
|
|
161
|
-
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
162
|
-
*/
|
|
163
|
-
this.handleKeyPress = (event) => {
|
|
164
|
-
switch (event.key) {
|
|
165
|
-
case 'Enter':
|
|
166
|
-
if (!this.state.isMenuOpen) {
|
|
167
|
-
this.toggleMenu(false, event);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
this.setState({ valueChanged: true });
|
|
171
|
-
this.handleSelection(this.state.value ? this.state.value : '');
|
|
172
|
-
}
|
|
173
|
-
break;
|
|
174
|
-
case 'ArrowDown':
|
|
175
|
-
if (!this.state.isMenuOpen) {
|
|
176
|
-
// Open dropdown contextMenu if closed
|
|
177
|
-
this.toggleMenu(false, event);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
this.focusDropdown(event, event.key);
|
|
181
|
-
}
|
|
182
|
-
break;
|
|
183
|
-
case 'Tab':
|
|
184
|
-
if (this.state.isMenuOpen) {
|
|
185
|
-
// Close Dropdown if open
|
|
186
|
-
this.toggleMenu(true);
|
|
187
|
-
}
|
|
188
|
-
this.handleSelection(this.state.value ? this.state.value : '');
|
|
189
|
-
break;
|
|
190
|
-
default: {
|
|
191
|
-
// do nothing
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
this.lastKeyDown = event.key;
|
|
195
|
-
};
|
|
196
|
-
/**
|
|
197
|
-
* Handle ContextMenu focus.
|
|
198
|
-
*
|
|
199
|
-
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
200
|
-
* @param {string} key
|
|
201
|
-
*/
|
|
202
|
-
this.focusDropdown = (event, key) => {
|
|
203
|
-
if (this.UITreeDropdownFocusZoneRef) {
|
|
204
|
-
if (key === KEYBOARD_KEYS.Enter) {
|
|
205
|
-
this.focusItemWithValue(this.state.value, this.state.items);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
this.UITreeDropdownFocusZoneRef.current?.focus(true);
|
|
209
|
-
}
|
|
210
|
-
// disable scroll which sometimes triggers
|
|
211
|
-
event.preventDefault();
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
/**
|
|
215
|
-
* Custom handle the render from subMenu to control the highlight and the .is-selected.
|
|
216
|
-
*
|
|
217
|
-
* @param {IContextualMenuListProps} props
|
|
218
|
-
* @param {IContextualMenuItemRenderFunctions} defaultRenders
|
|
219
|
-
* @returns { React.ReactNode | null}
|
|
220
|
-
*/
|
|
221
|
-
this.handleRenderContent = (props, defaultRenders) => {
|
|
222
|
-
props.item.className = props.item.value === this.props.value ? 'is-selected' : '';
|
|
223
|
-
props.item.text = this.highlightQuery(props.item.label, this.state.query);
|
|
224
|
-
this.applySubmenuPosition(props.item);
|
|
225
|
-
return defaultRenders ? defaultRenders.renderItemName(props) : null;
|
|
226
|
-
};
|
|
227
|
-
/**
|
|
228
|
-
* Custom handle the render to control the highlight and the .is-selected.
|
|
229
|
-
*
|
|
230
|
-
* @param {IContextualMenuListProps} props
|
|
231
|
-
* @param {IRenderFunction<IContextualMenuListProps>} defaultRender
|
|
232
|
-
* @returns {JSX.Element | null}
|
|
233
|
-
*/
|
|
234
|
-
this.handleRenderMenuList = (props, defaultRender) => {
|
|
235
|
-
let mappedItems = [];
|
|
236
|
-
if (props?.items) {
|
|
237
|
-
mappedItems = props.items.map((item) => {
|
|
238
|
-
item.className = item.value === this.props.value ? 'is-selected' : '';
|
|
239
|
-
item.text = this.highlightQuery(item.label, this.state.query);
|
|
240
|
-
this.applySubmenuPosition(item);
|
|
241
|
-
item.subMenuProps?.items.map((subItem) => {
|
|
242
|
-
subItem.className = subItem.value === this.props.value ? 'is-selected' : '';
|
|
243
|
-
return subItem;
|
|
244
|
-
});
|
|
245
|
-
return { ...item };
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
return defaultRender ? defaultRender({ ...props, items: mappedItems }) : null;
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* Handle on/off ContextualMenu.
|
|
252
|
-
*
|
|
253
|
-
* @param {boolean} status
|
|
254
|
-
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
255
|
-
*/
|
|
256
|
-
this.toggleMenu = (status, event) => {
|
|
257
|
-
if (this.props.readOnly) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
this.setState({
|
|
261
|
-
isHidden: status
|
|
262
|
-
});
|
|
263
|
-
const key = event?.key;
|
|
264
|
-
//select first item after contextMenu is opened
|
|
265
|
-
if (event) {
|
|
266
|
-
setTimeout(() => {
|
|
267
|
-
event.persist();
|
|
268
|
-
this.focusDropdown(event, key);
|
|
269
|
-
}, 0);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
/**
|
|
273
|
-
* Highlight the search string.
|
|
274
|
-
*
|
|
275
|
-
* @param {string} text
|
|
276
|
-
* @param {string} query
|
|
277
|
-
* @returns {JSX.Element}
|
|
278
|
-
*/
|
|
279
|
-
this.highlightQuery = (text, query) => {
|
|
280
|
-
return react_1.default.createElement(UIContextualMenu_1.UIHighlightMenuOption, { text: text, query: query });
|
|
281
|
-
};
|
|
282
|
-
/**
|
|
283
|
-
* Filter all options that match the query string.
|
|
284
|
-
*
|
|
285
|
-
* @param {string} input
|
|
286
|
-
* @param {IContextualMenuItem} item
|
|
287
|
-
* @returns {boolean}
|
|
288
|
-
*/
|
|
289
|
-
this.filterElement = (input, item) => {
|
|
290
|
-
const regex = new RegExp(input, 'ig');
|
|
291
|
-
if (item?.children?.length) {
|
|
292
|
-
return item.children.filter((item) => this.filterElement(input, item)).length > 0;
|
|
293
|
-
}
|
|
294
|
-
if (item?.value) {
|
|
295
|
-
return item.value.search(regex) !== -1;
|
|
296
|
-
}
|
|
297
|
-
return false;
|
|
298
|
-
};
|
|
299
|
-
/**
|
|
300
|
-
* Update the query string and the prop value.
|
|
301
|
-
*
|
|
302
|
-
* @param {React.FormEvent<HTMLInputElement | HTMLTextAreaElement>} event
|
|
303
|
-
*/
|
|
304
|
-
this.handleOnChangeValue = (event) => {
|
|
305
|
-
const query = event.target;
|
|
306
|
-
const list = this.state.originalItems.filter((item) => this.filterElement(query.value, item));
|
|
307
|
-
this.setState({
|
|
308
|
-
hasSelected: false,
|
|
309
|
-
value: query.value,
|
|
310
|
-
items: list,
|
|
311
|
-
query: query.value,
|
|
312
|
-
valueChanged: true
|
|
313
|
-
});
|
|
314
|
-
if (!this.state.isMenuOpen) {
|
|
315
|
-
this.toggleMenu(false);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
/**
|
|
319
|
-
* Method reset states.
|
|
320
|
-
*
|
|
321
|
-
* @param {Event | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent} event
|
|
322
|
-
*/
|
|
323
|
-
this.handleDismiss = (event) => {
|
|
324
|
-
if (event && 'key' in event && event.key === KEYBOARD_KEYS.Escape) {
|
|
325
|
-
this.resetValue();
|
|
326
|
-
}
|
|
327
|
-
else if (!this.state.hasSelected) {
|
|
328
|
-
this.props.onParameterValueChange('');
|
|
329
|
-
}
|
|
330
|
-
this.setState({
|
|
331
|
-
items: this.state.originalItems,
|
|
332
|
-
query: ''
|
|
333
|
-
});
|
|
334
|
-
this.toggleMenu(true);
|
|
335
|
-
this.originalValue = undefined;
|
|
336
|
-
};
|
|
337
|
-
/**
|
|
338
|
-
* Method applies additional styling for submnu callout.
|
|
339
|
-
* It is used to apply scroll width offset - in result submenu should be displayed on the edge of root menu.
|
|
340
|
-
*
|
|
341
|
-
* @param {IContextualMenuItem} item Context menu item.
|
|
342
|
-
*/
|
|
343
|
-
this.applySubmenuPosition = (item) => {
|
|
344
|
-
if (item.subMenuProps?.items) {
|
|
345
|
-
if (!item.subMenuProps.calloutProps) {
|
|
346
|
-
item.subMenuProps.calloutProps = {};
|
|
347
|
-
}
|
|
348
|
-
item.subMenuProps.calloutProps.styles = {
|
|
349
|
-
root: {
|
|
350
|
-
marginLeft: this.submenuOffset
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
this.getCalloutDomRef = (submenu = false) => {
|
|
356
|
-
const menuLayerClass = `${SELECTOR_CLASSES.callout}${this.state.uiidInput}`;
|
|
357
|
-
const callout = document.querySelector(`.${menuLayerClass}`);
|
|
358
|
-
return submenu && callout ? callout.nextSibling : callout;
|
|
359
|
-
};
|
|
360
|
-
/**
|
|
361
|
-
* Method calculates offset size for submenus.
|
|
362
|
-
* Calculated offset should be used to position submenu right to edge of root menu.
|
|
363
|
-
* - Detects if scrollbar exists.
|
|
364
|
-
* - Calculates size of scrollbar and stores it as value for offset.
|
|
365
|
-
*/
|
|
366
|
-
this.calculateSubmenuOffset = () => {
|
|
367
|
-
const callout = this.getCalloutDomRef();
|
|
368
|
-
if (callout) {
|
|
369
|
-
const scrollContainer = callout.querySelector(`.${SELECTOR_CLASSES.scrollArea}`);
|
|
370
|
-
this.submenuOffset = 0;
|
|
371
|
-
if (scrollContainer && scrollContainer.scrollHeight > scrollContainer.clientHeight) {
|
|
372
|
-
this.submenuOffset = scrollContainer.offsetWidth - scrollContainer.clientWidth;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
/**
|
|
377
|
-
* Method updates state, if focus visible, using arrow keys.
|
|
378
|
-
*
|
|
379
|
-
* @param {HTMLElement|React.FocusEvent<HTMLElement>} ev
|
|
380
|
-
*/
|
|
381
|
-
this.onFocusElementChanged = (ev) => {
|
|
382
|
-
const menuOption = ev.getElementsByClassName('ts-Menu-option');
|
|
383
|
-
const isFocusVisible = document.getElementsByClassName('ms-Fabric--isFocusVisible');
|
|
384
|
-
if (isFocusVisible.length > 0 && menuOption.length > 0) {
|
|
385
|
-
this.setState({
|
|
386
|
-
value: ev.value ? ev.value : menuOption[0].innerText,
|
|
387
|
-
valueChanged: true
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
/**
|
|
392
|
-
* Method handles window keydown event.
|
|
393
|
-
* 1. Stores last keyboard pressed event.
|
|
394
|
-
* 2. Disables CircularNavigation for menus.
|
|
395
|
-
*
|
|
396
|
-
* @param {KeyboardEvent | React.KeyboardEvent<HTMLInputElement>} event
|
|
397
|
-
*/
|
|
398
|
-
this.onWindowKeyDown = (event) => {
|
|
399
|
-
this.lastKeyDown = event.key;
|
|
400
|
-
// Avoid circular navigation
|
|
401
|
-
const activeElement = document.activeElement;
|
|
402
|
-
if ([KEYBOARD_KEYS.ArrowDown, KEYBOARD_KEYS.ArrowUp].includes(event.key) && activeElement) {
|
|
403
|
-
// Disable CircularNavigation
|
|
404
|
-
// There is property in focusZoneProps, but it is overwritten by fluent ui and we can not change it from outside
|
|
405
|
-
const positions = this.getEdgePosition(activeElement);
|
|
406
|
-
const fromFirst = positions.includes(EdgePosition.First) && event.key === KEYBOARD_KEYS.ArrowUp;
|
|
407
|
-
const fromLast = positions.includes(EdgePosition.Last) && event.key === KEYBOARD_KEYS.ArrowDown;
|
|
408
|
-
if (fromFirst || fromLast) {
|
|
409
|
-
// Circular navigation case.
|
|
410
|
-
// Check if first item focused in root menu.
|
|
411
|
-
if (fromFirst && activeElement.closest(`.${SELECTOR_CLASSES.callout}${this.state.uiidInput}`)) {
|
|
412
|
-
// Focus input field if navigation triggered from first item using ArrowUp
|
|
413
|
-
this.inputRef.current?.focus();
|
|
414
|
-
}
|
|
415
|
-
event.stopPropagation();
|
|
416
|
-
event.preventDefault();
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
/**
|
|
421
|
-
* Method handles focus logic if arrow key was pressed.
|
|
422
|
-
*
|
|
423
|
-
* @param {FocusEvent} event
|
|
424
|
-
*/
|
|
425
|
-
this.handleCustomDownKey = (event) => {
|
|
426
|
-
if (this.lastKeyDown.includes('Arrow')) {
|
|
427
|
-
this.lastKeyDown = '';
|
|
428
|
-
this.onFocusElementChanged(event.target);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
/**
|
|
432
|
-
* Method appends custom keydown and focus event listeners when context menu is opened.
|
|
433
|
-
*/
|
|
434
|
-
this.applyCustomKeyDownHandlingEvents = () => {
|
|
435
|
-
window.addEventListener('keydown', this.onWindowKeyDown, true);
|
|
436
|
-
window.addEventListener('focus', this.handleCustomDownKey, true);
|
|
437
|
-
};
|
|
438
|
-
/**
|
|
439
|
-
* Method removes custom keydown and focus event listeners when context menu is dismissed.
|
|
440
|
-
*/
|
|
441
|
-
this.removeCustomKeyDownHandlingEvents = () => {
|
|
442
|
-
window.removeEventListener('keydown', this.onWindowKeyDown, true);
|
|
443
|
-
window.removeEventListener('focus', this.handleCustomDownKey, true);
|
|
444
|
-
};
|
|
445
|
-
/**
|
|
446
|
-
* Method receives any menu child element and returns edge positions if item is first or last in rendered menu.
|
|
447
|
-
*
|
|
448
|
-
* @param {Element} itemElement Item's DOM to check position.
|
|
449
|
-
* @returns {EdgePosition[]} Returns positions if element is first or last in menu - also can be both.
|
|
450
|
-
*/
|
|
451
|
-
this.getEdgePosition = (itemElement) => {
|
|
452
|
-
const container = itemElement.closest('ul');
|
|
453
|
-
const item = itemElement.closest('li');
|
|
454
|
-
const position = [];
|
|
455
|
-
if (container && item) {
|
|
456
|
-
if (container.children[0] === item) {
|
|
457
|
-
position.push(EdgePosition.First);
|
|
458
|
-
}
|
|
459
|
-
if (container.children[container.children.length - 1] === item) {
|
|
460
|
-
position.push(EdgePosition.Last);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return position;
|
|
464
|
-
};
|
|
465
|
-
/**
|
|
466
|
-
* Recursive method finds menu item info object in tree menu items by passed value/key of item.
|
|
467
|
-
*
|
|
468
|
-
* @param {string} [value] Value/key of item.
|
|
469
|
-
* @param {IContextualMenuItem[]} [items
|
|
470
|
-
* @param {TreeItemInfo} [parent] Item's parent object.
|
|
471
|
-
* @param {number} [level
|
|
472
|
-
* @returns {TreeItemInfo | undefined} Found menu item.
|
|
473
|
-
*/
|
|
474
|
-
this.findItemByValue = (value, items = [], parent, level = 0) => {
|
|
475
|
-
let selectedItem;
|
|
476
|
-
for (let i = 0; i < items.length; i++) {
|
|
477
|
-
const item = items[i];
|
|
478
|
-
const selectedItemTemp = {
|
|
479
|
-
item,
|
|
480
|
-
index: i,
|
|
481
|
-
parent,
|
|
482
|
-
level
|
|
483
|
-
};
|
|
484
|
-
if (item.value === value) {
|
|
485
|
-
selectedItem = selectedItemTemp;
|
|
486
|
-
}
|
|
487
|
-
else if (item.subMenuProps?.items?.length) {
|
|
488
|
-
selectedItem = this.findItemByValue(value, item.subMenuProps.items, selectedItemTemp, level + 1);
|
|
489
|
-
}
|
|
490
|
-
if (selectedItem) {
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return selectedItem;
|
|
495
|
-
};
|
|
496
|
-
/**
|
|
497
|
-
* Method finds DOM node of menu item based on received item object and container DOM.
|
|
498
|
-
*
|
|
499
|
-
* @param {HTMLElement} container Menu container DOM.
|
|
500
|
-
* @param {TreeItemInfo} item Menu item info object.
|
|
501
|
-
* @returns {HTMLElement | undefined} Found DOM element of item.
|
|
502
|
-
*/
|
|
503
|
-
this.getItemTarget = (container, item) => {
|
|
504
|
-
let itemDom;
|
|
505
|
-
const listDom = container.querySelector('.ms-ContextualMenu-list');
|
|
506
|
-
if (listDom && listDom.childNodes[item.index]) {
|
|
507
|
-
const listItemDom = listDom.childNodes[item.index];
|
|
508
|
-
const itemElement = listItemDom.firstChild;
|
|
509
|
-
itemDom = itemElement;
|
|
510
|
-
}
|
|
511
|
-
return itemDom;
|
|
512
|
-
};
|
|
513
|
-
/**
|
|
514
|
-
* Method focuses context menu item based on recieved value/key and menude data(items and hoisted object).
|
|
515
|
-
* Method works with any level menu.
|
|
516
|
-
*
|
|
517
|
-
* @param {string} [value] Value/key of item.
|
|
518
|
-
* @param {IContextualMenuItem[]} [items
|
|
519
|
-
*/
|
|
520
|
-
this.focusItemWithValue = (value, items = []) => {
|
|
521
|
-
const selectedItem = this.findItemByValue(value, items);
|
|
522
|
-
const callout = this.getCalloutDomRef(!!this.defaultSubmenuFocus);
|
|
523
|
-
this.defaultSubmenuFocus = undefined;
|
|
524
|
-
if (selectedItem && callout) {
|
|
525
|
-
let itemDom;
|
|
526
|
-
if (selectedItem.parent) {
|
|
527
|
-
// Item is in next level of menu - we need open submenu
|
|
528
|
-
const parentItemDom = this.getItemTarget(callout, selectedItem.parent);
|
|
529
|
-
if (parentItemDom) {
|
|
530
|
-
this.defaultSubmenuFocus = selectedItem;
|
|
531
|
-
parentItemDom.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 39, which: 39, bubbles: true }));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
// Item is in first level
|
|
536
|
-
itemDom = this.getItemTarget(callout, selectedItem);
|
|
537
|
-
}
|
|
538
|
-
// Focus target item or focus container while submenu is not opened
|
|
539
|
-
if (itemDom) {
|
|
540
|
-
itemDom.focus();
|
|
541
|
-
}
|
|
542
|
-
else {
|
|
543
|
-
const menuContainer = callout.querySelector(`.${SELECTOR_CLASSES.scrollArea}`);
|
|
544
|
-
menuContainer?.focus();
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
/**
|
|
549
|
-
* Generate unique id for menu component references.
|
|
550
|
-
*
|
|
551
|
-
* @param {string} value Value of item.
|
|
552
|
-
* @param {number} level Level of item in tree structure.
|
|
553
|
-
* @returns {string} Id containing value andf level in format "${value}__${level}".
|
|
554
|
-
*/
|
|
555
|
-
this.getRefId = (value, level) => {
|
|
556
|
-
return `${value}__${level}`;
|
|
557
|
-
};
|
|
558
|
-
this.state = {
|
|
559
|
-
query: '',
|
|
560
|
-
hasSelected: this.props.value ? true : false,
|
|
561
|
-
// value has to be set, otherwise react treats this as "uncontrolled" component
|
|
562
|
-
// and displays warnings when value is set later on
|
|
563
|
-
value: this.props.value || '',
|
|
564
|
-
isHidden: true,
|
|
565
|
-
originalItems: [],
|
|
566
|
-
items: [],
|
|
567
|
-
valueSeparator: this.props.valueSeparator || '.',
|
|
568
|
-
uiidInput: uuid_1.default.v4(),
|
|
569
|
-
isDisabled: this.props.items.length ? false : true,
|
|
570
|
-
isMenuOpen: false,
|
|
571
|
-
valueChanged: false
|
|
572
|
-
};
|
|
573
|
-
this.toggleMenu = this.toggleMenu.bind(this);
|
|
574
|
-
this.onWindowKeyDown = this.onWindowKeyDown.bind(this);
|
|
575
|
-
this.handleCustomDownKey = this.handleCustomDownKey.bind(this);
|
|
576
|
-
// Suppress icon warnings, as they are irrelevant
|
|
577
|
-
(0, react_2.setIconOptions)({ disableWarnings: true });
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* Method resets value of dropdown input to original value, which was stored after open.
|
|
581
|
-
*/
|
|
582
|
-
resetValue() {
|
|
583
|
-
this.setState({
|
|
584
|
-
value: this.originalValue,
|
|
585
|
-
valueChanged: false
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Method returns class names for wrapper element depending on props and component state.
|
|
590
|
-
*
|
|
591
|
-
* @returns {string} Class names of wrapper element.
|
|
592
|
-
*/
|
|
593
|
-
getClassNames() {
|
|
594
|
-
let classNames = `ui-treeDropdown-wrapper ui-treeDropdown-wrapper-menu${this.state.isMenuOpen ? '-open' : '-close'} ui-treeDropdown-wrapper-${this.state.uiidInput}`;
|
|
595
|
-
if (this.state.isDisabled) {
|
|
596
|
-
classNames += ' disabled';
|
|
597
|
-
}
|
|
598
|
-
if (this.props.readOnly) {
|
|
599
|
-
classNames += ' readonly';
|
|
600
|
-
}
|
|
601
|
-
return classNames;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* @returns {JSX.Element}
|
|
605
|
-
*/
|
|
606
|
-
render() {
|
|
607
|
-
const messageInfo = (0, ValidationMessage_1.getMessageInfo)(this.props);
|
|
608
|
-
let useTargetWidth = true;
|
|
609
|
-
if (this.props.useTargetWidth) {
|
|
610
|
-
useTargetWidth = false;
|
|
611
|
-
}
|
|
612
|
-
return (react_1.default.createElement("div", { className: `ui-treeDropdown ui-treeDropDown-${this.state.uiidInput} ${this.props.label ? 'ui-treeDropdown-with-label' : ''}` },
|
|
613
|
-
this.props.label && (react_1.default.createElement("label", { className: `${this.props.required ? 'required' : ''} ${this.state.isDisabled ? 'disabled' : ''}` }, this.props.label)),
|
|
614
|
-
react_1.default.createElement("div", { className: this.getClassNames() },
|
|
615
|
-
react_1.default.createElement(UIInput_1.UITextInput, { componentRef: this.inputRef, disabled: this.state.isDisabled, readOnly: this.props.readOnly, autoComplete: "off", value: this.state.value, placeholder: this.props.placeholderText, onKeyDown: this.handleKeyPress, onChange: this.handleOnChangeValue, onClick: () => {
|
|
616
|
-
this.toggleMenu(false);
|
|
617
|
-
}, onFocus: (event) => {
|
|
618
|
-
// Select the text of the input
|
|
619
|
-
event.target.select();
|
|
620
|
-
}, errorMessage: messageInfo.message }),
|
|
621
|
-
react_1.default.createElement(UIIconButton_1.UIIconButton, { tabIndex: -1, allowDisabledFocus: true, className: "ui-treeDropdown-caret", iconProps: { iconName: Icons_1.UiIcons.ArrowDown }, onClick: () => {
|
|
622
|
-
if (this.state.isHidden) {
|
|
623
|
-
// Menu would become visible - focus input
|
|
624
|
-
this.inputRef.current?.focus();
|
|
625
|
-
}
|
|
626
|
-
this.toggleMenu(!this.state.isHidden);
|
|
627
|
-
} })),
|
|
628
|
-
!this.state.isHidden && (react_1.default.createElement(UIContextualMenu_1.UIContextualMenu, { componentRef: this.UITreeDropdownRef, onRenderMenuList: this.handleRenderMenuList, className: "ui-treeDropDown-context-menu", target: `.ui-treeDropDown-${this.state.uiidInput}`, onMenuOpened: () => {
|
|
629
|
-
this.originalValue = this.state.value;
|
|
630
|
-
this.applyCustomKeyDownHandlingEvents();
|
|
631
|
-
this.setState({
|
|
632
|
-
isMenuOpen: true
|
|
633
|
-
});
|
|
634
|
-
}, onMenuDismissed: () => {
|
|
635
|
-
this.removeCustomKeyDownHandlingEvents();
|
|
636
|
-
this.setState({ isMenuOpen: false });
|
|
637
|
-
if (this.state.valueChanged) {
|
|
638
|
-
this.handleSelection(this.state.value ? this.state.value : '');
|
|
639
|
-
}
|
|
640
|
-
}, useTargetWidth: useTargetWidth, useTargetAsMinWidth: true, onRestoreFocus: (params) => {
|
|
641
|
-
params.originalElement?.focus();
|
|
642
|
-
}, shouldUpdateWhenHidden: true, items: this.state.items, onDismiss: this.handleDismiss, shouldFocusOnContainer: false, focusZoneProps: {
|
|
643
|
-
componentRef: this.UITreeDropdownFocusZoneRef,
|
|
644
|
-
handleTabKey: react_2.FocusZoneTabbableElements.none,
|
|
645
|
-
isCircularNavigation: false
|
|
646
|
-
}, shouldFocusOnMount: false, directionalHint: this.props.directionalHint, calloutProps: {
|
|
647
|
-
layerProps: {
|
|
648
|
-
className: `${SELECTOR_CLASSES.callout}${this.state.uiidInput}`
|
|
649
|
-
},
|
|
650
|
-
onLayerMounted: () => {
|
|
651
|
-
this.calculateSubmenuOffset();
|
|
652
|
-
}
|
|
653
|
-
}, styles: {
|
|
654
|
-
container: {
|
|
655
|
-
maxHeight: 192,
|
|
656
|
-
overflowY: 'auto'
|
|
657
|
-
}
|
|
658
|
-
}, maxWidth: this.props.maxWidth }))));
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
exports.UITreeDropdown = UITreeDropdown;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UITreeDropdown = exports.EdgePosition = exports.UIDirectionalHint = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const uuid_1 = __importDefault(require("uuid"));
|
|
9
|
+
const react_2 = require("@fluentui/react");
|
|
10
|
+
Object.defineProperty(exports, "UIDirectionalHint", { enumerable: true, get: function () { return react_2.DirectionalHint; } });
|
|
11
|
+
const UIInput_1 = require("../UIInput");
|
|
12
|
+
const UIContextualMenu_1 = require("../UIContextualMenu");
|
|
13
|
+
const UIIconButton_1 = require("../UIButton/UIIconButton");
|
|
14
|
+
const Icons_1 = require("../Icons");
|
|
15
|
+
const ValidationMessage_1 = require("../../helper/ValidationMessage");
|
|
16
|
+
require("./UITreeDropdown.scss");
|
|
17
|
+
const SELECTOR_CLASSES = {
|
|
18
|
+
callout: 'ui-tree-callout',
|
|
19
|
+
scrollArea: 'ms-ContextualMenu-container',
|
|
20
|
+
splitButton: 'ms-ContextualMenu-splitMenu'
|
|
21
|
+
};
|
|
22
|
+
var EdgePosition;
|
|
23
|
+
(function (EdgePosition) {
|
|
24
|
+
EdgePosition["First"] = "First";
|
|
25
|
+
EdgePosition["Last"] = "Last";
|
|
26
|
+
})(EdgePosition = exports.EdgePosition || (exports.EdgePosition = {}));
|
|
27
|
+
const KEYBOARD_KEYS = {
|
|
28
|
+
ArrowUp: 'ArrowUp',
|
|
29
|
+
ArrowDown: 'ArrowDown',
|
|
30
|
+
Enter: 'Enter',
|
|
31
|
+
Escape: 'Escape'
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* UITreeDropdown component.
|
|
35
|
+
*
|
|
36
|
+
* @exports
|
|
37
|
+
* @class UIVerticalDivider
|
|
38
|
+
* @extends {React.Component<UITreeDropdownProps, UITreeDropdownState>}
|
|
39
|
+
*/
|
|
40
|
+
class UITreeDropdown extends react_1.default.Component {
|
|
41
|
+
/**
|
|
42
|
+
* Initializes component properties.
|
|
43
|
+
*
|
|
44
|
+
* @param {UITreeDropdownProps} props
|
|
45
|
+
*/
|
|
46
|
+
constructor(props) {
|
|
47
|
+
super(props);
|
|
48
|
+
this.UITreeDropdownRef = react_1.default.createRef();
|
|
49
|
+
this.UITreeDropdownFocusZoneRef = react_1.default.createRef();
|
|
50
|
+
this.inputRef = react_1.default.createRef();
|
|
51
|
+
this.submenuRefs = {};
|
|
52
|
+
// Calculated offset for submenu positions
|
|
53
|
+
// It is added because root menu can have scrollbar - in result root menu's items width is smaller than root menu.
|
|
54
|
+
// In such case(when scrollbar) submenu is positioned near to expand/hovered item and position is not on the edge of root menu.
|
|
55
|
+
// Using offset/margin we can do corrections to position of submenu and place it one the edge of root menu.
|
|
56
|
+
this.submenuOffset = 0;
|
|
57
|
+
this.lastKeyDown = '';
|
|
58
|
+
this.componentDidMount = () => {
|
|
59
|
+
if (this.props.items.length) {
|
|
60
|
+
this.buildItems(this.props.items);
|
|
61
|
+
this.setState({ isDisabled: false });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
this.componentDidUpdate = (prevProps) => {
|
|
65
|
+
if (this.props.items.length !== prevProps.items.length) {
|
|
66
|
+
this.setState({ isHidden: true });
|
|
67
|
+
this.setState({ isDisabled: this.props.items.length ? false : true });
|
|
68
|
+
this.buildItems(this.props.items);
|
|
69
|
+
}
|
|
70
|
+
if (this.props.value !== prevProps.value) {
|
|
71
|
+
this.setState({ value: this.props.value });
|
|
72
|
+
}
|
|
73
|
+
// Calculate size for submenu offset
|
|
74
|
+
this.calculateSubmenuOffset();
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Map the payload.
|
|
78
|
+
*
|
|
79
|
+
* @param {ItemsProps[]} items
|
|
80
|
+
*/
|
|
81
|
+
this.buildItems = (items) => {
|
|
82
|
+
if (this.state.items.length !== items.length) {
|
|
83
|
+
items = items.map(this.buildSubItems);
|
|
84
|
+
const mapedItems = this.mapValuesToContextMenu(items);
|
|
85
|
+
this.setState({
|
|
86
|
+
originalItems: mapedItems,
|
|
87
|
+
items: mapedItems
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Sub items values and style.
|
|
93
|
+
*
|
|
94
|
+
* @param {ItemsProps} item
|
|
95
|
+
* @returns {ItemsProps}
|
|
96
|
+
*/
|
|
97
|
+
this.buildSubItems = (item) => {
|
|
98
|
+
if (item.children && item.children.length) {
|
|
99
|
+
item.children = item.children.map((el) => {
|
|
100
|
+
const regex = new RegExp(item.value, 'ig');
|
|
101
|
+
const value = el.value.search(regex) === -1 ? `${item.value}${this.state.valueSeparator}${el.value}` : el.value;
|
|
102
|
+
return {
|
|
103
|
+
...el,
|
|
104
|
+
split: false,
|
|
105
|
+
value
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return item;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Map GD payload to ContextMenu payload.
|
|
113
|
+
*
|
|
114
|
+
* @param {ItemsProps[]} items
|
|
115
|
+
* @param {number} level
|
|
116
|
+
* @returns {IContextualMenuItem[]}
|
|
117
|
+
*/
|
|
118
|
+
this.mapValuesToContextMenu = (items, level = 0) => {
|
|
119
|
+
return items.map((item) => {
|
|
120
|
+
if (item.children && item.children.length) {
|
|
121
|
+
item.split = true;
|
|
122
|
+
const refId = this.getRefId(item.value, level);
|
|
123
|
+
if (!this.submenuRefs[refId]) {
|
|
124
|
+
this.submenuRefs[refId] = react_1.default.createRef();
|
|
125
|
+
}
|
|
126
|
+
item.subMenuProps = {
|
|
127
|
+
componentRef: this.submenuRefs[refId],
|
|
128
|
+
items: this.mapValuesToContextMenu(item.children, level + 1),
|
|
129
|
+
focusZoneProps: {
|
|
130
|
+
handleTabKey: react_2.FocusZoneTabbableElements.none,
|
|
131
|
+
onFocus: () => {
|
|
132
|
+
const openerItem = this.defaultSubmenuFocus?.parent;
|
|
133
|
+
if (openerItem && openerItem.item.value === item.value && openerItem.level === level) {
|
|
134
|
+
this.focusItemWithValue(this.state.value, this.defaultSubmenuFocus?.parent?.item.subMenuProps?.items);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...item,
|
|
142
|
+
key: item.value,
|
|
143
|
+
text: item.label,
|
|
144
|
+
className: 'ui-tree-dropdown-list-item',
|
|
145
|
+
onClick: () => this.handleSelection(item.value),
|
|
146
|
+
onRenderContent: this.handleRenderContent
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Handle the selected value.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} value
|
|
154
|
+
*/
|
|
155
|
+
this.handleSelection = (value) => {
|
|
156
|
+
this.setState({ hasSelected: true, value: value, valueChanged: false }, () => this.props.onParameterValueChange(value));
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Handle the keypress value.
|
|
160
|
+
*
|
|
161
|
+
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
162
|
+
*/
|
|
163
|
+
this.handleKeyPress = (event) => {
|
|
164
|
+
switch (event.key) {
|
|
165
|
+
case 'Enter':
|
|
166
|
+
if (!this.state.isMenuOpen) {
|
|
167
|
+
this.toggleMenu(false, event);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this.setState({ valueChanged: true });
|
|
171
|
+
this.handleSelection(this.state.value ? this.state.value : '');
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'ArrowDown':
|
|
175
|
+
if (!this.state.isMenuOpen) {
|
|
176
|
+
// Open dropdown contextMenu if closed
|
|
177
|
+
this.toggleMenu(false, event);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.focusDropdown(event, event.key);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'Tab':
|
|
184
|
+
if (this.state.isMenuOpen) {
|
|
185
|
+
// Close Dropdown if open
|
|
186
|
+
this.toggleMenu(true);
|
|
187
|
+
}
|
|
188
|
+
this.handleSelection(this.state.value ? this.state.value : '');
|
|
189
|
+
break;
|
|
190
|
+
default: {
|
|
191
|
+
// do nothing
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
this.lastKeyDown = event.key;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Handle ContextMenu focus.
|
|
198
|
+
*
|
|
199
|
+
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
200
|
+
* @param {string} key
|
|
201
|
+
*/
|
|
202
|
+
this.focusDropdown = (event, key) => {
|
|
203
|
+
if (this.UITreeDropdownFocusZoneRef) {
|
|
204
|
+
if (key === KEYBOARD_KEYS.Enter) {
|
|
205
|
+
this.focusItemWithValue(this.state.value, this.state.items);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.UITreeDropdownFocusZoneRef.current?.focus(true);
|
|
209
|
+
}
|
|
210
|
+
// disable scroll which sometimes triggers
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Custom handle the render from subMenu to control the highlight and the .is-selected.
|
|
216
|
+
*
|
|
217
|
+
* @param {IContextualMenuListProps} props
|
|
218
|
+
* @param {IContextualMenuItemRenderFunctions} defaultRenders
|
|
219
|
+
* @returns { React.ReactNode | null}
|
|
220
|
+
*/
|
|
221
|
+
this.handleRenderContent = (props, defaultRenders) => {
|
|
222
|
+
props.item.className = props.item.value === this.props.value ? 'is-selected' : '';
|
|
223
|
+
props.item.text = this.highlightQuery(props.item.label, this.state.query);
|
|
224
|
+
this.applySubmenuPosition(props.item);
|
|
225
|
+
return defaultRenders ? defaultRenders.renderItemName(props) : null;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Custom handle the render to control the highlight and the .is-selected.
|
|
229
|
+
*
|
|
230
|
+
* @param {IContextualMenuListProps} props
|
|
231
|
+
* @param {IRenderFunction<IContextualMenuListProps>} defaultRender
|
|
232
|
+
* @returns {JSX.Element | null}
|
|
233
|
+
*/
|
|
234
|
+
this.handleRenderMenuList = (props, defaultRender) => {
|
|
235
|
+
let mappedItems = [];
|
|
236
|
+
if (props?.items) {
|
|
237
|
+
mappedItems = props.items.map((item) => {
|
|
238
|
+
item.className = item.value === this.props.value ? 'is-selected' : '';
|
|
239
|
+
item.text = this.highlightQuery(item.label, this.state.query);
|
|
240
|
+
this.applySubmenuPosition(item);
|
|
241
|
+
item.subMenuProps?.items.map((subItem) => {
|
|
242
|
+
subItem.className = subItem.value === this.props.value ? 'is-selected' : '';
|
|
243
|
+
return subItem;
|
|
244
|
+
});
|
|
245
|
+
return { ...item };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return defaultRender ? defaultRender({ ...props, items: mappedItems }) : null;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Handle on/off ContextualMenu.
|
|
252
|
+
*
|
|
253
|
+
* @param {boolean} status
|
|
254
|
+
* @param {React.KeyboardEvent<HTMLInputElement>} event
|
|
255
|
+
*/
|
|
256
|
+
this.toggleMenu = (status, event) => {
|
|
257
|
+
if (this.props.readOnly) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this.setState({
|
|
261
|
+
isHidden: status
|
|
262
|
+
});
|
|
263
|
+
const key = event?.key;
|
|
264
|
+
//select first item after contextMenu is opened
|
|
265
|
+
if (event) {
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
event.persist();
|
|
268
|
+
this.focusDropdown(event, key);
|
|
269
|
+
}, 0);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Highlight the search string.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} text
|
|
276
|
+
* @param {string} query
|
|
277
|
+
* @returns {JSX.Element}
|
|
278
|
+
*/
|
|
279
|
+
this.highlightQuery = (text, query) => {
|
|
280
|
+
return react_1.default.createElement(UIContextualMenu_1.UIHighlightMenuOption, { text: text, query: query });
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Filter all options that match the query string.
|
|
284
|
+
*
|
|
285
|
+
* @param {string} input
|
|
286
|
+
* @param {IContextualMenuItem} item
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
this.filterElement = (input, item) => {
|
|
290
|
+
const regex = new RegExp(input, 'ig');
|
|
291
|
+
if (item?.children?.length) {
|
|
292
|
+
return item.children.filter((item) => this.filterElement(input, item)).length > 0;
|
|
293
|
+
}
|
|
294
|
+
if (item?.value) {
|
|
295
|
+
return item.value.search(regex) !== -1;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
};
|
|
299
|
+
/**
|
|
300
|
+
* Update the query string and the prop value.
|
|
301
|
+
*
|
|
302
|
+
* @param {React.FormEvent<HTMLInputElement | HTMLTextAreaElement>} event
|
|
303
|
+
*/
|
|
304
|
+
this.handleOnChangeValue = (event) => {
|
|
305
|
+
const query = event.target;
|
|
306
|
+
const list = this.state.originalItems.filter((item) => this.filterElement(query.value, item));
|
|
307
|
+
this.setState({
|
|
308
|
+
hasSelected: false,
|
|
309
|
+
value: query.value,
|
|
310
|
+
items: list,
|
|
311
|
+
query: query.value,
|
|
312
|
+
valueChanged: true
|
|
313
|
+
});
|
|
314
|
+
if (!this.state.isMenuOpen) {
|
|
315
|
+
this.toggleMenu(false);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Method reset states.
|
|
320
|
+
*
|
|
321
|
+
* @param {Event | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent} event
|
|
322
|
+
*/
|
|
323
|
+
this.handleDismiss = (event) => {
|
|
324
|
+
if (event && 'key' in event && event.key === KEYBOARD_KEYS.Escape) {
|
|
325
|
+
this.resetValue();
|
|
326
|
+
}
|
|
327
|
+
else if (!this.state.hasSelected) {
|
|
328
|
+
this.props.onParameterValueChange('');
|
|
329
|
+
}
|
|
330
|
+
this.setState({
|
|
331
|
+
items: this.state.originalItems,
|
|
332
|
+
query: ''
|
|
333
|
+
});
|
|
334
|
+
this.toggleMenu(true);
|
|
335
|
+
this.originalValue = undefined;
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Method applies additional styling for submnu callout.
|
|
339
|
+
* It is used to apply scroll width offset - in result submenu should be displayed on the edge of root menu.
|
|
340
|
+
*
|
|
341
|
+
* @param {IContextualMenuItem} item Context menu item.
|
|
342
|
+
*/
|
|
343
|
+
this.applySubmenuPosition = (item) => {
|
|
344
|
+
if (item.subMenuProps?.items) {
|
|
345
|
+
if (!item.subMenuProps.calloutProps) {
|
|
346
|
+
item.subMenuProps.calloutProps = {};
|
|
347
|
+
}
|
|
348
|
+
item.subMenuProps.calloutProps.styles = {
|
|
349
|
+
root: {
|
|
350
|
+
marginLeft: this.submenuOffset
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
this.getCalloutDomRef = (submenu = false) => {
|
|
356
|
+
const menuLayerClass = `${SELECTOR_CLASSES.callout}${this.state.uiidInput}`;
|
|
357
|
+
const callout = document.querySelector(`.${menuLayerClass}`);
|
|
358
|
+
return submenu && callout ? callout.nextSibling : callout;
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* Method calculates offset size for submenus.
|
|
362
|
+
* Calculated offset should be used to position submenu right to edge of root menu.
|
|
363
|
+
* - Detects if scrollbar exists.
|
|
364
|
+
* - Calculates size of scrollbar and stores it as value for offset.
|
|
365
|
+
*/
|
|
366
|
+
this.calculateSubmenuOffset = () => {
|
|
367
|
+
const callout = this.getCalloutDomRef();
|
|
368
|
+
if (callout) {
|
|
369
|
+
const scrollContainer = callout.querySelector(`.${SELECTOR_CLASSES.scrollArea}`);
|
|
370
|
+
this.submenuOffset = 0;
|
|
371
|
+
if (scrollContainer && scrollContainer.scrollHeight > scrollContainer.clientHeight) {
|
|
372
|
+
this.submenuOffset = scrollContainer.offsetWidth - scrollContainer.clientWidth;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* Method updates state, if focus visible, using arrow keys.
|
|
378
|
+
*
|
|
379
|
+
* @param {HTMLElement|React.FocusEvent<HTMLElement>} ev
|
|
380
|
+
*/
|
|
381
|
+
this.onFocusElementChanged = (ev) => {
|
|
382
|
+
const menuOption = ev.getElementsByClassName('ts-Menu-option');
|
|
383
|
+
const isFocusVisible = document.getElementsByClassName('ms-Fabric--isFocusVisible');
|
|
384
|
+
if (isFocusVisible.length > 0 && menuOption.length > 0) {
|
|
385
|
+
this.setState({
|
|
386
|
+
value: ev.value ? ev.value : menuOption[0].innerText,
|
|
387
|
+
valueChanged: true
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* Method handles window keydown event.
|
|
393
|
+
* 1. Stores last keyboard pressed event.
|
|
394
|
+
* 2. Disables CircularNavigation for menus.
|
|
395
|
+
*
|
|
396
|
+
* @param {KeyboardEvent | React.KeyboardEvent<HTMLInputElement>} event
|
|
397
|
+
*/
|
|
398
|
+
this.onWindowKeyDown = (event) => {
|
|
399
|
+
this.lastKeyDown = event.key;
|
|
400
|
+
// Avoid circular navigation
|
|
401
|
+
const activeElement = document.activeElement;
|
|
402
|
+
if ([KEYBOARD_KEYS.ArrowDown, KEYBOARD_KEYS.ArrowUp].includes(event.key) && activeElement) {
|
|
403
|
+
// Disable CircularNavigation
|
|
404
|
+
// There is property in focusZoneProps, but it is overwritten by fluent ui and we can not change it from outside
|
|
405
|
+
const positions = this.getEdgePosition(activeElement);
|
|
406
|
+
const fromFirst = positions.includes(EdgePosition.First) && event.key === KEYBOARD_KEYS.ArrowUp;
|
|
407
|
+
const fromLast = positions.includes(EdgePosition.Last) && event.key === KEYBOARD_KEYS.ArrowDown;
|
|
408
|
+
if (fromFirst || fromLast) {
|
|
409
|
+
// Circular navigation case.
|
|
410
|
+
// Check if first item focused in root menu.
|
|
411
|
+
if (fromFirst && activeElement.closest(`.${SELECTOR_CLASSES.callout}${this.state.uiidInput}`)) {
|
|
412
|
+
// Focus input field if navigation triggered from first item using ArrowUp
|
|
413
|
+
this.inputRef.current?.focus();
|
|
414
|
+
}
|
|
415
|
+
event.stopPropagation();
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Method handles focus logic if arrow key was pressed.
|
|
422
|
+
*
|
|
423
|
+
* @param {FocusEvent} event
|
|
424
|
+
*/
|
|
425
|
+
this.handleCustomDownKey = (event) => {
|
|
426
|
+
if (this.lastKeyDown.includes('Arrow')) {
|
|
427
|
+
this.lastKeyDown = '';
|
|
428
|
+
this.onFocusElementChanged(event.target);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Method appends custom keydown and focus event listeners when context menu is opened.
|
|
433
|
+
*/
|
|
434
|
+
this.applyCustomKeyDownHandlingEvents = () => {
|
|
435
|
+
window.addEventListener('keydown', this.onWindowKeyDown, true);
|
|
436
|
+
window.addEventListener('focus', this.handleCustomDownKey, true);
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Method removes custom keydown and focus event listeners when context menu is dismissed.
|
|
440
|
+
*/
|
|
441
|
+
this.removeCustomKeyDownHandlingEvents = () => {
|
|
442
|
+
window.removeEventListener('keydown', this.onWindowKeyDown, true);
|
|
443
|
+
window.removeEventListener('focus', this.handleCustomDownKey, true);
|
|
444
|
+
};
|
|
445
|
+
/**
|
|
446
|
+
* Method receives any menu child element and returns edge positions if item is first or last in rendered menu.
|
|
447
|
+
*
|
|
448
|
+
* @param {Element} itemElement Item's DOM to check position.
|
|
449
|
+
* @returns {EdgePosition[]} Returns positions if element is first or last in menu - also can be both.
|
|
450
|
+
*/
|
|
451
|
+
this.getEdgePosition = (itemElement) => {
|
|
452
|
+
const container = itemElement.closest('ul');
|
|
453
|
+
const item = itemElement.closest('li');
|
|
454
|
+
const position = [];
|
|
455
|
+
if (container && item) {
|
|
456
|
+
if (container.children[0] === item) {
|
|
457
|
+
position.push(EdgePosition.First);
|
|
458
|
+
}
|
|
459
|
+
if (container.children[container.children.length - 1] === item) {
|
|
460
|
+
position.push(EdgePosition.Last);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return position;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* Recursive method finds menu item info object in tree menu items by passed value/key of item.
|
|
467
|
+
*
|
|
468
|
+
* @param {string} [value] Value/key of item.
|
|
469
|
+
* @param {IContextualMenuItem[]} [items] Menu items.
|
|
470
|
+
* @param {TreeItemInfo} [parent] Item's parent object.
|
|
471
|
+
* @param {number} [level] Level of item in tree structure.
|
|
472
|
+
* @returns {TreeItemInfo | undefined} Found menu item.
|
|
473
|
+
*/
|
|
474
|
+
this.findItemByValue = (value, items = [], parent, level = 0) => {
|
|
475
|
+
let selectedItem;
|
|
476
|
+
for (let i = 0; i < items.length; i++) {
|
|
477
|
+
const item = items[i];
|
|
478
|
+
const selectedItemTemp = {
|
|
479
|
+
item,
|
|
480
|
+
index: i,
|
|
481
|
+
parent,
|
|
482
|
+
level
|
|
483
|
+
};
|
|
484
|
+
if (item.value === value) {
|
|
485
|
+
selectedItem = selectedItemTemp;
|
|
486
|
+
}
|
|
487
|
+
else if (item.subMenuProps?.items?.length) {
|
|
488
|
+
selectedItem = this.findItemByValue(value, item.subMenuProps.items, selectedItemTemp, level + 1);
|
|
489
|
+
}
|
|
490
|
+
if (selectedItem) {
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return selectedItem;
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Method finds DOM node of menu item based on received item object and container DOM.
|
|
498
|
+
*
|
|
499
|
+
* @param {HTMLElement} container Menu container DOM.
|
|
500
|
+
* @param {TreeItemInfo} item Menu item info object.
|
|
501
|
+
* @returns {HTMLElement | undefined} Found DOM element of item.
|
|
502
|
+
*/
|
|
503
|
+
this.getItemTarget = (container, item) => {
|
|
504
|
+
let itemDom;
|
|
505
|
+
const listDom = container.querySelector('.ms-ContextualMenu-list');
|
|
506
|
+
if (listDom && listDom.childNodes[item.index]) {
|
|
507
|
+
const listItemDom = listDom.childNodes[item.index];
|
|
508
|
+
const itemElement = listItemDom.firstChild;
|
|
509
|
+
itemDom = itemElement;
|
|
510
|
+
}
|
|
511
|
+
return itemDom;
|
|
512
|
+
};
|
|
513
|
+
/**
|
|
514
|
+
* Method focuses context menu item based on recieved value/key and menude data(items and hoisted object).
|
|
515
|
+
* Method works with any level menu.
|
|
516
|
+
*
|
|
517
|
+
* @param {string} [value] Value/key of item.
|
|
518
|
+
* @param {IContextualMenuItem[]} [items] Target menu items.
|
|
519
|
+
*/
|
|
520
|
+
this.focusItemWithValue = (value, items = []) => {
|
|
521
|
+
const selectedItem = this.findItemByValue(value, items);
|
|
522
|
+
const callout = this.getCalloutDomRef(!!this.defaultSubmenuFocus);
|
|
523
|
+
this.defaultSubmenuFocus = undefined;
|
|
524
|
+
if (selectedItem && callout) {
|
|
525
|
+
let itemDom;
|
|
526
|
+
if (selectedItem.parent) {
|
|
527
|
+
// Item is in next level of menu - we need open submenu
|
|
528
|
+
const parentItemDom = this.getItemTarget(callout, selectedItem.parent);
|
|
529
|
+
if (parentItemDom) {
|
|
530
|
+
this.defaultSubmenuFocus = selectedItem;
|
|
531
|
+
parentItemDom.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 39, which: 39, bubbles: true }));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
// Item is in first level
|
|
536
|
+
itemDom = this.getItemTarget(callout, selectedItem);
|
|
537
|
+
}
|
|
538
|
+
// Focus target item or focus container while submenu is not opened
|
|
539
|
+
if (itemDom) {
|
|
540
|
+
itemDom.focus();
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
const menuContainer = callout.querySelector(`.${SELECTOR_CLASSES.scrollArea}`);
|
|
544
|
+
menuContainer?.focus();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* Generate unique id for menu component references.
|
|
550
|
+
*
|
|
551
|
+
* @param {string} value Value of item.
|
|
552
|
+
* @param {number} level Level of item in tree structure.
|
|
553
|
+
* @returns {string} Id containing value andf level in format "${value}__${level}".
|
|
554
|
+
*/
|
|
555
|
+
this.getRefId = (value, level) => {
|
|
556
|
+
return `${value}__${level}`;
|
|
557
|
+
};
|
|
558
|
+
this.state = {
|
|
559
|
+
query: '',
|
|
560
|
+
hasSelected: this.props.value ? true : false,
|
|
561
|
+
// value has to be set, otherwise react treats this as "uncontrolled" component
|
|
562
|
+
// and displays warnings when value is set later on
|
|
563
|
+
value: this.props.value || '',
|
|
564
|
+
isHidden: true,
|
|
565
|
+
originalItems: [],
|
|
566
|
+
items: [],
|
|
567
|
+
valueSeparator: this.props.valueSeparator || '.',
|
|
568
|
+
uiidInput: uuid_1.default.v4(),
|
|
569
|
+
isDisabled: this.props.items.length ? false : true,
|
|
570
|
+
isMenuOpen: false,
|
|
571
|
+
valueChanged: false
|
|
572
|
+
};
|
|
573
|
+
this.toggleMenu = this.toggleMenu.bind(this);
|
|
574
|
+
this.onWindowKeyDown = this.onWindowKeyDown.bind(this);
|
|
575
|
+
this.handleCustomDownKey = this.handleCustomDownKey.bind(this);
|
|
576
|
+
// Suppress icon warnings, as they are irrelevant
|
|
577
|
+
(0, react_2.setIconOptions)({ disableWarnings: true });
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Method resets value of dropdown input to original value, which was stored after open.
|
|
581
|
+
*/
|
|
582
|
+
resetValue() {
|
|
583
|
+
this.setState({
|
|
584
|
+
value: this.originalValue,
|
|
585
|
+
valueChanged: false
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Method returns class names for wrapper element depending on props and component state.
|
|
590
|
+
*
|
|
591
|
+
* @returns {string} Class names of wrapper element.
|
|
592
|
+
*/
|
|
593
|
+
getClassNames() {
|
|
594
|
+
let classNames = `ui-treeDropdown-wrapper ui-treeDropdown-wrapper-menu${this.state.isMenuOpen ? '-open' : '-close'} ui-treeDropdown-wrapper-${this.state.uiidInput}`;
|
|
595
|
+
if (this.state.isDisabled) {
|
|
596
|
+
classNames += ' disabled';
|
|
597
|
+
}
|
|
598
|
+
if (this.props.readOnly) {
|
|
599
|
+
classNames += ' readonly';
|
|
600
|
+
}
|
|
601
|
+
return classNames;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* @returns {JSX.Element}
|
|
605
|
+
*/
|
|
606
|
+
render() {
|
|
607
|
+
const messageInfo = (0, ValidationMessage_1.getMessageInfo)(this.props);
|
|
608
|
+
let useTargetWidth = true;
|
|
609
|
+
if (this.props.useTargetWidth) {
|
|
610
|
+
useTargetWidth = false;
|
|
611
|
+
}
|
|
612
|
+
return (react_1.default.createElement("div", { className: `ui-treeDropdown ui-treeDropDown-${this.state.uiidInput} ${this.props.label ? 'ui-treeDropdown-with-label' : ''}` },
|
|
613
|
+
this.props.label && (react_1.default.createElement("label", { className: `${this.props.required ? 'required' : ''} ${this.state.isDisabled ? 'disabled' : ''}` }, this.props.label)),
|
|
614
|
+
react_1.default.createElement("div", { className: this.getClassNames() },
|
|
615
|
+
react_1.default.createElement(UIInput_1.UITextInput, { componentRef: this.inputRef, disabled: this.state.isDisabled, readOnly: this.props.readOnly, autoComplete: "off", value: this.state.value, placeholder: this.props.placeholderText, onKeyDown: this.handleKeyPress, onChange: this.handleOnChangeValue, onClick: () => {
|
|
616
|
+
this.toggleMenu(false);
|
|
617
|
+
}, onFocus: (event) => {
|
|
618
|
+
// Select the text of the input
|
|
619
|
+
event.target.select();
|
|
620
|
+
}, errorMessage: messageInfo.message }),
|
|
621
|
+
react_1.default.createElement(UIIconButton_1.UIIconButton, { tabIndex: -1, allowDisabledFocus: true, className: "ui-treeDropdown-caret", iconProps: { iconName: Icons_1.UiIcons.ArrowDown }, onClick: () => {
|
|
622
|
+
if (this.state.isHidden) {
|
|
623
|
+
// Menu would become visible - focus input
|
|
624
|
+
this.inputRef.current?.focus();
|
|
625
|
+
}
|
|
626
|
+
this.toggleMenu(!this.state.isHidden);
|
|
627
|
+
} })),
|
|
628
|
+
!this.state.isHidden && (react_1.default.createElement(UIContextualMenu_1.UIContextualMenu, { componentRef: this.UITreeDropdownRef, onRenderMenuList: this.handleRenderMenuList, className: "ui-treeDropDown-context-menu", target: `.ui-treeDropDown-${this.state.uiidInput}`, onMenuOpened: () => {
|
|
629
|
+
this.originalValue = this.state.value;
|
|
630
|
+
this.applyCustomKeyDownHandlingEvents();
|
|
631
|
+
this.setState({
|
|
632
|
+
isMenuOpen: true
|
|
633
|
+
});
|
|
634
|
+
}, onMenuDismissed: () => {
|
|
635
|
+
this.removeCustomKeyDownHandlingEvents();
|
|
636
|
+
this.setState({ isMenuOpen: false });
|
|
637
|
+
if (this.state.valueChanged) {
|
|
638
|
+
this.handleSelection(this.state.value ? this.state.value : '');
|
|
639
|
+
}
|
|
640
|
+
}, useTargetWidth: useTargetWidth, useTargetAsMinWidth: true, onRestoreFocus: (params) => {
|
|
641
|
+
params.originalElement?.focus();
|
|
642
|
+
}, shouldUpdateWhenHidden: true, items: this.state.items, onDismiss: this.handleDismiss, shouldFocusOnContainer: false, focusZoneProps: {
|
|
643
|
+
componentRef: this.UITreeDropdownFocusZoneRef,
|
|
644
|
+
handleTabKey: react_2.FocusZoneTabbableElements.none,
|
|
645
|
+
isCircularNavigation: false
|
|
646
|
+
}, shouldFocusOnMount: false, directionalHint: this.props.directionalHint, calloutProps: {
|
|
647
|
+
layerProps: {
|
|
648
|
+
className: `${SELECTOR_CLASSES.callout}${this.state.uiidInput}`
|
|
649
|
+
},
|
|
650
|
+
onLayerMounted: () => {
|
|
651
|
+
this.calculateSubmenuOffset();
|
|
652
|
+
}
|
|
653
|
+
}, styles: {
|
|
654
|
+
container: {
|
|
655
|
+
maxHeight: 192,
|
|
656
|
+
overflowY: 'auto'
|
|
657
|
+
}
|
|
658
|
+
}, maxWidth: this.props.maxWidth }))));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
exports.UITreeDropdown = UITreeDropdown;
|
|
662
662
|
//# sourceMappingURL=UITreeDropdown.js.map
|