@khanacademy/wonder-blocks-dropdown 5.3.6 → 5.3.8
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 +16 -0
- package/dist/components/dropdown-core.d.ts +4 -0
- package/dist/components/multi-select.d.ts +1 -1
- package/dist/components/single-select.d.ts +1 -1
- package/dist/es/index.js +27 -22
- package/dist/index.js +27 -22
- package/package.json +3 -3
- package/src/components/__tests__/dropdown-core.test.tsx +76 -0
- package/src/components/__tests__/multi-select.test.tsx +68 -1
- package/src/components/__tests__/select-opener.test.tsx +106 -1
- package/src/components/__tests__/single-select.test.tsx +81 -1
- package/src/components/dropdown-core.tsx +7 -3
- package/src/components/multi-select.tsx +13 -8
- package/src/components/single-select.tsx +21 -8
- package/tsconfig-build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-dropdown
|
|
2
2
|
|
|
3
|
+
## 5.3.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cb95286d: - SingleSelect and MultiSelect: Fixing bug where the components can be opened via keyboard when it is disabled because it has 0 items
|
|
8
|
+
- SingleSelect: If all option items are disabled, the SingleSelect is disabled, similar to the behaviour of the MultiSelect
|
|
9
|
+
- @khanacademy/wonder-blocks-modal@5.1.5
|
|
10
|
+
- @khanacademy/wonder-blocks-search-field@2.2.16
|
|
11
|
+
|
|
12
|
+
## 5.3.7
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- f7ff9a77: SingleSelect and MultiSelect: Disable keyboard interactions to open the select if the `disabled` prop is set to `true`. Prevent select from being in an open state if
|
|
17
|
+
`disabled` prop is `true`.
|
|
18
|
+
|
|
3
19
|
## 5.3.6
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -77,6 +77,10 @@ declare const _default: React.ComponentType<Readonly<{
|
|
|
77
77
|
* top. The items will be filtered by the input.
|
|
78
78
|
*/
|
|
79
79
|
isFilterable?: boolean | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the dropdown and it's interactions should be disabled.
|
|
82
|
+
*/
|
|
83
|
+
disabled?: boolean | undefined;
|
|
80
84
|
/**
|
|
81
85
|
* Whether this menu should be left-aligned or right-aligned with the
|
|
82
86
|
* opener component. Defaults to left-aligned.
|
|
@@ -196,7 +196,7 @@ export default class MultiSelect extends React.Component<Props, State> {
|
|
|
196
196
|
handleOpenerRef: (node?: any) => void;
|
|
197
197
|
handleSearchTextChanged: (searchText: string) => void;
|
|
198
198
|
handleClick: (e: React.SyntheticEvent) => void;
|
|
199
|
-
renderOpener(allChildren: React.ReactElement<React.ComponentProps<typeof OptionItem>>[]): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
|
|
199
|
+
renderOpener(allChildren: React.ReactElement<React.ComponentProps<typeof OptionItem>>[], isDisabled: boolean): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
|
|
200
200
|
render(): React.ReactNode;
|
|
201
201
|
}
|
|
202
202
|
export {};
|
|
@@ -204,7 +204,7 @@ export default class SingleSelect extends React.Component<Props, State> {
|
|
|
204
204
|
handleSearchTextChanged: (searchText: string) => void;
|
|
205
205
|
handleOpenerRef: (node?: any) => void;
|
|
206
206
|
handleClick: (e: React.SyntheticEvent) => void;
|
|
207
|
-
renderOpener(
|
|
207
|
+
renderOpener(isDisabled: boolean): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
|
|
208
208
|
render(): React.ReactNode;
|
|
209
209
|
}
|
|
210
210
|
export {};
|
package/dist/es/index.js
CHANGED
|
@@ -1274,11 +1274,12 @@ class DropdownCore extends React.Component {
|
|
|
1274
1274
|
open,
|
|
1275
1275
|
opener,
|
|
1276
1276
|
style,
|
|
1277
|
-
className
|
|
1277
|
+
className,
|
|
1278
|
+
disabled
|
|
1278
1279
|
} = this.props;
|
|
1279
1280
|
return React.createElement(View, {
|
|
1280
|
-
onKeyDown: this.handleKeyDown,
|
|
1281
|
-
onKeyUp: this.handleKeyUp,
|
|
1281
|
+
onKeyDown: !disabled ? this.handleKeyDown : undefined,
|
|
1282
|
+
onKeyUp: !disabled ? this.handleKeyUp : undefined,
|
|
1282
1283
|
style: [styles$4.menuWrapper, style],
|
|
1283
1284
|
className: className
|
|
1284
1285
|
}, this.renderLiveRegion(), opener, open && this.renderDropdown());
|
|
@@ -1834,7 +1835,7 @@ const _generateStyles = (light, placeholder, error) => {
|
|
|
1834
1835
|
return stateStyles[styleKey];
|
|
1835
1836
|
};
|
|
1836
1837
|
|
|
1837
|
-
const _excluded$1 = ["children", "
|
|
1838
|
+
const _excluded$1 = ["children", "error", "id", "light", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required"];
|
|
1838
1839
|
class SingleSelect extends React.Component {
|
|
1839
1840
|
constructor(props) {
|
|
1840
1841
|
super(props);
|
|
@@ -1913,7 +1914,7 @@ class SingleSelect extends React.Component {
|
|
|
1913
1914
|
}
|
|
1914
1915
|
static getDerivedStateFromProps(props, state) {
|
|
1915
1916
|
return {
|
|
1916
|
-
open: typeof props.opened === "boolean" ? props.opened : state.open
|
|
1917
|
+
open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
|
|
1917
1918
|
};
|
|
1918
1919
|
}
|
|
1919
1920
|
filterChildren(children) {
|
|
@@ -1931,11 +1932,10 @@ class SingleSelect extends React.Component {
|
|
|
1931
1932
|
} = this.props;
|
|
1932
1933
|
return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
|
|
1933
1934
|
}
|
|
1934
|
-
renderOpener(
|
|
1935
|
+
renderOpener(isDisabled) {
|
|
1935
1936
|
const _this$props = this.props,
|
|
1936
1937
|
{
|
|
1937
1938
|
children,
|
|
1938
|
-
disabled,
|
|
1939
1939
|
error,
|
|
1940
1940
|
id,
|
|
1941
1941
|
light,
|
|
@@ -1950,12 +1950,12 @@ class SingleSelect extends React.Component {
|
|
|
1950
1950
|
const menuText = selectedItem ? getLabel(selectedItem.props) : placeholder;
|
|
1951
1951
|
const dropdownOpener = opener ? React.createElement(DropdownOpener, {
|
|
1952
1952
|
onClick: this.handleClick,
|
|
1953
|
-
disabled:
|
|
1953
|
+
disabled: isDisabled,
|
|
1954
1954
|
ref: this.handleOpenerRef,
|
|
1955
1955
|
text: menuText,
|
|
1956
1956
|
opened: this.state.open
|
|
1957
1957
|
}, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
|
|
1958
|
-
disabled:
|
|
1958
|
+
disabled: isDisabled,
|
|
1959
1959
|
id: id,
|
|
1960
1960
|
error: error,
|
|
1961
1961
|
isPlaceholder: !selectedItem,
|
|
@@ -1980,14 +1980,17 @@ class SingleSelect extends React.Component {
|
|
|
1980
1980
|
light,
|
|
1981
1981
|
style,
|
|
1982
1982
|
"aria-invalid": ariaInvalid,
|
|
1983
|
-
"aria-required": ariaRequired
|
|
1983
|
+
"aria-required": ariaRequired,
|
|
1984
|
+
disabled
|
|
1984
1985
|
} = this.props;
|
|
1985
1986
|
const {
|
|
1986
1987
|
searchText
|
|
1987
1988
|
} = this.state;
|
|
1988
1989
|
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
1990
|
+
const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
1989
1991
|
const items = this.getMenuItems(allChildren);
|
|
1990
|
-
const
|
|
1992
|
+
const isDisabled = numEnabledOptions === 0 || disabled;
|
|
1993
|
+
const opener = this.renderOpener(isDisabled);
|
|
1991
1994
|
return React.createElement(DropdownCore$1, {
|
|
1992
1995
|
role: "listbox",
|
|
1993
1996
|
selectionType: "single",
|
|
@@ -2009,7 +2012,8 @@ class SingleSelect extends React.Component {
|
|
|
2009
2012
|
searchText: isFilterable ? searchText : "",
|
|
2010
2013
|
labels: labels,
|
|
2011
2014
|
"aria-invalid": ariaInvalid,
|
|
2012
|
-
"aria-required": ariaRequired
|
|
2015
|
+
"aria-required": ariaRequired,
|
|
2016
|
+
disabled: isDisabled
|
|
2013
2017
|
});
|
|
2014
2018
|
}
|
|
2015
2019
|
}
|
|
@@ -2028,7 +2032,7 @@ SingleSelect.defaultProps = {
|
|
|
2028
2032
|
}
|
|
2029
2033
|
};
|
|
2030
2034
|
|
|
2031
|
-
const _excluded = ["
|
|
2035
|
+
const _excluded = ["id", "light", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-invalid", "aria-required"];
|
|
2032
2036
|
class MultiSelect extends React.Component {
|
|
2033
2037
|
constructor(props) {
|
|
2034
2038
|
super(props);
|
|
@@ -2113,7 +2117,7 @@ class MultiSelect extends React.Component {
|
|
|
2113
2117
|
}
|
|
2114
2118
|
static getDerivedStateFromProps(props, state) {
|
|
2115
2119
|
return {
|
|
2116
|
-
open: typeof props.opened === "boolean" ? props.opened : state.open
|
|
2120
|
+
open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
|
|
2117
2121
|
};
|
|
2118
2122
|
}
|
|
2119
2123
|
componentDidUpdate(prevProps) {
|
|
@@ -2235,10 +2239,9 @@ class MultiSelect extends React.Component {
|
|
|
2235
2239
|
}
|
|
2236
2240
|
return [...lastSelectedItems, ...restOfTheChildren.map(this.mapOptionItemToDropdownItem)];
|
|
2237
2241
|
}
|
|
2238
|
-
renderOpener(allChildren) {
|
|
2242
|
+
renderOpener(allChildren, isDisabled) {
|
|
2239
2243
|
const _this$props = this.props,
|
|
2240
2244
|
{
|
|
2241
|
-
disabled,
|
|
2242
2245
|
id,
|
|
2243
2246
|
light,
|
|
2244
2247
|
opener,
|
|
@@ -2249,15 +2252,14 @@ class MultiSelect extends React.Component {
|
|
|
2249
2252
|
noneSelected
|
|
2250
2253
|
} = this.state.labels;
|
|
2251
2254
|
const menuText = this.getMenuText(allChildren);
|
|
2252
|
-
const numOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
2253
2255
|
const dropdownOpener = opener ? React.createElement(DropdownOpener, {
|
|
2254
2256
|
onClick: this.handleClick,
|
|
2255
|
-
disabled:
|
|
2257
|
+
disabled: isDisabled,
|
|
2256
2258
|
ref: this.handleOpenerRef,
|
|
2257
2259
|
text: menuText,
|
|
2258
2260
|
opened: this.state.open
|
|
2259
2261
|
}, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
|
|
2260
|
-
disabled:
|
|
2262
|
+
disabled: isDisabled,
|
|
2261
2263
|
id: id,
|
|
2262
2264
|
isPlaceholder: menuText === noneSelected,
|
|
2263
2265
|
light: light,
|
|
@@ -2278,7 +2280,8 @@ class MultiSelect extends React.Component {
|
|
|
2278
2280
|
children,
|
|
2279
2281
|
isFilterable,
|
|
2280
2282
|
"aria-invalid": ariaInvalid,
|
|
2281
|
-
"aria-required": ariaRequired
|
|
2283
|
+
"aria-required": ariaRequired,
|
|
2284
|
+
disabled
|
|
2282
2285
|
} = this.props;
|
|
2283
2286
|
const {
|
|
2284
2287
|
open,
|
|
@@ -2293,7 +2296,8 @@ class MultiSelect extends React.Component {
|
|
|
2293
2296
|
const allChildren = React.Children.toArray(children).filter(Boolean);
|
|
2294
2297
|
const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
2295
2298
|
const filteredItems = this.getMenuItems(allChildren);
|
|
2296
|
-
const
|
|
2299
|
+
const isDisabled = numEnabledOptions === 0 || disabled;
|
|
2300
|
+
const opener = this.renderOpener(allChildren, isDisabled);
|
|
2297
2301
|
return React.createElement(DropdownCore$1, {
|
|
2298
2302
|
role: "listbox",
|
|
2299
2303
|
alignment: alignment,
|
|
@@ -2317,7 +2321,8 @@ class MultiSelect extends React.Component {
|
|
|
2317
2321
|
someResults: someSelected
|
|
2318
2322
|
},
|
|
2319
2323
|
"aria-invalid": ariaInvalid,
|
|
2320
|
-
"aria-required": ariaRequired
|
|
2324
|
+
"aria-required": ariaRequired,
|
|
2325
|
+
disabled: isDisabled
|
|
2321
2326
|
});
|
|
2322
2327
|
}
|
|
2323
2328
|
}
|
package/dist/index.js
CHANGED
|
@@ -1304,11 +1304,12 @@ class DropdownCore extends React__namespace.Component {
|
|
|
1304
1304
|
open,
|
|
1305
1305
|
opener,
|
|
1306
1306
|
style,
|
|
1307
|
-
className
|
|
1307
|
+
className,
|
|
1308
|
+
disabled
|
|
1308
1309
|
} = this.props;
|
|
1309
1310
|
return React__namespace.createElement(wonderBlocksCore.View, {
|
|
1310
|
-
onKeyDown: this.handleKeyDown,
|
|
1311
|
-
onKeyUp: this.handleKeyUp,
|
|
1311
|
+
onKeyDown: !disabled ? this.handleKeyDown : undefined,
|
|
1312
|
+
onKeyUp: !disabled ? this.handleKeyUp : undefined,
|
|
1312
1313
|
style: [styles$4.menuWrapper, style],
|
|
1313
1314
|
className: className
|
|
1314
1315
|
}, this.renderLiveRegion(), opener, open && this.renderDropdown());
|
|
@@ -1864,7 +1865,7 @@ const _generateStyles = (light, placeholder, error) => {
|
|
|
1864
1865
|
return stateStyles[styleKey];
|
|
1865
1866
|
};
|
|
1866
1867
|
|
|
1867
|
-
const _excluded$1 = ["children", "
|
|
1868
|
+
const _excluded$1 = ["children", "error", "id", "light", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required"];
|
|
1868
1869
|
class SingleSelect extends React__namespace.Component {
|
|
1869
1870
|
constructor(props) {
|
|
1870
1871
|
super(props);
|
|
@@ -1943,7 +1944,7 @@ class SingleSelect extends React__namespace.Component {
|
|
|
1943
1944
|
}
|
|
1944
1945
|
static getDerivedStateFromProps(props, state) {
|
|
1945
1946
|
return {
|
|
1946
|
-
open: typeof props.opened === "boolean" ? props.opened : state.open
|
|
1947
|
+
open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
|
|
1947
1948
|
};
|
|
1948
1949
|
}
|
|
1949
1950
|
filterChildren(children) {
|
|
@@ -1961,11 +1962,10 @@ class SingleSelect extends React__namespace.Component {
|
|
|
1961
1962
|
} = this.props;
|
|
1962
1963
|
return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
|
|
1963
1964
|
}
|
|
1964
|
-
renderOpener(
|
|
1965
|
+
renderOpener(isDisabled) {
|
|
1965
1966
|
const _this$props = this.props,
|
|
1966
1967
|
{
|
|
1967
1968
|
children,
|
|
1968
|
-
disabled,
|
|
1969
1969
|
error,
|
|
1970
1970
|
id,
|
|
1971
1971
|
light,
|
|
@@ -1980,12 +1980,12 @@ class SingleSelect extends React__namespace.Component {
|
|
|
1980
1980
|
const menuText = selectedItem ? getLabel(selectedItem.props) : placeholder;
|
|
1981
1981
|
const dropdownOpener = opener ? React__namespace.createElement(DropdownOpener, {
|
|
1982
1982
|
onClick: this.handleClick,
|
|
1983
|
-
disabled:
|
|
1983
|
+
disabled: isDisabled,
|
|
1984
1984
|
ref: this.handleOpenerRef,
|
|
1985
1985
|
text: menuText,
|
|
1986
1986
|
opened: this.state.open
|
|
1987
1987
|
}, opener) : React__namespace.createElement(SelectOpener, _extends({}, sharedProps, {
|
|
1988
|
-
disabled:
|
|
1988
|
+
disabled: isDisabled,
|
|
1989
1989
|
id: id,
|
|
1990
1990
|
error: error,
|
|
1991
1991
|
isPlaceholder: !selectedItem,
|
|
@@ -2010,14 +2010,17 @@ class SingleSelect extends React__namespace.Component {
|
|
|
2010
2010
|
light,
|
|
2011
2011
|
style,
|
|
2012
2012
|
"aria-invalid": ariaInvalid,
|
|
2013
|
-
"aria-required": ariaRequired
|
|
2013
|
+
"aria-required": ariaRequired,
|
|
2014
|
+
disabled
|
|
2014
2015
|
} = this.props;
|
|
2015
2016
|
const {
|
|
2016
2017
|
searchText
|
|
2017
2018
|
} = this.state;
|
|
2018
2019
|
const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
|
|
2020
|
+
const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
2019
2021
|
const items = this.getMenuItems(allChildren);
|
|
2020
|
-
const
|
|
2022
|
+
const isDisabled = numEnabledOptions === 0 || disabled;
|
|
2023
|
+
const opener = this.renderOpener(isDisabled);
|
|
2021
2024
|
return React__namespace.createElement(DropdownCore$1, {
|
|
2022
2025
|
role: "listbox",
|
|
2023
2026
|
selectionType: "single",
|
|
@@ -2039,7 +2042,8 @@ class SingleSelect extends React__namespace.Component {
|
|
|
2039
2042
|
searchText: isFilterable ? searchText : "",
|
|
2040
2043
|
labels: labels,
|
|
2041
2044
|
"aria-invalid": ariaInvalid,
|
|
2042
|
-
"aria-required": ariaRequired
|
|
2045
|
+
"aria-required": ariaRequired,
|
|
2046
|
+
disabled: isDisabled
|
|
2043
2047
|
});
|
|
2044
2048
|
}
|
|
2045
2049
|
}
|
|
@@ -2058,7 +2062,7 @@ SingleSelect.defaultProps = {
|
|
|
2058
2062
|
}
|
|
2059
2063
|
};
|
|
2060
2064
|
|
|
2061
|
-
const _excluded = ["
|
|
2065
|
+
const _excluded = ["id", "light", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-invalid", "aria-required"];
|
|
2062
2066
|
class MultiSelect extends React__namespace.Component {
|
|
2063
2067
|
constructor(props) {
|
|
2064
2068
|
super(props);
|
|
@@ -2143,7 +2147,7 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2143
2147
|
}
|
|
2144
2148
|
static getDerivedStateFromProps(props, state) {
|
|
2145
2149
|
return {
|
|
2146
|
-
open: typeof props.opened === "boolean" ? props.opened : state.open
|
|
2150
|
+
open: props.disabled ? false : typeof props.opened === "boolean" ? props.opened : state.open
|
|
2147
2151
|
};
|
|
2148
2152
|
}
|
|
2149
2153
|
componentDidUpdate(prevProps) {
|
|
@@ -2265,10 +2269,9 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2265
2269
|
}
|
|
2266
2270
|
return [...lastSelectedItems, ...restOfTheChildren.map(this.mapOptionItemToDropdownItem)];
|
|
2267
2271
|
}
|
|
2268
|
-
renderOpener(allChildren) {
|
|
2272
|
+
renderOpener(allChildren, isDisabled) {
|
|
2269
2273
|
const _this$props = this.props,
|
|
2270
2274
|
{
|
|
2271
|
-
disabled,
|
|
2272
2275
|
id,
|
|
2273
2276
|
light,
|
|
2274
2277
|
opener,
|
|
@@ -2279,15 +2282,14 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2279
2282
|
noneSelected
|
|
2280
2283
|
} = this.state.labels;
|
|
2281
2284
|
const menuText = this.getMenuText(allChildren);
|
|
2282
|
-
const numOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
2283
2285
|
const dropdownOpener = opener ? React__namespace.createElement(DropdownOpener, {
|
|
2284
2286
|
onClick: this.handleClick,
|
|
2285
|
-
disabled:
|
|
2287
|
+
disabled: isDisabled,
|
|
2286
2288
|
ref: this.handleOpenerRef,
|
|
2287
2289
|
text: menuText,
|
|
2288
2290
|
opened: this.state.open
|
|
2289
2291
|
}, opener) : React__namespace.createElement(SelectOpener, _extends({}, sharedProps, {
|
|
2290
|
-
disabled:
|
|
2292
|
+
disabled: isDisabled,
|
|
2291
2293
|
id: id,
|
|
2292
2294
|
isPlaceholder: menuText === noneSelected,
|
|
2293
2295
|
light: light,
|
|
@@ -2308,7 +2310,8 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2308
2310
|
children,
|
|
2309
2311
|
isFilterable,
|
|
2310
2312
|
"aria-invalid": ariaInvalid,
|
|
2311
|
-
"aria-required": ariaRequired
|
|
2313
|
+
"aria-required": ariaRequired,
|
|
2314
|
+
disabled
|
|
2312
2315
|
} = this.props;
|
|
2313
2316
|
const {
|
|
2314
2317
|
open,
|
|
@@ -2323,7 +2326,8 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2323
2326
|
const allChildren = React__namespace.Children.toArray(children).filter(Boolean);
|
|
2324
2327
|
const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
|
|
2325
2328
|
const filteredItems = this.getMenuItems(allChildren);
|
|
2326
|
-
const
|
|
2329
|
+
const isDisabled = numEnabledOptions === 0 || disabled;
|
|
2330
|
+
const opener = this.renderOpener(allChildren, isDisabled);
|
|
2327
2331
|
return React__namespace.createElement(DropdownCore$1, {
|
|
2328
2332
|
role: "listbox",
|
|
2329
2333
|
alignment: alignment,
|
|
@@ -2347,7 +2351,8 @@ class MultiSelect extends React__namespace.Component {
|
|
|
2347
2351
|
someResults: someSelected
|
|
2348
2352
|
},
|
|
2349
2353
|
"aria-invalid": ariaInvalid,
|
|
2350
|
-
"aria-required": ariaRequired
|
|
2354
|
+
"aria-required": ariaRequired,
|
|
2355
|
+
disabled: isDisabled
|
|
2351
2356
|
});
|
|
2352
2357
|
}
|
|
2353
2358
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-dropdown",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.8",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Dropdown variants for Wonder Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@khanacademy/wonder-blocks-core": "^6.4.1",
|
|
22
22
|
"@khanacademy/wonder-blocks-icon": "^4.1.1",
|
|
23
23
|
"@khanacademy/wonder-blocks-layout": "^2.1.0",
|
|
24
|
-
"@khanacademy/wonder-blocks-modal": "^5.1.
|
|
25
|
-
"@khanacademy/wonder-blocks-search-field": "^2.2.
|
|
24
|
+
"@khanacademy/wonder-blocks-modal": "^5.1.5",
|
|
25
|
+
"@khanacademy/wonder-blocks-search-field": "^2.2.16",
|
|
26
26
|
"@khanacademy/wonder-blocks-timing": "^5.0.0",
|
|
27
27
|
"@khanacademy/wonder-blocks-tokens": "^1.3.0",
|
|
28
28
|
"@khanacademy/wonder-blocks-typography": "^2.1.12"
|
|
@@ -772,4 +772,80 @@ describe("DropdownCore", () => {
|
|
|
772
772
|
expect(container).toHaveTextContent("3 items");
|
|
773
773
|
});
|
|
774
774
|
});
|
|
775
|
+
|
|
776
|
+
describe("onOpenChanged", () => {
|
|
777
|
+
it("Should be triggered when the down key is pressed and the menu is closed", async () => {
|
|
778
|
+
// Arrange
|
|
779
|
+
const onOpenMock = jest.fn();
|
|
780
|
+
|
|
781
|
+
render(
|
|
782
|
+
<DropdownCore
|
|
783
|
+
initialFocusedIndex={undefined}
|
|
784
|
+
onSearchTextChanged={jest.fn()}
|
|
785
|
+
// mock the items (3 options)
|
|
786
|
+
items={items}
|
|
787
|
+
role="listbox"
|
|
788
|
+
open={false}
|
|
789
|
+
// mock the opener elements
|
|
790
|
+
opener={<button />}
|
|
791
|
+
onOpenChanged={onOpenMock}
|
|
792
|
+
/>,
|
|
793
|
+
);
|
|
794
|
+
// Act
|
|
795
|
+
// Press the button
|
|
796
|
+
const button = await screen.findByRole("button");
|
|
797
|
+
// NOTE: we need to use fireEvent here because await userEvent doesn't
|
|
798
|
+
// support keyUp/Down events and we use these handlers to override
|
|
799
|
+
// the default behavior of the button.
|
|
800
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
801
|
+
fireEvent.keyDown(button, {
|
|
802
|
+
keyCode: 40,
|
|
803
|
+
});
|
|
804
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
805
|
+
fireEvent.keyUp(button, {
|
|
806
|
+
keyCode: 40,
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Assert
|
|
810
|
+
expect(onOpenMock).toHaveBeenCalledTimes(1);
|
|
811
|
+
expect(onOpenMock).toHaveBeenCalledWith(true);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it("Should not be triggered when the dropdown is disabled and the down key is pressed and the menu is closed", async () => {
|
|
815
|
+
// Arrange
|
|
816
|
+
const onOpenMock = jest.fn();
|
|
817
|
+
|
|
818
|
+
render(
|
|
819
|
+
<DropdownCore
|
|
820
|
+
initialFocusedIndex={undefined}
|
|
821
|
+
onSearchTextChanged={jest.fn()}
|
|
822
|
+
// mock the items (3 options)
|
|
823
|
+
items={items}
|
|
824
|
+
role="listbox"
|
|
825
|
+
open={false}
|
|
826
|
+
// mock the opener elements
|
|
827
|
+
opener={<button />}
|
|
828
|
+
onOpenChanged={onOpenMock}
|
|
829
|
+
disabled={true}
|
|
830
|
+
/>,
|
|
831
|
+
);
|
|
832
|
+
// Act
|
|
833
|
+
// Press the button
|
|
834
|
+
const button = await screen.findByRole("button");
|
|
835
|
+
// NOTE: we need to use fireEvent here because await userEvent doesn't
|
|
836
|
+
// support keyUp/Down events and we use these handlers to override
|
|
837
|
+
// the default behavior of the button.
|
|
838
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
839
|
+
fireEvent.keyDown(button, {
|
|
840
|
+
keyCode: 40,
|
|
841
|
+
});
|
|
842
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
843
|
+
fireEvent.keyUp(button, {
|
|
844
|
+
keyCode: 40,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Assert
|
|
848
|
+
expect(onOpenMock).toHaveBeenCalledTimes(0);
|
|
849
|
+
});
|
|
850
|
+
});
|
|
775
851
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-constant-condition */
|
|
2
2
|
/* eslint-disable max-lines */
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {render, screen, waitFor} from "@testing-library/react";
|
|
4
|
+
import {fireEvent, render, screen, waitFor} from "@testing-library/react";
|
|
5
5
|
import {
|
|
6
6
|
userEvent as ue,
|
|
7
7
|
PointerEventsCheckLevel,
|
|
@@ -1623,6 +1623,73 @@ describe("MultiSelect", () => {
|
|
|
1623
1623
|
// Assert
|
|
1624
1624
|
expect(multiSelect).not.toHaveAttribute("disabled");
|
|
1625
1625
|
});
|
|
1626
|
+
|
|
1627
|
+
it("should not be opened if it is disabled and `open` prop is set to true", () => {
|
|
1628
|
+
// Arrange
|
|
1629
|
+
|
|
1630
|
+
// Act
|
|
1631
|
+
doRender(
|
|
1632
|
+
<MultiSelect onChange={jest.fn()} disabled={true} opened={true}>
|
|
1633
|
+
<OptionItem label="item 1" value="1" />
|
|
1634
|
+
<OptionItem label="item 2" value="2" />
|
|
1635
|
+
<OptionItem label="item 3" value="3" />
|
|
1636
|
+
</MultiSelect>,
|
|
1637
|
+
);
|
|
1638
|
+
|
|
1639
|
+
// Assert
|
|
1640
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
it("should not be able to open the select using the keyboard if there are no items", async () => {
|
|
1644
|
+
// Arrange
|
|
1645
|
+
doRender(<MultiSelect onChange={jest.fn()} />);
|
|
1646
|
+
|
|
1647
|
+
// Act
|
|
1648
|
+
// Press the button
|
|
1649
|
+
const button = await screen.findByRole("button");
|
|
1650
|
+
// NOTE: we need to use fireEvent here because await userEvent doesn't
|
|
1651
|
+
// support keyUp/Down events and we use these handlers to override
|
|
1652
|
+
// the default behavior of the button.
|
|
1653
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
1654
|
+
fireEvent.keyDown(button, {
|
|
1655
|
+
keyCode: 40,
|
|
1656
|
+
});
|
|
1657
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
1658
|
+
fireEvent.keyUp(button, {
|
|
1659
|
+
keyCode: 40,
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
// Assert
|
|
1663
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
it("should not be able to open the select using the keyboard if all items are disabled", async () => {
|
|
1667
|
+
// Arrange
|
|
1668
|
+
doRender(
|
|
1669
|
+
<MultiSelect onChange={jest.fn()}>
|
|
1670
|
+
<OptionItem label="item 1" value="1" disabled={true} />
|
|
1671
|
+
<OptionItem label="item 2" value="2" disabled={true} />
|
|
1672
|
+
</MultiSelect>,
|
|
1673
|
+
);
|
|
1674
|
+
|
|
1675
|
+
// Act
|
|
1676
|
+
// Press the button
|
|
1677
|
+
const button = await screen.findByRole("button");
|
|
1678
|
+
// NOTE: we need to use fireEvent here because await userEvent doesn't
|
|
1679
|
+
// support keyUp/Down events and we use these handlers to override
|
|
1680
|
+
// the default behavior of the button.
|
|
1681
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
1682
|
+
fireEvent.keyDown(button, {
|
|
1683
|
+
keyCode: 40,
|
|
1684
|
+
});
|
|
1685
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
1686
|
+
fireEvent.keyUp(button, {
|
|
1687
|
+
keyCode: 40,
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
// Assert
|
|
1691
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
1692
|
+
});
|
|
1626
1693
|
});
|
|
1627
1694
|
|
|
1628
1695
|
describe("a11y > Focusable", () => {
|