@khanacademy/wonder-blocks-dropdown 2.7.4 → 2.8.0
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/CHANGELOG.md +28 -0
- package/dist/es/index.js +167 -167
- package/dist/index.js +389 -360
- package/package.json +7 -7
- package/src/components/__docs__/action-menu.argtypes.js +44 -0
- package/src/components/__docs__/action-menu.stories.js +435 -0
- package/src/components/__docs__/base-select.argtypes.js +54 -0
- package/src/components/__docs__/multi-select.stories.js +509 -0
- package/src/components/__docs__/single-select.accessibility.stories.mdx +59 -0
- package/src/components/__docs__/single-select.argtypes.js +54 -0
- package/src/components/__docs__/single-select.stories.js +464 -0
- package/src/components/__tests__/dropdown-core-virtualized.test.js +0 -15
- package/src/components/__tests__/dropdown-core.test.js +113 -209
- package/src/components/__tests__/multi-select.test.js +49 -3
- package/src/components/__tests__/single-select.test.js +43 -50
- package/src/components/action-menu.js +11 -0
- package/src/components/dropdown-core-virtualized.js +0 -5
- package/src/components/dropdown-core.js +224 -130
- package/src/components/multi-select.js +18 -33
- package/src/components/single-select.js +16 -30
- package/src/util/__tests__/dropdown-menu-styles.test.js +0 -26
- package/src/util/__tests__/helpers.test.js +73 -0
- package/src/util/constants.js +0 -11
- package/src/util/dropdown-menu-styles.js +0 -5
- package/src/util/helpers.js +44 -0
- package/src/util/types.js +2 -5
- package/src/components/__tests__/search-text-input.test.js +0 -212
- package/src/components/action-menu.stories.js +0 -48
- package/src/components/multi-select.stories.js +0 -124
- package/src/components/search-text-input.js +0 -115
- package/src/components/single-select.stories.js +0 -247
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-dropdown
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ee6fc773: Added keyboard support to search items when the dropdown is focused, included "Enter" as a key to trigger actions with the "option" role
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [ee6fc773]
|
|
12
|
+
- @khanacademy/wonder-blocks-clickable@2.3.0
|
|
13
|
+
- @khanacademy/wonder-blocks-button@3.0.1
|
|
14
|
+
- @khanacademy/wonder-blocks-icon-button@3.4.9
|
|
15
|
+
- @khanacademy/wonder-blocks-modal@2.3.4
|
|
16
|
+
- @khanacademy/wonder-blocks-search-field@1.0.7
|
|
17
|
+
|
|
18
|
+
## 2.7.6
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 3007ecd7: Move the search field out of the listbox container
|
|
23
|
+
|
|
24
|
+
## 2.7.5
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [85c31780]
|
|
29
|
+
- @khanacademy/wonder-blocks-button@3.0.0
|
|
30
|
+
|
|
3
31
|
## 2.7.4
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/dist/es/index.js
CHANGED
|
@@ -12,8 +12,8 @@ import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutP
|
|
|
12
12
|
import Icon, { icons } from '@khanacademy/wonder-blocks-icon';
|
|
13
13
|
import ReactDOM from 'react-dom';
|
|
14
14
|
import { VariableSizeList } from 'react-window';
|
|
15
|
-
import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
|
|
16
15
|
import SearchField from '@khanacademy/wonder-blocks-search-field';
|
|
16
|
+
import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
|
|
17
17
|
import { Popper } from 'react-popper';
|
|
18
18
|
import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
|
|
19
19
|
import { Strut } from '@khanacademy/wonder-blocks-layout';
|
|
@@ -33,15 +33,9 @@ const selectDropdownStyle = {
|
|
|
33
33
|
const filterableDropdownStyle = {
|
|
34
34
|
minHeight: 100
|
|
35
35
|
};
|
|
36
|
-
const searchInputStyle = {
|
|
37
|
-
margin: Spacing.xSmall_8,
|
|
38
|
-
marginTop: Spacing.xxxSmall_4,
|
|
39
|
-
minHeight: "auto"
|
|
40
|
-
};
|
|
41
36
|
const DROPDOWN_ITEM_HEIGHT = 40;
|
|
42
37
|
const MAX_VISIBLE_ITEMS = 9;
|
|
43
38
|
const SEPARATOR_ITEM_HEIGHT = 9;
|
|
44
|
-
const SEARCH_ITEM_HEIGHT = DROPDOWN_ITEM_HEIGHT + searchInputStyle.margin + searchInputStyle.marginTop;
|
|
45
39
|
const defaultLabels = {
|
|
46
40
|
clearSearch: "Clear search",
|
|
47
41
|
filter: "Filter",
|
|
@@ -487,56 +481,10 @@ class DropdownVirtualizedItem extends React.Component {
|
|
|
487
481
|
|
|
488
482
|
}
|
|
489
483
|
|
|
490
|
-
class SearchTextInput extends React.Component {
|
|
491
|
-
static isClassOf(instance) {
|
|
492
|
-
return instance && instance.type && instance.type.__IS_SEARCH_TEXT_INPUT__;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
componentDidMount() {
|
|
496
|
-
if (this.props.autofocus) {
|
|
497
|
-
var _this$props$itemRef;
|
|
498
|
-
|
|
499
|
-
(_this$props$itemRef = this.props.itemRef) == null ? void 0 : _this$props$itemRef.current.focus();
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
render() {
|
|
504
|
-
const {
|
|
505
|
-
labels,
|
|
506
|
-
onChange,
|
|
507
|
-
onClick,
|
|
508
|
-
itemRef,
|
|
509
|
-
searchText,
|
|
510
|
-
style,
|
|
511
|
-
testId
|
|
512
|
-
} = this.props;
|
|
513
|
-
return React.createElement(SearchField, {
|
|
514
|
-
clearAriaLabel: labels.clearSearch,
|
|
515
|
-
onChange: onChange,
|
|
516
|
-
onClick: onClick,
|
|
517
|
-
placeholder: labels.filter,
|
|
518
|
-
ref: itemRef,
|
|
519
|
-
style: style,
|
|
520
|
-
testId: testId,
|
|
521
|
-
value: searchText
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
}
|
|
526
|
-
SearchTextInput.defaultProps = {
|
|
527
|
-
labels: {
|
|
528
|
-
clearSearch: defaultLabels.clearSearch,
|
|
529
|
-
filter: defaultLabels.filter
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
SearchTextInput.__IS_SEARCH_TEXT_INPUT__ = true;
|
|
533
|
-
|
|
534
484
|
function getDropdownMenuHeight(items, initialHeight = 0) {
|
|
535
485
|
return items.slice(0, MAX_VISIBLE_ITEMS).reduce((sum, item) => {
|
|
536
486
|
if (SeparatorItem.isClassOf(item.component)) {
|
|
537
487
|
return sum + SEPARATOR_ITEM_HEIGHT;
|
|
538
|
-
} else if (SearchTextInput.isClassOf(item.component)) {
|
|
539
|
-
return sum + SEARCH_ITEM_HEIGHT;
|
|
540
488
|
} else {
|
|
541
489
|
return sum + DROPDOWN_ITEM_HEIGHT;
|
|
542
490
|
}
|
|
@@ -561,8 +509,6 @@ class DropdownCoreVirtualized extends React.Component {
|
|
|
561
509
|
|
|
562
510
|
if (SeparatorItem.isClassOf(item.component)) {
|
|
563
511
|
return SEPARATOR_ITEM_HEIGHT;
|
|
564
|
-
} else if (SearchTextInput.isClassOf(item.component)) {
|
|
565
|
-
return SEARCH_ITEM_HEIGHT;
|
|
566
512
|
} else {
|
|
567
513
|
return DROPDOWN_ITEM_HEIGHT;
|
|
568
514
|
}
|
|
@@ -718,6 +664,26 @@ function DropdownPopper({
|
|
|
718
664
|
}), modalHost);
|
|
719
665
|
}
|
|
720
666
|
|
|
667
|
+
function getStringForKey(key) {
|
|
668
|
+
if (key.length === 1 || !/^[A-Z]/i.test(key)) {
|
|
669
|
+
return key;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return "";
|
|
673
|
+
}
|
|
674
|
+
function debounce(callback, wait) {
|
|
675
|
+
let timeout;
|
|
676
|
+
return function executedFunction(...args) {
|
|
677
|
+
const later = () => {
|
|
678
|
+
clearTimeout(timeout);
|
|
679
|
+
callback.apply(void 0, args);
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
clearTimeout(timeout);
|
|
683
|
+
timeout = setTimeout(later, wait);
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
721
687
|
const VIRTUALIZE_THRESHOLD = 125;
|
|
722
688
|
const StyledSpan = addStyle("span");
|
|
723
689
|
|
|
@@ -765,6 +731,7 @@ class DropdownCore extends React.Component {
|
|
|
765
731
|
|
|
766
732
|
constructor(props) {
|
|
767
733
|
super(props);
|
|
734
|
+
this.searchFieldRef = React.createRef();
|
|
768
735
|
|
|
769
736
|
this.handleInteract = event => {
|
|
770
737
|
const {
|
|
@@ -787,6 +754,12 @@ class DropdownCore extends React.Component {
|
|
|
787
754
|
} = this.props;
|
|
788
755
|
const keyCode = event.which || event.keyCode;
|
|
789
756
|
|
|
757
|
+
if (getStringForKey(event.key)) {
|
|
758
|
+
event.stopPropagation();
|
|
759
|
+
this.textSuggestion += event.key;
|
|
760
|
+
this.handleKeyDownDebounced(this.textSuggestion);
|
|
761
|
+
}
|
|
762
|
+
|
|
790
763
|
if (!open) {
|
|
791
764
|
if (keyCode === keyCodes.down) {
|
|
792
765
|
event.preventDefault();
|
|
@@ -799,7 +772,7 @@ class DropdownCore extends React.Component {
|
|
|
799
772
|
|
|
800
773
|
switch (keyCode) {
|
|
801
774
|
case keyCodes.tab:
|
|
802
|
-
if (this.
|
|
775
|
+
if (this.isSearchFieldFocused() && searchText) {
|
|
803
776
|
return;
|
|
804
777
|
}
|
|
805
778
|
|
|
@@ -808,7 +781,7 @@ class DropdownCore extends React.Component {
|
|
|
808
781
|
return;
|
|
809
782
|
|
|
810
783
|
case keyCodes.space:
|
|
811
|
-
if (this.
|
|
784
|
+
if (this.isSearchFieldFocused()) {
|
|
812
785
|
return;
|
|
813
786
|
}
|
|
814
787
|
|
|
@@ -836,7 +809,7 @@ class DropdownCore extends React.Component {
|
|
|
836
809
|
|
|
837
810
|
switch (keyCode) {
|
|
838
811
|
case keyCodes.space:
|
|
839
|
-
if (this.
|
|
812
|
+
if (this.isSearchFieldFocused()) {
|
|
840
813
|
return;
|
|
841
814
|
}
|
|
842
815
|
|
|
@@ -854,6 +827,39 @@ class DropdownCore extends React.Component {
|
|
|
854
827
|
}
|
|
855
828
|
};
|
|
856
829
|
|
|
830
|
+
this.handleKeyDownDebounceResult = key => {
|
|
831
|
+
const foundIndex = this.props.items.filter(item => item.focusable).findIndex(({
|
|
832
|
+
component
|
|
833
|
+
}) => {
|
|
834
|
+
var _component$props;
|
|
835
|
+
|
|
836
|
+
if (SeparatorItem.isClassOf(component)) {
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const label = (_component$props = component.props) == null ? void 0 : _component$props.label.toLowerCase();
|
|
841
|
+
return label.startsWith(key.toLowerCase());
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
if (foundIndex >= 0) {
|
|
845
|
+
const isClosed = !this.props.open;
|
|
846
|
+
|
|
847
|
+
if (isClosed) {
|
|
848
|
+
this.props.onOpenChanged(true);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
this.focusedIndex = foundIndex;
|
|
852
|
+
this.scheduleToFocusCurrentItem(node => {
|
|
853
|
+
if (this.props.selectionType === "single" && isClosed && node) {
|
|
854
|
+
node.click();
|
|
855
|
+
this.props.onOpenChanged(false);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
this.textSuggestion = "";
|
|
861
|
+
};
|
|
862
|
+
|
|
857
863
|
this.handleClickFocus = index => {
|
|
858
864
|
this.itemsClicked = true;
|
|
859
865
|
this.focusedIndex = index;
|
|
@@ -880,6 +886,16 @@ class DropdownCore extends React.Component {
|
|
|
880
886
|
}
|
|
881
887
|
};
|
|
882
888
|
|
|
889
|
+
this.handleSearchTextChanged = searchText => {
|
|
890
|
+
const {
|
|
891
|
+
onSearchTextChanged
|
|
892
|
+
} = this.props;
|
|
893
|
+
|
|
894
|
+
if (onSearchTextChanged) {
|
|
895
|
+
onSearchTextChanged(searchText);
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
883
899
|
this.resetFocusedIndex();
|
|
884
900
|
this.state = {
|
|
885
901
|
prevItems: this.props.items,
|
|
@@ -890,6 +906,8 @@ class DropdownCore extends React.Component {
|
|
|
890
906
|
}, props.labels)
|
|
891
907
|
};
|
|
892
908
|
this.virtualizedListRef = React.createRef();
|
|
909
|
+
this.handleKeyDownDebounced = debounce(this.handleKeyDownDebounceResult, 500);
|
|
910
|
+
this.textSuggestion = "";
|
|
893
911
|
}
|
|
894
912
|
|
|
895
913
|
componentDidMount() {
|
|
@@ -937,22 +955,18 @@ class DropdownCore extends React.Component {
|
|
|
937
955
|
this.removeEventListeners();
|
|
938
956
|
}
|
|
939
957
|
|
|
940
|
-
hasSearchBox() {
|
|
941
|
-
return !!this.props.onSearchTextChanged && typeof this.props.searchText === "string";
|
|
942
|
-
}
|
|
943
|
-
|
|
944
958
|
resetFocusedIndex() {
|
|
945
959
|
const {
|
|
946
960
|
initialFocusedIndex
|
|
947
961
|
} = this.props;
|
|
948
962
|
|
|
949
|
-
if (initialFocusedIndex) {
|
|
950
|
-
|
|
951
|
-
this.focusedIndex = initialFocusedIndex + 1;
|
|
952
|
-
} else {
|
|
953
|
-
this.focusedIndex = initialFocusedIndex;
|
|
954
|
-
}
|
|
963
|
+
if (typeof initialFocusedIndex !== "undefined") {
|
|
964
|
+
this.focusedIndex = initialFocusedIndex;
|
|
955
965
|
} else {
|
|
966
|
+
if (this.hasSearchField() && !this.isSearchFieldFocused()) {
|
|
967
|
+
return this.focusSearchField();
|
|
968
|
+
}
|
|
969
|
+
|
|
956
970
|
this.focusedIndex = 0;
|
|
957
971
|
}
|
|
958
972
|
}
|
|
@@ -988,33 +1002,57 @@ class DropdownCore extends React.Component {
|
|
|
988
1002
|
document.removeEventListener("touchend", this.handleInteract);
|
|
989
1003
|
}
|
|
990
1004
|
|
|
991
|
-
scheduleToFocusCurrentItem() {
|
|
1005
|
+
scheduleToFocusCurrentItem(onFocus) {
|
|
992
1006
|
if (this.shouldVirtualizeList()) {
|
|
993
|
-
this.props.schedule.animationFrame(() =>
|
|
1007
|
+
this.props.schedule.animationFrame(() => {
|
|
1008
|
+
this.focusCurrentItem(onFocus);
|
|
1009
|
+
});
|
|
994
1010
|
} else {
|
|
995
|
-
this.focusCurrentItem();
|
|
1011
|
+
this.focusCurrentItem(onFocus);
|
|
996
1012
|
}
|
|
997
1013
|
}
|
|
998
1014
|
|
|
999
|
-
focusCurrentItem() {
|
|
1000
|
-
const
|
|
1015
|
+
focusCurrentItem(onFocus) {
|
|
1016
|
+
const focusedItemRef = this.state.itemRefs[this.focusedIndex];
|
|
1001
1017
|
|
|
1002
|
-
if (
|
|
1018
|
+
if (focusedItemRef) {
|
|
1003
1019
|
if (this.virtualizedListRef.current) {
|
|
1004
|
-
this.virtualizedListRef.current.scrollToItem(
|
|
1020
|
+
this.virtualizedListRef.current.scrollToItem(focusedItemRef.originalIndex);
|
|
1005
1021
|
}
|
|
1006
1022
|
|
|
1007
|
-
const node = ReactDOM.findDOMNode(
|
|
1023
|
+
const node = ReactDOM.findDOMNode(focusedItemRef.ref.current);
|
|
1008
1024
|
|
|
1009
1025
|
if (node) {
|
|
1010
1026
|
node.focus();
|
|
1011
|
-
this.focusedOriginalIndex =
|
|
1027
|
+
this.focusedOriginalIndex = focusedItemRef.originalIndex;
|
|
1028
|
+
|
|
1029
|
+
if (onFocus) {
|
|
1030
|
+
onFocus(node);
|
|
1031
|
+
}
|
|
1012
1032
|
}
|
|
1013
1033
|
}
|
|
1014
1034
|
}
|
|
1015
1035
|
|
|
1036
|
+
focusSearchField() {
|
|
1037
|
+
if (this.searchFieldRef.current) {
|
|
1038
|
+
this.searchFieldRef.current.focus();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
hasSearchField() {
|
|
1043
|
+
return !!this.props.isFilterable;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
isSearchFieldFocused() {
|
|
1047
|
+
return this.hasSearchField() && document.activeElement === this.searchFieldRef.current;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1016
1050
|
focusPreviousItem() {
|
|
1017
1051
|
if (this.focusedIndex === 0) {
|
|
1052
|
+
if (this.hasSearchField() && !this.isSearchFieldFocused()) {
|
|
1053
|
+
return this.focusSearchField();
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1018
1056
|
this.focusedIndex = this.state.itemRefs.length - 1;
|
|
1019
1057
|
} else {
|
|
1020
1058
|
this.focusedIndex -= 1;
|
|
@@ -1025,6 +1063,10 @@ class DropdownCore extends React.Component {
|
|
|
1025
1063
|
|
|
1026
1064
|
focusNextItem() {
|
|
1027
1065
|
if (this.focusedIndex === this.state.itemRefs.length - 1) {
|
|
1066
|
+
if (this.hasSearchField() && !this.isSearchFieldFocused()) {
|
|
1067
|
+
return this.focusSearchField();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1028
1070
|
this.focusedIndex = 0;
|
|
1029
1071
|
} else {
|
|
1030
1072
|
this.focusedIndex += 1;
|
|
@@ -1059,15 +1101,11 @@ class DropdownCore extends React.Component {
|
|
|
1059
1101
|
maybeRenderNoResults() {
|
|
1060
1102
|
const {
|
|
1061
1103
|
items,
|
|
1062
|
-
onSearchTextChanged,
|
|
1063
|
-
searchText,
|
|
1064
1104
|
labels: {
|
|
1065
1105
|
noResults
|
|
1066
1106
|
}
|
|
1067
1107
|
} = this.props;
|
|
1068
|
-
const
|
|
1069
|
-
const includeSearchCount = showSearchTextInput ? 1 : 0;
|
|
1070
|
-
const numResults = items.length - includeSearchCount;
|
|
1108
|
+
const numResults = items.length;
|
|
1071
1109
|
|
|
1072
1110
|
if (numResults === 0) {
|
|
1073
1111
|
return React.createElement(LabelMedium, {
|
|
@@ -1103,20 +1141,6 @@ class DropdownCore extends React.Component {
|
|
|
1103
1141
|
|
|
1104
1142
|
const focusIndex = focusCounter - 1;
|
|
1105
1143
|
const currentRef = this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null;
|
|
1106
|
-
|
|
1107
|
-
if (SearchTextInput.isClassOf(component)) {
|
|
1108
|
-
return React.cloneElement(component, _extends({}, populatedProps, {
|
|
1109
|
-
key: "search-text-input",
|
|
1110
|
-
itemRef: currentRef,
|
|
1111
|
-
onClick: () => {
|
|
1112
|
-
this.handleClickFocus(0);
|
|
1113
|
-
this.focusCurrentItem();
|
|
1114
|
-
},
|
|
1115
|
-
style: searchInputStyle,
|
|
1116
|
-
autofocus: this.focusedIndex === 0
|
|
1117
|
-
}));
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
1144
|
return React.cloneElement(component, _extends({}, populatedProps, {
|
|
1121
1145
|
key: index,
|
|
1122
1146
|
onClick: () => {
|
|
@@ -1137,21 +1161,6 @@ class DropdownCore extends React.Component {
|
|
|
1137
1161
|
}
|
|
1138
1162
|
|
|
1139
1163
|
const focusIndex = focusCounter - 1;
|
|
1140
|
-
|
|
1141
|
-
if (SearchTextInput.isClassOf(item.component)) {
|
|
1142
|
-
return _extends({}, item, {
|
|
1143
|
-
onClick: () => {
|
|
1144
|
-
this.handleClickFocus(0);
|
|
1145
|
-
this.focusCurrentItem();
|
|
1146
|
-
},
|
|
1147
|
-
populatedProps: {
|
|
1148
|
-
style: searchInputStyle,
|
|
1149
|
-
itemRef: this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null,
|
|
1150
|
-
autofocus: this.focusedIndex === 0
|
|
1151
|
-
}
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
1164
|
return _extends({}, item, {
|
|
1156
1165
|
role: itemRole,
|
|
1157
1166
|
ref: item.focusable ? this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null : null,
|
|
@@ -1170,6 +1179,23 @@ class DropdownCore extends React.Component {
|
|
|
1170
1179
|
});
|
|
1171
1180
|
}
|
|
1172
1181
|
|
|
1182
|
+
renderSearchField() {
|
|
1183
|
+
const {
|
|
1184
|
+
searchText
|
|
1185
|
+
} = this.props;
|
|
1186
|
+
const {
|
|
1187
|
+
labels
|
|
1188
|
+
} = this.state;
|
|
1189
|
+
return React.createElement(SearchField, {
|
|
1190
|
+
clearAriaLabel: labels.clearSearch,
|
|
1191
|
+
onChange: this.handleSearchTextChanged,
|
|
1192
|
+
placeholder: labels.filter,
|
|
1193
|
+
ref: this.searchFieldRef,
|
|
1194
|
+
style: styles$3.searchInputStyle,
|
|
1195
|
+
value: searchText || ""
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1173
1199
|
renderDropdownMenu(listRenderer, isReferenceHidden) {
|
|
1174
1200
|
const {
|
|
1175
1201
|
dropdownStyle,
|
|
@@ -1178,13 +1204,15 @@ class DropdownCore extends React.Component {
|
|
|
1178
1204
|
} = this.props;
|
|
1179
1205
|
const openerStyle = openerElement && window.getComputedStyle(openerElement);
|
|
1180
1206
|
const minDropdownWidth = openerStyle ? openerStyle.getPropertyValue("width") : 0;
|
|
1181
|
-
const
|
|
1182
|
-
const maxDropdownHeight = getDropdownMenuHeight(this.props.items, initialHeight);
|
|
1207
|
+
const maxDropdownHeight = getDropdownMenuHeight(this.props.items);
|
|
1183
1208
|
return React.createElement(View, {
|
|
1184
1209
|
onMouseUp: this.handleDropdownMouseUp,
|
|
1210
|
+
style: [styles$3.dropdown, light && styles$3.light, isReferenceHidden && styles$3.hidden, dropdownStyle],
|
|
1211
|
+
testId: "dropdown-core-container"
|
|
1212
|
+
}, this.props.isFilterable && this.renderSearchField(), React.createElement(View, {
|
|
1185
1213
|
role: this.props.role,
|
|
1186
|
-
style: [styles$3.
|
|
1187
|
-
}, listRenderer, this.maybeRenderNoResults());
|
|
1214
|
+
style: [styles$3.listboxOrMenu, generateDropdownMenuStyles(minDropdownWidth, maxDropdownHeight)]
|
|
1215
|
+
}, listRenderer), this.maybeRenderNoResults());
|
|
1188
1216
|
}
|
|
1189
1217
|
|
|
1190
1218
|
renderDropdown() {
|
|
@@ -1210,7 +1238,7 @@ class DropdownCore extends React.Component {
|
|
|
1210
1238
|
const {
|
|
1211
1239
|
labels
|
|
1212
1240
|
} = this.state;
|
|
1213
|
-
const totalItems =
|
|
1241
|
+
const totalItems = items.length;
|
|
1214
1242
|
return React.createElement(StyledSpan, {
|
|
1215
1243
|
"aria-live": "polite",
|
|
1216
1244
|
"aria-atomic": "true",
|
|
@@ -1240,10 +1268,13 @@ class DropdownCore extends React.Component {
|
|
|
1240
1268
|
DropdownCore.defaultProps = {
|
|
1241
1269
|
alignment: "left",
|
|
1242
1270
|
labels: {
|
|
1271
|
+
clearSearch: defaultLabels.clearSearch,
|
|
1272
|
+
filter: defaultLabels.filter,
|
|
1243
1273
|
noResults: defaultLabels.noResults,
|
|
1244
1274
|
someSelected: defaultLabels.someSelected
|
|
1245
1275
|
},
|
|
1246
|
-
light: false
|
|
1276
|
+
light: false,
|
|
1277
|
+
selectionType: "single"
|
|
1247
1278
|
};
|
|
1248
1279
|
const styles$3 = StyleSheet.create({
|
|
1249
1280
|
menuWrapper: {
|
|
@@ -1255,12 +1286,14 @@ const styles$3 = StyleSheet.create({
|
|
|
1255
1286
|
paddingTop: Spacing.xxxSmall_4,
|
|
1256
1287
|
paddingBottom: Spacing.xxxSmall_4,
|
|
1257
1288
|
border: `solid 1px ${Color.offBlack16}`,
|
|
1258
|
-
boxShadow: `0px 8px 8px 0px ${fade(Color.offBlack, 0.1)}
|
|
1259
|
-
overflowY: "auto"
|
|
1289
|
+
boxShadow: `0px 8px 8px 0px ${fade(Color.offBlack, 0.1)}`
|
|
1260
1290
|
},
|
|
1261
1291
|
light: {
|
|
1262
1292
|
border: "none"
|
|
1263
1293
|
},
|
|
1294
|
+
listboxOrMenu: {
|
|
1295
|
+
overflowY: "auto"
|
|
1296
|
+
},
|
|
1264
1297
|
hidden: {
|
|
1265
1298
|
pointerEvents: "none",
|
|
1266
1299
|
visibility: "hidden"
|
|
@@ -1270,6 +1303,11 @@ const styles$3 = StyleSheet.create({
|
|
|
1270
1303
|
alignSelf: "center",
|
|
1271
1304
|
marginTop: Spacing.xxSmall_6
|
|
1272
1305
|
},
|
|
1306
|
+
searchInputStyle: {
|
|
1307
|
+
margin: Spacing.xSmall_8,
|
|
1308
|
+
marginTop: Spacing.xxxSmall_4,
|
|
1309
|
+
minHeight: "auto"
|
|
1310
|
+
},
|
|
1273
1311
|
srOnly: {
|
|
1274
1312
|
border: 0,
|
|
1275
1313
|
clip: "rect(0,0,0,0)",
|
|
@@ -1887,26 +1925,6 @@ class SingleSelect extends React.Component {
|
|
|
1887
1925
|
return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
|
|
1888
1926
|
}
|
|
1889
1927
|
|
|
1890
|
-
getSearchField() {
|
|
1891
|
-
if (!this.props.isFilterable) {
|
|
1892
|
-
return null;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
return {
|
|
1896
|
-
component: React.createElement(SearchTextInput, {
|
|
1897
|
-
key: "search-text-input",
|
|
1898
|
-
onChange: this.handleSearchTextChanged,
|
|
1899
|
-
searchText: this.state.searchText,
|
|
1900
|
-
labels: {
|
|
1901
|
-
clearSearch: defaultLabels.clearSearch,
|
|
1902
|
-
filter: defaultLabels.filter
|
|
1903
|
-
}
|
|
1904
|
-
}),
|
|
1905
|
-
focusable: true,
|
|
1906
|
-
populatedProps: {}
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
1928
|
renderOpener(numItems) {
|
|
1911
1929
|
const _this$props = this.props,
|
|
1912
1930
|
{
|
|
@@ -1955,12 +1973,11 @@ class SingleSelect extends React.Component {
|
|
|
1955
1973
|
searchText
|
|
1956
1974
|
} = this.state;
|
|
1957
1975
|
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
1958
|
-
const
|
|
1976
|
+
const items = this.getMenuItems(allChildren);
|
|
1959
1977
|
const opener = this.renderOpener(allChildren.length);
|
|
1960
|
-
const searchField = this.getSearchField();
|
|
1961
|
-
const items = searchField ? [searchField].concat(filteredItems) : filteredItems;
|
|
1962
1978
|
return React.createElement(DropdownCore$1, {
|
|
1963
1979
|
role: "listbox",
|
|
1980
|
+
selectionType: "single",
|
|
1964
1981
|
alignment: alignment,
|
|
1965
1982
|
dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
|
|
1966
1983
|
initialFocusedIndex: this.selectedIndex,
|
|
@@ -1972,6 +1989,7 @@ class SingleSelect extends React.Component {
|
|
|
1972
1989
|
openerElement: this.state.openerElement,
|
|
1973
1990
|
style: style,
|
|
1974
1991
|
className: className,
|
|
1992
|
+
isFilterable: isFilterable,
|
|
1975
1993
|
onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : null,
|
|
1976
1994
|
searchText: isFilterable ? searchText : ""
|
|
1977
1995
|
});
|
|
@@ -2120,30 +2138,6 @@ class MultiSelect extends React.Component {
|
|
|
2120
2138
|
}
|
|
2121
2139
|
}
|
|
2122
2140
|
|
|
2123
|
-
getSearchField() {
|
|
2124
|
-
if (!this.props.isFilterable) {
|
|
2125
|
-
return [];
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
const {
|
|
2129
|
-
clearSearch,
|
|
2130
|
-
filter
|
|
2131
|
-
} = this.state.labels;
|
|
2132
|
-
return [{
|
|
2133
|
-
component: React.createElement(SearchTextInput, {
|
|
2134
|
-
key: "search-text-input",
|
|
2135
|
-
onChange: this.handleSearchTextChanged,
|
|
2136
|
-
searchText: this.state.searchText,
|
|
2137
|
-
labels: {
|
|
2138
|
-
clearSearch,
|
|
2139
|
-
filter
|
|
2140
|
-
}
|
|
2141
|
-
}),
|
|
2142
|
-
focusable: true,
|
|
2143
|
-
populatedProps: {}
|
|
2144
|
-
}];
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
2141
|
getShortcuts(numOptions) {
|
|
2148
2142
|
const {
|
|
2149
2143
|
selectedValues,
|
|
@@ -2282,6 +2276,8 @@ class MultiSelect extends React.Component {
|
|
|
2282
2276
|
searchText
|
|
2283
2277
|
} = this.state;
|
|
2284
2278
|
const {
|
|
2279
|
+
clearSearch,
|
|
2280
|
+
filter,
|
|
2285
2281
|
noResults,
|
|
2286
2282
|
someSelected
|
|
2287
2283
|
} = this.state.labels;
|
|
@@ -2293,17 +2289,21 @@ class MultiSelect extends React.Component {
|
|
|
2293
2289
|
role: "listbox",
|
|
2294
2290
|
alignment: alignment,
|
|
2295
2291
|
dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
|
|
2296
|
-
|
|
2292
|
+
isFilterable: isFilterable,
|
|
2293
|
+
items: [].concat(this.getShortcuts(numOptions), filteredItems),
|
|
2297
2294
|
light: light,
|
|
2298
2295
|
onOpenChanged: this.handleOpenChanged,
|
|
2299
2296
|
open: open,
|
|
2300
2297
|
opener: opener,
|
|
2301
2298
|
openerElement: this.state.openerElement,
|
|
2299
|
+
selectionType: "multi",
|
|
2302
2300
|
style: style,
|
|
2303
2301
|
className: className,
|
|
2304
2302
|
onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : null,
|
|
2305
2303
|
searchText: isFilterable ? searchText : "",
|
|
2306
2304
|
labels: {
|
|
2305
|
+
clearSearch,
|
|
2306
|
+
filter,
|
|
2307
2307
|
noResults,
|
|
2308
2308
|
someSelected
|
|
2309
2309
|
}
|