@khanacademy/wonder-blocks-dropdown 6.0.0 → 6.1.1

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/index.js CHANGED
@@ -61,13 +61,12 @@ var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
61
61
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
62
62
  var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
63
63
 
64
- const keyCodes = {
65
- tab: 9,
66
- enter: 13,
67
- escape: 27,
68
- space: 32,
69
- up: 38,
70
- down: 40
64
+ const keys = {
65
+ escape: "Escape",
66
+ tab: "Tab",
67
+ space: " ",
68
+ up: "ArrowUp",
69
+ down: "ArrowDown"
71
70
  };
72
71
  const selectDropdownStyle = {
73
72
  marginTop: tokens.spacing.xSmall_8,
@@ -531,7 +530,9 @@ class DropdownOpener extends React__namespace.Component {
531
530
  opened,
532
531
  "aria-controls": ariaControls,
533
532
  "aria-haspopup": ariaHasPopUp,
534
- id
533
+ "aria-required": ariaRequired,
534
+ id,
535
+ onBlur
535
536
  } = this.props;
536
537
  const renderedChildren = this.props.children(_extends__default["default"]({}, eventState, {
537
538
  text,
@@ -540,16 +541,19 @@ class DropdownOpener extends React__namespace.Component {
540
541
  const childrenProps = renderedChildren.props;
541
542
  const childrenTestId = this.getTestIdFromProps(childrenProps);
542
543
  return React__namespace.cloneElement(renderedChildren, _extends__default["default"]({}, clickableChildrenProps, {
544
+ "aria-invalid": this.props.error,
543
545
  disabled,
544
546
  "aria-controls": ariaControls,
545
547
  id,
546
548
  "aria-expanded": opened ? "true" : "false",
547
549
  "aria-haspopup": ariaHasPopUp,
550
+ "aria-required": ariaRequired,
548
551
  onClick: childrenProps.onClick ? e => {
549
552
  childrenProps.onClick(e);
550
553
  clickableChildrenProps.onClick(e);
551
554
  } : clickableChildrenProps.onClick,
552
- "data-testid": childrenTestId || testId
555
+ "data-testid": childrenTestId || testId,
556
+ onBlur
553
557
  }));
554
558
  }
555
559
  render() {
@@ -887,39 +891,39 @@ class DropdownCore extends React__namespace.Component {
887
891
  open,
888
892
  searchText
889
893
  } = this.props;
890
- const keyCode = event.which || event.keyCode;
891
- if (enableTypeAhead && getStringForKey(event.key)) {
894
+ const key = event.key;
895
+ if (enableTypeAhead && getStringForKey(key)) {
892
896
  event.stopPropagation();
893
- this.textSuggestion += event.key;
897
+ this.textSuggestion += key;
894
898
  this.handleKeyDownDebounced(this.textSuggestion);
895
899
  }
896
900
  if (!open) {
897
- if (keyCode === keyCodes.down) {
901
+ if (key === keys.down) {
898
902
  event.preventDefault();
899
903
  onOpenChanged(true);
900
904
  return;
901
905
  }
902
906
  return;
903
907
  }
904
- switch (keyCode) {
905
- case keyCodes.tab:
908
+ switch (key) {
909
+ case keys.tab:
906
910
  if (this.isSearchFieldFocused() && searchText) {
907
911
  return;
908
912
  }
909
913
  this.restoreTabOrder();
910
914
  onOpenChanged(false);
911
915
  return;
912
- case keyCodes.space:
916
+ case keys.space:
913
917
  if (this.isSearchFieldFocused()) {
914
918
  return;
915
919
  }
916
920
  event.preventDefault();
917
921
  return;
918
- case keyCodes.up:
922
+ case keys.up:
919
923
  event.preventDefault();
920
924
  this.focusPreviousItem();
921
925
  return;
922
- case keyCodes.down:
926
+ case keys.down:
923
927
  event.preventDefault();
924
928
  this.focusNextItem();
925
929
  return;
@@ -930,15 +934,15 @@ class DropdownCore extends React__namespace.Component {
930
934
  onOpenChanged,
931
935
  open
932
936
  } = this.props;
933
- const keyCode = event.which || event.keyCode;
934
- switch (keyCode) {
935
- case keyCodes.space:
937
+ const key = event.key;
938
+ switch (key) {
939
+ case keys.space:
936
940
  if (this.isSearchFieldFocused()) {
937
941
  return;
938
942
  }
939
943
  event.preventDefault();
940
944
  return;
941
- case keyCodes.escape:
945
+ case keys.escape:
942
946
  if (open) {
943
947
  event.stopPropagation();
944
948
  this.restoreTabOrder();
@@ -1715,7 +1719,7 @@ const styles$5 = aphrodite.StyleSheet.create({
1715
1719
  }
1716
1720
  });
1717
1721
 
1718
- const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "light", "open", "testId", "onOpenChanged"];
1722
+ const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "light", "open", "testId", "aria-required", "onBlur", "onOpenChanged"];
1719
1723
  const StyledButton = wonderBlocksCore.addStyle("button");
1720
1724
  class SelectOpener extends React__namespace.Component {
1721
1725
  constructor(props) {
@@ -1758,7 +1762,9 @@ class SelectOpener extends React__namespace.Component {
1758
1762
  isPlaceholder,
1759
1763
  light,
1760
1764
  open,
1761
- testId
1765
+ testId,
1766
+ "aria-required": ariaRequired,
1767
+ onBlur
1762
1768
  } = _this$props,
1763
1769
  sharedProps = _objectWithoutPropertiesLoose__default["default"](_this$props, _excluded$2);
1764
1770
  const stateStyles = _generateStyles(light, isPlaceholder, error);
@@ -1767,6 +1773,8 @@ class SelectOpener extends React__namespace.Component {
1767
1773
  return React__namespace.createElement(StyledButton, _extends__default["default"]({}, sharedProps, {
1768
1774
  "aria-disabled": disabled,
1769
1775
  "aria-expanded": open ? "true" : "false",
1776
+ "aria-invalid": error,
1777
+ "aria-required": ariaRequired,
1770
1778
  "aria-haspopup": "listbox",
1771
1779
  "data-testid": testId,
1772
1780
  id: id,
@@ -1774,7 +1782,8 @@ class SelectOpener extends React__namespace.Component {
1774
1782
  type: "button",
1775
1783
  onClick: !disabled ? this.handleClick : undefined,
1776
1784
  onKeyDown: !disabled ? this.handleKeyDown : undefined,
1777
- onKeyUp: !disabled ? this.handleKeyUp : undefined
1785
+ onKeyUp: !disabled ? this.handleKeyUp : undefined,
1786
+ onBlur: onBlur
1778
1787
  }), React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
1779
1788
  style: styles$4.text
1780
1789
  }, children || "\u00A0"), React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
@@ -1923,117 +1932,206 @@ const _generateStyles = (light, placeholder, error) => {
1923
1932
  return stateStyles[styleKey];
1924
1933
  };
1925
1934
 
1926
- const _excluded$1 = ["children", "error", "id", "light", "opener", "placeholder", "selectedValue", "testId", "showOpenerLabelAsText", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required"];
1927
- class SingleSelect extends React__namespace.Component {
1928
- constructor(props) {
1929
- super(props);
1930
- this.selectedIndex = void 0;
1931
- this.handleOpenChanged = opened => {
1932
- this.setState({
1933
- open: opened,
1934
- searchText: ""
1935
- });
1936
- if (this.props.onToggle) {
1937
- this.props.onToggle(opened);
1935
+ const defaultErrorMessage = "This field is required.";
1936
+ function hasValue(value) {
1937
+ return value ? value.length > 0 : false;
1938
+ }
1939
+ function useSelectValidation({
1940
+ value,
1941
+ disabled = false,
1942
+ validate,
1943
+ onValidate,
1944
+ required,
1945
+ open
1946
+ }) {
1947
+ const [errorMessage, setErrorMessage] = React__namespace.useState(() => validate && hasValue(value) && !disabled && validate(value) || null);
1948
+ const handleValidation = React__namespace.useCallback(newValue => {
1949
+ if (disabled) {
1950
+ return;
1951
+ }
1952
+ if (validate) {
1953
+ const error = newValue !== undefined && validate(newValue) || null;
1954
+ setErrorMessage(error);
1955
+ if (onValidate) {
1956
+ onValidate(error);
1938
1957
  }
1939
- };
1940
- this.handleToggle = selectedValue => {
1941
- if (selectedValue !== this.props.selectedValue) {
1942
- this.props.onChange(selectedValue);
1958
+ if (error) {
1959
+ return;
1943
1960
  }
1944
- if (this.state.open && this.state.openerElement) {
1945
- this.state.openerElement.focus();
1961
+ }
1962
+ if (required) {
1963
+ const requiredString = typeof required === "string" ? required : defaultErrorMessage;
1964
+ const error = hasValue(newValue) ? null : requiredString;
1965
+ setErrorMessage(error);
1966
+ if (onValidate) {
1967
+ onValidate(error);
1946
1968
  }
1947
- this.setState({
1948
- open: false
1949
- });
1950
- if (this.props.onToggle) {
1951
- this.props.onToggle(false);
1969
+ }
1970
+ }, [disabled, validate, setErrorMessage, onValidate, required]);
1971
+ wonderBlocksCore.useOnMountEffect(() => {
1972
+ if (hasValue(value)) {
1973
+ handleValidation(value);
1974
+ }
1975
+ });
1976
+ function onOpenerBlurValidation() {
1977
+ if (!open && required && !hasValue(value)) {
1978
+ handleValidation(value);
1979
+ }
1980
+ }
1981
+ const onDropdownClosedValidation = () => {
1982
+ if (required && !hasValue(value)) {
1983
+ handleValidation(value);
1984
+ }
1985
+ };
1986
+ const onSelectionValidation = newValue => {
1987
+ handleValidation(newValue);
1988
+ };
1989
+ const onSelectedValuesChangeValidation = () => {
1990
+ setErrorMessage(null);
1991
+ if (onValidate) {
1992
+ onValidate(null);
1993
+ }
1994
+ };
1995
+ return {
1996
+ errorMessage,
1997
+ onOpenerBlurValidation,
1998
+ onDropdownClosedValidation,
1999
+ onSelectionValidation,
2000
+ onSelectedValuesChangeValidation
2001
+ };
2002
+ }
2003
+
2004
+ const _excluded$1 = ["children", "error", "id", "opener", "light", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required", "disabled", "dropdownId", "validate", "onValidate", "required", "showOpenerLabelAsText"];
2005
+ const SingleSelect = props => {
2006
+ const selectedIndex = React__namespace.useRef(0);
2007
+ const {
2008
+ children,
2009
+ error = false,
2010
+ id,
2011
+ opener,
2012
+ light = false,
2013
+ placeholder,
2014
+ selectedValue,
2015
+ testId,
2016
+ alignment = "left",
2017
+ autoFocus = true,
2018
+ dropdownStyle,
2019
+ enableTypeAhead = true,
2020
+ isFilterable,
2021
+ labels = {
2022
+ clearSearch: defaultLabels.clearSearch,
2023
+ filter: defaultLabels.filter,
2024
+ noResults: defaultLabels.noResults,
2025
+ someResults: defaultLabels.someSelected
2026
+ },
2027
+ onChange,
2028
+ onToggle,
2029
+ opened,
2030
+ style,
2031
+ className,
2032
+ "aria-invalid": ariaInvalid,
2033
+ "aria-required": ariaRequired,
2034
+ disabled = false,
2035
+ dropdownId,
2036
+ validate,
2037
+ onValidate,
2038
+ required,
2039
+ showOpenerLabelAsText = true
2040
+ } = props,
2041
+ sharedProps = _objectWithoutPropertiesLoose__default["default"](props, _excluded$1);
2042
+ const [open, setOpen] = React__namespace.useState(false);
2043
+ const [searchText, setSearchText] = React__namespace.useState("");
2044
+ const [openerElement, setOpenerElement] = React__namespace.useState();
2045
+ const {
2046
+ errorMessage,
2047
+ onOpenerBlurValidation,
2048
+ onDropdownClosedValidation,
2049
+ onSelectionValidation
2050
+ } = useSelectValidation({
2051
+ value: selectedValue,
2052
+ disabled,
2053
+ validate,
2054
+ onValidate,
2055
+ required,
2056
+ open
2057
+ });
2058
+ const hasError = error || !!errorMessage;
2059
+ React__namespace.useEffect(() => {
2060
+ if (disabled) {
2061
+ setOpen(false);
2062
+ } else if (typeof opened === "boolean") {
2063
+ setOpen(opened);
2064
+ }
2065
+ }, [disabled, opened]);
2066
+ const handleOpenChanged = opened => {
2067
+ setOpen(opened);
2068
+ setSearchText("");
2069
+ if (onToggle) {
2070
+ onToggle(opened);
2071
+ }
2072
+ if (!opened) {
2073
+ onDropdownClosedValidation();
2074
+ }
2075
+ };
2076
+ const handleToggle = newSelectedValue => {
2077
+ if (newSelectedValue !== selectedValue) {
2078
+ onChange(newSelectedValue);
2079
+ }
2080
+ if (open && openerElement) {
2081
+ openerElement.focus();
2082
+ }
2083
+ setOpen(false);
2084
+ if (onToggle) {
2085
+ onToggle(false);
2086
+ }
2087
+ onSelectionValidation(newSelectedValue);
2088
+ };
2089
+ const mapOptionItemsToDropdownItems = children => {
2090
+ let indexCounter = 0;
2091
+ selectedIndex.current = 0;
2092
+ return children.map(option => {
2093
+ const {
2094
+ disabled,
2095
+ value
2096
+ } = option.props;
2097
+ const selected = selectedValue === value;
2098
+ if (selected) {
2099
+ selectedIndex.current = indexCounter;
1952
2100
  }
1953
- };
1954
- this.mapOptionItemsToDropdownItems = children => {
1955
- let indexCounter = 0;
1956
- this.selectedIndex = 0;
1957
- return children.map(option => {
1958
- const {
1959
- selectedValue
1960
- } = this.props;
1961
- const {
1962
- disabled,
1963
- value
1964
- } = option.props;
1965
- const selected = selectedValue === value;
1966
- if (selected) {
1967
- this.selectedIndex = indexCounter;
1968
- }
1969
- if (!disabled) {
1970
- indexCounter += 1;
2101
+ if (!disabled) {
2102
+ indexCounter += 1;
2103
+ }
2104
+ return {
2105
+ component: option,
2106
+ focusable: !disabled,
2107
+ populatedProps: {
2108
+ onToggle: handleToggle,
2109
+ selected: selected,
2110
+ variant: "check"
1971
2111
  }
1972
- return {
1973
- component: option,
1974
- focusable: !disabled,
1975
- populatedProps: {
1976
- onToggle: this.handleToggle,
1977
- selected: selected,
1978
- variant: "check"
1979
- }
1980
- };
1981
- });
1982
- };
1983
- this.handleSearchTextChanged = searchText => {
1984
- this.setState({
1985
- searchText
1986
- });
1987
- };
1988
- this.handleOpenerRef = node => {
1989
- const openerElement = ReactDOM__namespace.findDOMNode(node);
1990
- this.setState({
1991
- openerElement
1992
- });
1993
- };
1994
- this.handleClick = e => {
1995
- this.handleOpenChanged(!this.state.open);
1996
- };
1997
- this.selectedIndex = 0;
1998
- this.state = {
1999
- open: false,
2000
- searchText: ""
2001
- };
2002
- }
2003
- static getDerivedStateFromProps(props, state) {
2004
- return {
2005
- open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
2006
- };
2007
- }
2008
- filterChildren(children) {
2009
- const {
2010
- searchText
2011
- } = this.state;
2112
+ };
2113
+ });
2114
+ };
2115
+ const filterChildren = children => {
2012
2116
  const lowercasedSearchText = searchText.toLowerCase();
2013
2117
  return children.filter(({
2014
2118
  props
2015
2119
  }) => !searchText || getLabel(props).toLowerCase().indexOf(lowercasedSearchText) > -1);
2016
- }
2017
- getMenuItems(children) {
2018
- const {
2019
- isFilterable
2020
- } = this.props;
2021
- return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
2022
- }
2023
- renderOpener(isDisabled, dropdownId) {
2024
- const _this$props = this.props,
2025
- {
2026
- children,
2027
- error,
2028
- id,
2029
- light,
2030
- opener,
2031
- placeholder,
2032
- selectedValue,
2033
- testId,
2034
- showOpenerLabelAsText
2035
- } = _this$props,
2036
- sharedProps = _objectWithoutPropertiesLoose__default["default"](_this$props, _excluded$1);
2120
+ };
2121
+ const getMenuItems = children => {
2122
+ return mapOptionItemsToDropdownItems(isFilterable ? filterChildren(children) : children);
2123
+ };
2124
+ const handleSearchTextChanged = searchText => {
2125
+ setSearchText(searchText);
2126
+ };
2127
+ const handleOpenerRef = node => {
2128
+ const openerElement = ReactDOM__namespace.findDOMNode(node);
2129
+ setOpenerElement(openerElement);
2130
+ };
2131
+ const handleClick = e => {
2132
+ handleOpenChanged(!open);
2133
+ };
2134
+ const renderOpener = (isDisabled, dropdownId) => {
2037
2135
  const items = React__namespace.Children.toArray(children);
2038
2136
  const selectedItem = items.find(option => option.props.value === selectedValue);
2039
2137
  const menuText = selectedItem ? getSelectOpenerLabel(showOpenerLabelAsText, selectedItem.props) : placeholder;
@@ -2045,202 +2143,160 @@ class SingleSelect extends React__namespace.Component {
2045
2143
  id: uniqueOpenerId,
2046
2144
  "aria-controls": dropdownId,
2047
2145
  "aria-haspopup": "listbox",
2048
- onClick: this.handleClick,
2146
+ onClick: handleClick,
2049
2147
  disabled: isDisabled,
2050
- ref: this.handleOpenerRef,
2148
+ ref: handleOpenerRef,
2051
2149
  text: menuText,
2052
- opened: this.state.open
2150
+ opened: open,
2151
+ error: hasError,
2152
+ onBlur: onOpenerBlurValidation
2053
2153
  }, opener) : React__namespace.createElement(SelectOpener, _extends__default["default"]({}, sharedProps, {
2054
2154
  "aria-controls": dropdownId,
2055
2155
  disabled: isDisabled,
2056
2156
  id: uniqueOpenerId,
2057
- error: error,
2157
+ error: hasError,
2058
2158
  isPlaceholder: !selectedItem,
2059
2159
  light: light,
2060
- onOpenChanged: this.handleOpenChanged,
2061
- open: this.state.open,
2062
- ref: this.handleOpenerRef,
2063
- testId: testId
2160
+ onOpenChanged: handleOpenChanged,
2161
+ open: open,
2162
+ ref: handleOpenerRef,
2163
+ testId: testId,
2164
+ onBlur: onOpenerBlurValidation
2064
2165
  }), menuText);
2065
2166
  });
2066
2167
  return dropdownOpener;
2067
- }
2068
- render() {
2069
- const {
2070
- alignment,
2071
- autoFocus,
2072
- children,
2073
- className,
2168
+ };
2169
+ const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
2170
+ const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
2171
+ const items = getMenuItems(allChildren);
2172
+ const isDisabled = numEnabledOptions === 0 || disabled;
2173
+ return React__namespace.createElement(wonderBlocksCore.IDProvider, {
2174
+ id: dropdownId,
2175
+ scope: "single-select-dropdown"
2176
+ }, uniqueDropdownId => React__namespace.createElement(DropdownCore$1, {
2177
+ id: uniqueDropdownId,
2178
+ role: "listbox",
2179
+ selectionType: "single",
2180
+ alignment: alignment,
2181
+ autoFocus: autoFocus,
2182
+ enableTypeAhead: enableTypeAhead,
2183
+ dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
2184
+ initialFocusedIndex: selectedIndex.current,
2185
+ items: items,
2186
+ light: light,
2187
+ onOpenChanged: handleOpenChanged,
2188
+ open: open,
2189
+ opener: renderOpener(isDisabled, uniqueDropdownId),
2190
+ openerElement: openerElement,
2191
+ style: style,
2192
+ className: className,
2193
+ isFilterable: isFilterable,
2194
+ onSearchTextChanged: isFilterable ? handleSearchTextChanged : undefined,
2195
+ searchText: isFilterable ? searchText : "",
2196
+ labels: labels,
2197
+ "aria-invalid": ariaInvalid,
2198
+ "aria-required": ariaRequired,
2199
+ disabled: isDisabled
2200
+ }));
2201
+ };
2202
+
2203
+ const _excluded = ["id", "light", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-invalid", "aria-required", "disabled", "error", "children", "dropdownId", "showOpenerLabelAsText", "validate", "onValidate", "required"];
2204
+ const MultiSelect = props => {
2205
+ const {
2206
+ id,
2207
+ light = false,
2208
+ opener,
2209
+ testId,
2210
+ alignment = "left",
2074
2211
  dropdownStyle,
2075
- enableTypeAhead,
2212
+ implicitAllEnabled,
2076
2213
  isFilterable,
2077
- labels,
2078
- light,
2214
+ labels: propLabels,
2215
+ onChange,
2216
+ onToggle,
2217
+ opened,
2218
+ selectedValues = [],
2219
+ shortcuts = false,
2079
2220
  style,
2221
+ className,
2080
2222
  "aria-invalid": ariaInvalid,
2081
2223
  "aria-required": ariaRequired,
2082
- disabled,
2083
- dropdownId
2084
- } = this.props;
2085
- const {
2086
- searchText
2087
- } = this.state;
2088
- const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
2089
- const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
2090
- const items = this.getMenuItems(allChildren);
2091
- const isDisabled = numEnabledOptions === 0 || disabled;
2092
- return React__namespace.createElement(wonderBlocksCore.IDProvider, {
2093
- id: dropdownId,
2094
- scope: "single-select-dropdown"
2095
- }, uniqueDropdownId => React__namespace.createElement(DropdownCore$1, {
2096
- id: uniqueDropdownId,
2097
- role: "listbox",
2098
- selectionType: "single",
2099
- alignment: alignment,
2100
- autoFocus: autoFocus,
2101
- enableTypeAhead: enableTypeAhead,
2102
- dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
2103
- initialFocusedIndex: this.selectedIndex,
2104
- items: items,
2105
- light: light,
2106
- onOpenChanged: this.handleOpenChanged,
2107
- open: this.state.open,
2108
- opener: this.renderOpener(isDisabled, uniqueDropdownId),
2109
- openerElement: this.state.openerElement,
2110
- style: style,
2111
- className: className,
2112
- isFilterable: isFilterable,
2113
- onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : undefined,
2114
- searchText: isFilterable ? searchText : "",
2115
- labels: labels,
2116
- "aria-invalid": ariaInvalid,
2117
- "aria-required": ariaRequired,
2118
- disabled: isDisabled
2119
- }));
2120
- }
2121
- }
2122
- SingleSelect.defaultProps = {
2123
- alignment: "left",
2124
- autoFocus: true,
2125
- disabled: false,
2126
- enableTypeAhead: true,
2127
- error: false,
2128
- light: false,
2129
- labels: {
2130
- clearSearch: defaultLabels.clearSearch,
2131
- filter: defaultLabels.filter,
2132
- noResults: defaultLabels.noResults,
2133
- someResults: defaultLabels.someSelected
2134
- },
2135
- showOpenerLabelAsText: true
2136
- };
2137
-
2138
- const _excluded = ["id", "light", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-invalid", "aria-required", "showOpenerLabelAsText"];
2139
- class MultiSelect extends React__namespace.Component {
2140
- constructor(props) {
2141
- super(props);
2142
- this.labels = void 0;
2143
- this.handleOpenChanged = opened => {
2144
- this.setState({
2145
- open: opened,
2146
- searchText: "",
2147
- lastSelectedValues: this.props.selectedValues
2148
- });
2149
- if (this.props.onToggle) {
2150
- this.props.onToggle(opened);
2151
- }
2152
- };
2153
- this.handleToggle = selectedValue => {
2154
- const {
2155
- onChange,
2156
- selectedValues
2157
- } = this.props;
2158
- if (selectedValues.includes(selectedValue)) {
2159
- const index = selectedValues.indexOf(selectedValue);
2160
- const updatedSelection = [...selectedValues.slice(0, index), ...selectedValues.slice(index + 1)];
2161
- onChange(updatedSelection);
2224
+ disabled = false,
2225
+ error = false,
2226
+ children,
2227
+ dropdownId,
2228
+ showOpenerLabelAsText = true,
2229
+ validate,
2230
+ onValidate,
2231
+ required
2232
+ } = props,
2233
+ sharedProps = _objectWithoutPropertiesLoose__default["default"](props, _excluded);
2234
+ const labels = _extends__default["default"]({}, defaultLabels, propLabels);
2235
+ const [open, setOpen] = React__namespace.useState(false);
2236
+ const [searchText, setSearchText] = React__namespace.useState("");
2237
+ const [lastSelectedValues, setLastSelectedValues] = React__namespace.useState([]);
2238
+ const [openerElement, setOpenerElement] = React__namespace.useState();
2239
+ const {
2240
+ errorMessage,
2241
+ onOpenerBlurValidation,
2242
+ onDropdownClosedValidation,
2243
+ onSelectionValidation,
2244
+ onSelectedValuesChangeValidation
2245
+ } = useSelectValidation({
2246
+ value: selectedValues,
2247
+ disabled,
2248
+ validate,
2249
+ onValidate,
2250
+ required,
2251
+ open
2252
+ });
2253
+ const hasError = error || !!errorMessage;
2254
+ React__namespace.useEffect(() => {
2255
+ if (disabled) {
2256
+ setOpen(false);
2257
+ } else if (typeof opened === "boolean") {
2258
+ setOpen(opened);
2259
+ }
2260
+ }, [disabled, opened]);
2261
+ const handleOpenChanged = opened => {
2262
+ setOpen(opened);
2263
+ setSearchText("");
2264
+ setLastSelectedValues(selectedValues);
2265
+ if (onToggle) {
2266
+ onToggle(opened);
2267
+ }
2268
+ if (!opened) {
2269
+ if (lastSelectedValues !== selectedValues) {
2270
+ onSelectionValidation(selectedValues);
2162
2271
  } else {
2163
- onChange([...selectedValues, selectedValue]);
2272
+ onDropdownClosedValidation();
2164
2273
  }
2165
- };
2166
- this.handleSelectAll = () => {
2167
- const {
2168
- children,
2169
- onChange
2170
- } = this.props;
2171
- const allChildren = React__namespace.Children.toArray(children);
2172
- const selected = allChildren.filter(option => !!option && !option.props.disabled).map(option => option.props.value);
2173
- onChange(selected);
2174
- };
2175
- this.handleSelectNone = () => {
2176
- const {
2177
- onChange
2178
- } = this.props;
2179
- onChange([]);
2180
- };
2181
- this.mapOptionItemToDropdownItem = option => {
2182
- const {
2183
- selectedValues
2184
- } = this.props;
2185
- const {
2186
- disabled,
2187
- value
2188
- } = option.props;
2189
- return {
2190
- component: option,
2191
- focusable: !disabled,
2192
- populatedProps: {
2193
- onToggle: this.handleToggle,
2194
- selected: selectedValues.includes(value),
2195
- variant: "checkbox"
2196
- }
2197
- };
2198
- };
2199
- this.handleOpenerRef = node => {
2200
- const openerElement = ReactDOM__namespace.findDOMNode(node);
2201
- this.setState({
2202
- openerElement
2203
- });
2204
- };
2205
- this.handleSearchTextChanged = searchText => {
2206
- this.setState({
2207
- searchText
2208
- });
2209
- };
2210
- this.handleClick = e => {
2211
- this.handleOpenChanged(!this.state.open);
2212
- };
2213
- this.state = {
2214
- open: false,
2215
- searchText: "",
2216
- lastSelectedValues: [],
2217
- labels: _extends__default["default"]({}, defaultLabels, props.labels)
2218
- };
2219
- this.labels = _extends__default["default"]({}, defaultLabels, props.labels);
2220
- }
2221
- static getDerivedStateFromProps(props, state) {
2222
- return {
2223
- open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
2224
- };
2225
- }
2226
- componentDidUpdate(prevProps) {
2227
- if (this.props.labels !== prevProps.labels) {
2228
- this.setState({
2229
- labels: _extends__default["default"]({}, this.state.labels, this.props.labels)
2230
- });
2231
2274
  }
2232
- }
2233
- getMenuText(children) {
2234
- const {
2235
- implicitAllEnabled,
2236
- selectedValues,
2237
- showOpenerLabelAsText
2238
- } = this.props;
2275
+ };
2276
+ const handleToggle = selectedValue => {
2277
+ if (selectedValues.includes(selectedValue)) {
2278
+ const index = selectedValues.indexOf(selectedValue);
2279
+ const updatedSelection = [...selectedValues.slice(0, index), ...selectedValues.slice(index + 1)];
2280
+ onChange(updatedSelection);
2281
+ } else {
2282
+ onChange([...selectedValues, selectedValue]);
2283
+ }
2284
+ onSelectedValuesChangeValidation();
2285
+ };
2286
+ const handleSelectAll = () => {
2287
+ const allChildren = React__namespace.Children.toArray(children);
2288
+ const selected = allChildren.filter(option => !!option && !option.props.disabled).map(option => option.props.value);
2289
+ onChange(selected);
2290
+ };
2291
+ const handleSelectNone = () => {
2292
+ onChange([]);
2293
+ };
2294
+ const getMenuText = children => {
2239
2295
  const {
2240
2296
  noneSelected,
2241
2297
  someSelected,
2242
2298
  allSelected
2243
- } = this.state.labels;
2299
+ } = labels;
2244
2300
  const numSelectedAll = children.filter(option => !option.props.disabled).length;
2245
2301
  const noSelectionText = implicitAllEnabled ? allSelected : noneSelected;
2246
2302
  switch (selectedValues.length) {
@@ -2262,24 +2318,20 @@ class MultiSelect extends React__namespace.Component {
2262
2318
  default:
2263
2319
  return someSelected(selectedValues.length);
2264
2320
  }
2265
- }
2266
- getShortcuts(numOptions) {
2267
- const {
2268
- selectedValues,
2269
- shortcuts
2270
- } = this.props;
2321
+ };
2322
+ const getShortcuts = numOptions => {
2271
2323
  const {
2272
2324
  selectAllLabel,
2273
2325
  selectNoneLabel
2274
- } = this.state.labels;
2275
- if (shortcuts && !this.state.searchText) {
2326
+ } = labels;
2327
+ if (shortcuts && !searchText) {
2276
2328
  const selectAllDisabled = numOptions === selectedValues.length;
2277
2329
  const selectAll = {
2278
2330
  component: React__namespace.createElement(ActionItem, {
2279
2331
  disabled: selectAllDisabled,
2280
2332
  label: selectAllLabel(numOptions),
2281
2333
  indent: true,
2282
- onClick: this.handleSelectAll
2334
+ onClick: handleSelectAll
2283
2335
  }),
2284
2336
  focusable: !selectAllDisabled,
2285
2337
  populatedProps: {}
@@ -2290,7 +2342,7 @@ class MultiSelect extends React__namespace.Component {
2290
2342
  disabled: selectNoneDisabled,
2291
2343
  label: selectNoneLabel,
2292
2344
  indent: true,
2293
- onClick: this.handleSelectNone
2345
+ onClick: handleSelectNone
2294
2346
  }),
2295
2347
  focusable: !selectNoneDisabled,
2296
2348
  populatedProps: {}
@@ -2306,18 +2358,11 @@ class MultiSelect extends React__namespace.Component {
2306
2358
  } else {
2307
2359
  return [];
2308
2360
  }
2309
- }
2310
- getMenuItems(children) {
2311
- const {
2312
- isFilterable
2313
- } = this.props;
2361
+ };
2362
+ const getMenuItems = children => {
2314
2363
  if (!isFilterable) {
2315
- return children.map(this.mapOptionItemToDropdownItem);
2364
+ return children.map(mapOptionItemToDropdownItem);
2316
2365
  }
2317
- const {
2318
- searchText,
2319
- lastSelectedValues
2320
- } = this.state;
2321
2366
  const lowercasedSearchText = searchText.toLowerCase();
2322
2367
  const filteredChildren = children.filter(({
2323
2368
  props
@@ -2331,7 +2376,7 @@ class MultiSelect extends React__namespace.Component {
2331
2376
  restOfTheChildren.push(child);
2332
2377
  }
2333
2378
  }
2334
- const lastSelectedItems = lastSelectedChildren.map(this.mapOptionItemToDropdownItem);
2379
+ const lastSelectedItems = lastSelectedChildren.map(mapOptionItemToDropdownItem);
2335
2380
  if (lastSelectedChildren.length && restOfTheChildren.length) {
2336
2381
  lastSelectedItems.push({
2337
2382
  component: React__namespace.createElement(SeparatorItem, {
@@ -2341,116 +2386,109 @@ class MultiSelect extends React__namespace.Component {
2341
2386
  populatedProps: {}
2342
2387
  });
2343
2388
  }
2344
- return [...lastSelectedItems, ...restOfTheChildren.map(this.mapOptionItemToDropdownItem)];
2345
- }
2346
- renderOpener(allChildren, isDisabled, dropdownId) {
2347
- const _this$props = this.props,
2348
- {
2349
- id,
2350
- light,
2351
- opener,
2352
- testId
2353
- } = _this$props,
2354
- sharedProps = _objectWithoutPropertiesLoose__default["default"](_this$props, _excluded);
2389
+ return [...lastSelectedItems, ...restOfTheChildren.map(mapOptionItemToDropdownItem)];
2390
+ };
2391
+ const mapOptionItemToDropdownItem = option => {
2392
+ const {
2393
+ disabled,
2394
+ value
2395
+ } = option.props;
2396
+ return {
2397
+ component: option,
2398
+ focusable: !disabled,
2399
+ populatedProps: {
2400
+ onToggle: handleToggle,
2401
+ selected: selectedValues.includes(value),
2402
+ variant: "checkbox"
2403
+ }
2404
+ };
2405
+ };
2406
+ const handleOpenerRef = node => {
2407
+ const openerElement = ReactDOM__namespace.findDOMNode(node);
2408
+ setOpenerElement(openerElement);
2409
+ };
2410
+ const handleSearchTextChanged = searchText => {
2411
+ setSearchText(searchText);
2412
+ };
2413
+ const handleClick = e => {
2414
+ handleOpenChanged(!open);
2415
+ };
2416
+ const renderOpener = (allChildren, isDisabled, dropdownId) => {
2355
2417
  const {
2356
2418
  noneSelected
2357
- } = this.state.labels;
2358
- const menuText = this.getMenuText(allChildren);
2419
+ } = labels;
2420
+ const menuText = getMenuText(allChildren);
2359
2421
  const dropdownOpener = React__namespace.createElement(wonderBlocksCore.IDProvider, {
2360
2422
  id: id,
2361
2423
  scope: "multi-select-opener"
2362
2424
  }, uniqueOpenerId => {
2363
2425
  return opener ? React__namespace.createElement(DropdownOpener, {
2364
2426
  id: uniqueOpenerId,
2427
+ error: hasError,
2365
2428
  "aria-controls": dropdownId,
2366
2429
  "aria-haspopup": "listbox",
2367
- onClick: this.handleClick,
2430
+ onClick: handleClick,
2431
+ onBlur: onOpenerBlurValidation,
2368
2432
  disabled: isDisabled,
2369
- ref: this.handleOpenerRef,
2433
+ ref: handleOpenerRef,
2370
2434
  text: menuText,
2371
- opened: this.state.open
2435
+ opened: open
2372
2436
  }, opener) : React__namespace.createElement(SelectOpener, _extends__default["default"]({}, sharedProps, {
2437
+ error: hasError,
2373
2438
  disabled: isDisabled,
2374
2439
  id: uniqueOpenerId,
2375
2440
  "aria-controls": dropdownId,
2376
2441
  isPlaceholder: menuText === noneSelected,
2377
2442
  light: light,
2378
- onOpenChanged: this.handleOpenChanged,
2379
- open: this.state.open,
2380
- ref: this.handleOpenerRef,
2443
+ onOpenChanged: handleOpenChanged,
2444
+ onBlur: onOpenerBlurValidation,
2445
+ open: open,
2446
+ ref: handleOpenerRef,
2381
2447
  testId: testId
2382
2448
  }), menuText);
2383
2449
  });
2384
2450
  return dropdownOpener;
2385
- }
2386
- render() {
2387
- const {
2388
- alignment,
2389
- light,
2390
- style,
2391
- className,
2392
- dropdownStyle,
2393
- children,
2394
- isFilterable,
2395
- "aria-invalid": ariaInvalid,
2396
- "aria-required": ariaRequired,
2397
- disabled,
2398
- dropdownId
2399
- } = this.props;
2400
- const {
2401
- open,
2402
- searchText
2403
- } = this.state;
2404
- const {
2451
+ };
2452
+ const {
2453
+ clearSearch,
2454
+ filter,
2455
+ noResults,
2456
+ someSelected
2457
+ } = labels;
2458
+ const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
2459
+ const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
2460
+ const filteredItems = getMenuItems(allChildren);
2461
+ const isDisabled = numEnabledOptions === 0 || disabled;
2462
+ return React__namespace.createElement(wonderBlocksCore.IDProvider, {
2463
+ id: dropdownId,
2464
+ scope: "multi-select-dropdown"
2465
+ }, uniqueDropdownId => React__namespace.createElement(DropdownCore$1, {
2466
+ id: uniqueDropdownId,
2467
+ role: "listbox",
2468
+ alignment: alignment,
2469
+ dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
2470
+ isFilterable: isFilterable,
2471
+ items: [...getShortcuts(numEnabledOptions), ...filteredItems],
2472
+ light: light,
2473
+ onOpenChanged: handleOpenChanged,
2474
+ open: open,
2475
+ opener: renderOpener(allChildren, isDisabled, uniqueDropdownId),
2476
+ openerElement: openerElement,
2477
+ selectionType: "multi",
2478
+ style: style,
2479
+ className: className,
2480
+ onSearchTextChanged: isFilterable ? handleSearchTextChanged : undefined,
2481
+ searchText: isFilterable ? searchText : "",
2482
+ labels: {
2405
2483
  clearSearch,
2406
2484
  filter,
2407
2485
  noResults,
2408
- someSelected
2409
- } = this.state.labels;
2410
- const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
2411
- const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
2412
- const filteredItems = this.getMenuItems(allChildren);
2413
- const isDisabled = numEnabledOptions === 0 || disabled;
2414
- return React__namespace.createElement(wonderBlocksCore.IDProvider, {
2415
- id: dropdownId,
2416
- scope: "multi-select-dropdown"
2417
- }, uniqueDropdownId => React__namespace.createElement(DropdownCore$1, {
2418
- id: uniqueDropdownId,
2419
- role: "listbox",
2420
- alignment: alignment,
2421
- dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
2422
- isFilterable: isFilterable,
2423
- items: [...this.getShortcuts(numEnabledOptions), ...filteredItems],
2424
- light: light,
2425
- onOpenChanged: this.handleOpenChanged,
2426
- open: open,
2427
- opener: this.renderOpener(allChildren, isDisabled, uniqueDropdownId),
2428
- openerElement: this.state.openerElement,
2429
- selectionType: "multi",
2430
- style: style,
2431
- className: className,
2432
- onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : undefined,
2433
- searchText: isFilterable ? searchText : "",
2434
- labels: {
2435
- clearSearch,
2436
- filter,
2437
- noResults,
2438
- someResults: someSelected
2439
- },
2440
- "aria-invalid": ariaInvalid,
2441
- "aria-required": ariaRequired,
2442
- disabled: isDisabled
2443
- }));
2444
- }
2445
- }
2446
- MultiSelect.defaultProps = {
2447
- alignment: "left",
2448
- disabled: false,
2449
- error: false,
2450
- light: false,
2451
- shortcuts: false,
2452
- selectedValues: [],
2453
- showOpenerLabelAsText: true
2486
+ someResults: someSelected
2487
+ },
2488
+ "aria-invalid": ariaInvalid,
2489
+ "aria-required": ariaRequired,
2490
+ disabled: isDisabled
2491
+ }));
2454
2492
  };
2455
2493
 
2456
2494
  function updateMultipleSelection(previousSelection, value = "") {