@khanacademy/wonder-blocks-dropdown 8.0.1 → 9.0.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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 9.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 0199324d: Fixes keyboard tests in Dropdown and Clickable with specific key events. We now check `event.key` instead of `event.which` or `event.keyCode` to remove deprecated event properties and match the keys returned from Testing Library/userEvent.
8
+ - 1a18e98a: 1. Updates dropdown openers for SingleSelect and MultiSelect to use `role="combobox"` instead of `button`. 2. SingleSelect and MultiSelect should have a paired `<label>` element or `aria-label` attribute for accessibility. They no longer fall back to text content for labeling, as those contents are now used as combobox values. 3. Changes the type names for custom label objects from `Labels` to `LabelsValues` and `SingleSelectLabels` to `SingleSelectLabelsValues`, respectively.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [0199324d]
13
+ - @khanacademy/wonder-blocks-clickable@6.0.0
14
+ - @khanacademy/wonder-blocks-core@12.0.0
15
+ - @khanacademy/wonder-blocks-cell@4.0.8
16
+ - @khanacademy/wonder-blocks-pill@3.0.8
17
+ - @khanacademy/wonder-blocks-icon@5.0.6
18
+ - @khanacademy/wonder-blocks-layout@3.0.8
19
+ - @khanacademy/wonder-blocks-modal@7.0.7
20
+ - @khanacademy/wonder-blocks-search-field@5.0.3
21
+ - @khanacademy/wonder-blocks-typography@3.0.6
22
+
23
+ ## 8.0.2
24
+
25
+ ### Patch Changes
26
+
27
+ - @khanacademy/wonder-blocks-modal@7.0.6
28
+ - @khanacademy/wonder-blocks-search-field@5.0.2
29
+
3
30
  ## 8.0.1
4
31
 
5
32
  ### Patch Changes
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import type { StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import type { DropdownItem } from "../util/types";
4
- type Labels = {
4
+ type LabelsValues = {
5
5
  /**
6
6
  * Label for describing the dismiss icon on the search filter.
7
7
  */
@@ -116,7 +116,7 @@ type ExportProps = Readonly<{
116
116
  /**
117
117
  * The object containing the custom labels used inside this component.
118
118
  */
119
- labels?: Labels;
119
+ labels?: LabelsValues;
120
120
  /**
121
121
  * Used to determine if we can automatically select an item using the keyboard.
122
122
  */
@@ -28,7 +28,7 @@ type Props = Partial<Omit<AriaProps, "aria-disabled">> & {
28
28
  */
29
29
  testId?: string;
30
30
  /**
31
- * Text for the opener that can be passed to the child as an argument.
31
+ * Content for the opener that can be passed to the child as an argument.
32
32
  */
33
33
  text: OptionLabel;
34
34
  /**
@@ -43,6 +43,10 @@ type Props = Partial<Omit<AriaProps, "aria-disabled">> & {
43
43
  * If the dropdown has an error.
44
44
  */
45
45
  error?: boolean;
46
+ /**
47
+ * The role of the opener.
48
+ */
49
+ role: "combobox" | "button";
46
50
  };
47
51
  type DefaultProps = {
48
52
  disabled: Props["disabled"];
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { type AriaProps, type StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import OptionItem from "./option-item";
4
4
  import type { OpenerProps } from "../util/types";
5
- export type Labels = {
5
+ export type LabelsValues = {
6
6
  /**
7
7
  * Label for describing the dismiss icon on the search filter.
8
8
  */
@@ -12,7 +12,7 @@ export type Labels = {
12
12
  */
13
13
  filter: string;
14
14
  /**
15
- * Label for when the filter returns no results.
15
+ * Value for when the filter returns no results.
16
16
  */
17
17
  noResults: string;
18
18
  /**
@@ -24,15 +24,15 @@ export type Labels = {
24
24
  */
25
25
  selectNoneLabel: string;
26
26
  /**
27
- * Label for the opening component when there are no items selected.
27
+ * Value for the opening component when there are no items selected.
28
28
  */
29
29
  noneSelected: string;
30
30
  /**
31
- * Label for the opening component when there are some items selected.
31
+ * Value for the opening component when there are some items selected.
32
32
  */
33
33
  someSelected: (numOptions: number) => string;
34
34
  /**
35
- * Label for the opening component when all the items have been selected.
35
+ * Value for the opening component when all the items have been selected.
36
36
  */
37
37
  allSelected: string;
38
38
  };
@@ -60,8 +60,8 @@ type DefaultProps = Readonly<{
60
60
  */
61
61
  shortcuts?: boolean;
62
62
  /**
63
- * When false, the SelectOpener can show a Node as a label. When true, the
64
- * SelectOpener will use a string as a label. If using custom OptionItems, a
63
+ * When false, the SelectOpener can show a Node as a value. When true, the
64
+ * SelectOpener will use a string as a value. If using custom OptionItems, a
65
65
  * plain text label can be provided with the `labelAsText` prop.
66
66
  * Defaults to true.
67
67
  */
@@ -94,9 +94,9 @@ type Props = AriaProps & DefaultProps & Readonly<{
94
94
  */
95
95
  isFilterable?: boolean;
96
96
  /**
97
- * The object containing the custom labels used inside this component.
97
+ * The object containing the custom labels and placeholder values used inside this component.
98
98
  */
99
- labels?: Labels;
99
+ labels?: LabelsValues;
100
100
  /**
101
101
  * Callback for when the selection changes. Parameter is an updated array of
102
102
  * the values that are now selected.
@@ -177,6 +177,9 @@ type Props = AriaProps & DefaultProps & Readonly<{
177
177
  * multiple options to be selected. Clients are responsible for keeping track
178
178
  * of the selected items.
179
179
  *
180
+ * Clients are also responsible for labeling the select using `LabeledField`, or
181
+ * an `aria-label` attribute or `aria-labelledby` on the select.
182
+ *
180
183
  * The multi select stays open until closed by the user. The onChange callback
181
184
  * happens every time there is a change in the selection of the items.
182
185
  *
@@ -185,7 +188,7 @@ type Props = AriaProps & DefaultProps & Readonly<{
185
188
  * ```jsx
186
189
  * import {OptionItem, MultiSelect} from "@khanacademy/wonder-blocks-dropdown";
187
190
  *
188
- * <MultiSelect onChange={setSelectedValues} selectedValues={selectedValues}>
191
+ * <MultiSelect aria-label="Fruits" onChange={setSelectedValues} selectedValues={selectedValues}>
189
192
  * <OptionItem value="pear">Pear</OptionItem>
190
193
  * <OptionItem value="mango">Mango</OptionItem>
191
194
  * </MultiSelect>
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { AriaProps } from "@khanacademy/wonder-blocks-core";
2
+ import { type AriaProps } from "@khanacademy/wonder-blocks-core";
3
3
  import { OptionLabel } from "../util/types";
4
4
  type SelectOpenerProps = AriaProps & {
5
5
  /**
@@ -25,6 +25,10 @@ type SelectOpenerProps = AriaProps & {
25
25
  * of this component. A placeholder has more faded text colors and styles.
26
26
  */
27
27
  isPlaceholder: boolean;
28
+ /**
29
+ * A label to expose on the opener, in the absence of an associated label element or `aria-labelledby`.
30
+ */
31
+ ariaLabel?: string;
28
32
  /**
29
33
  * Callback for when the SelectOpener is pressed.
30
34
  */
@@ -2,9 +2,9 @@ import * as React from "react";
2
2
  import { type AriaProps, type StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import OptionItem from "./option-item";
4
4
  import type { OpenerProps } from "../util/types";
5
- export type SingleSelectLabels = {
5
+ export type SingleSelectLabelsValues = {
6
6
  /**
7
- * Label for describing the dismiss icon on the search filter.
7
+ * Label to create an accessible name for the dismiss icon on the search filter.
8
8
  */
9
9
  clearSearch: string;
10
10
  /**
@@ -12,11 +12,11 @@ export type SingleSelectLabels = {
12
12
  */
13
13
  filter: string;
14
14
  /**
15
- * Label for when the filter returns no results.
15
+ * Value for when the filter returns no results.
16
16
  */
17
17
  noResults: string;
18
18
  /**
19
- * Label for the opening component when there are some items selected.
19
+ * Value for the opening component when there are some items selected.
20
20
  */
21
21
  someResults: (numOptions: number) => string;
22
22
  };
@@ -54,12 +54,12 @@ type DefaultProps = Readonly<{
54
54
  */
55
55
  error?: boolean;
56
56
  /**
57
- * The object containing the custom labels used inside this component.
57
+ * The object containing the custom labels and placeholder values used inside this component.
58
58
  */
59
- labels?: SingleSelectLabels;
59
+ labels?: SingleSelectLabelsValues;
60
60
  /**
61
- * When false, the SelectOpener can show a Node as a label. When true, the
62
- * SelectOpener will use a string as a label. If using custom OptionItems, a
61
+ * When false, the SelectOpener can show a Node as a value. When true, the
62
+ * SelectOpener will use a string as a value. If using custom OptionItems, a
63
63
  * plain text label can be provided with the `labelAsText` prop.
64
64
  * Defaults to true.
65
65
  */
@@ -91,7 +91,8 @@ type Props = AriaProps & DefaultProps & Readonly<{
91
91
  */
92
92
  id?: string;
93
93
  /**
94
- * Placeholder for the opening component when there are no items selected.
94
+ * Placeholder value for the opening component when there are no items selected.
95
+ * Note: a label is still necessary to describe the purpose of the select.
95
96
  */
96
97
  placeholder: string;
97
98
  /**
@@ -172,6 +173,9 @@ type Props = AriaProps & DefaultProps & Readonly<{
172
173
  * The single select allows the selection of one item. Clients are responsible
173
174
  * for keeping track of the selected item in the select.
174
175
  *
176
+ * Clients are also responsible for labeling the select using `LabeledField`, an
177
+ * `aria-label` attribute, or `aria-labelledby`.
178
+ *
175
179
  * The single select dropdown closes after the selection of an item. If the same
176
180
  * item is selected, there is no callback.
177
181
  *
@@ -188,7 +192,7 @@ type Props = AriaProps & DefaultProps & Readonly<{
188
192
  *
189
193
  * const [selectedValue, setSelectedValue] = React.useState("");
190
194
  *
191
- * <SingleSelect placeholder="Choose a fruit" onChange={setSelectedValue} selectedValue={selectedValue}>
195
+ * <SingleSelect aria-label="Your Favorite Fruits" placeholder="Choose a fruit" onChange={setSelectedValue} selectedValue={selectedValue}>
192
196
  * <OptionItem label="Pear" value="pear" />
193
197
  * <OptionItem label="Mango" value="mango" />
194
198
  * </SingleSelect>
@@ -203,6 +207,7 @@ type Props = AriaProps & DefaultProps & Readonly<{
203
207
  * const fruitArray = ["Apple", "Banana", "Orange", "Mango", "Pear"];
204
208
  *
205
209
  * <SingleSelect
210
+ * aria-label="Your Favorite Fruits"
206
211
  * placeholder="Choose a fruit"
207
212
  * onChange={setSelectedValue}
208
213
  * selectedValue={selectedValue}
package/dist/es/index.js CHANGED
@@ -6,7 +6,7 @@ import { CompactCell, DetailCell } from '@khanacademy/wonder-blocks-cell';
6
6
  import { spacing, color, mix, fade, semanticColor, border, font } from '@khanacademy/wonder-blocks-tokens';
7
7
  import { LabelMedium, LabelSmall, LabelLarge } from '@khanacademy/wonder-blocks-typography';
8
8
  import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
9
- import { View, addStyle, Id, useOnMountEffect } from '@khanacademy/wonder-blocks-core';
9
+ import { View, addStyle, keys, Id, useOnMountEffect } from '@khanacademy/wonder-blocks-core';
10
10
  import { Strut } from '@khanacademy/wonder-blocks-layout';
11
11
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
12
12
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
@@ -25,13 +25,6 @@ import { TextField } from '@khanacademy/wonder-blocks-form';
25
25
  import IconButton from '@khanacademy/wonder-blocks-icon-button';
26
26
  import Pill from '@khanacademy/wonder-blocks-pill';
27
27
 
28
- const keys = {
29
- escape: "Escape",
30
- tab: "Tab",
31
- space: " ",
32
- up: "ArrowUp",
33
- down: "ArrowDown"
34
- };
35
28
  const selectDropdownStyle = {
36
29
  marginTop: spacing.xSmall_8,
37
30
  marginBottom: spacing.xSmall_8
@@ -487,6 +480,7 @@ class DropdownOpener extends React.Component {
487
480
  };
488
481
  }
489
482
  renderAnchorChildren(eventState, clickableChildrenProps) {
483
+ var _childrenProps$ariaL;
490
484
  const {
491
485
  disabled,
492
486
  testId,
@@ -496,6 +490,7 @@ class DropdownOpener extends React.Component {
496
490
  "aria-haspopup": ariaHasPopUp,
497
491
  "aria-required": ariaRequired,
498
492
  id,
493
+ role,
499
494
  onBlur
500
495
  } = this.props;
501
496
  const renderedChildren = this.props.children(_extends({}, eventState, {
@@ -504,10 +499,13 @@ class DropdownOpener extends React.Component {
504
499
  }));
505
500
  const childrenProps = renderedChildren.props;
506
501
  const childrenTestId = this.getTestIdFromProps(childrenProps);
502
+ const renderedAriaLabel = (_childrenProps$ariaL = childrenProps["aria-label"]) != null ? _childrenProps$ariaL : this.props["aria-label"];
507
503
  return React.cloneElement(renderedChildren, _extends({}, clickableChildrenProps, {
504
+ "aria-label": renderedAriaLabel != null ? renderedAriaLabel : undefined,
508
505
  "aria-invalid": this.props.error,
509
506
  disabled,
510
507
  "aria-controls": ariaControls,
508
+ role,
511
509
  id,
512
510
  "aria-expanded": opened ? "true" : "false",
513
511
  "aria-haspopup": ariaHasPopUp,
@@ -1618,7 +1616,8 @@ class ActionMenu extends React.Component {
1618
1616
  text: menuText,
1619
1617
  ref: this.handleOpenerRef,
1620
1618
  testId: opener ? undefined : testId,
1621
- opened: this.state.opened
1619
+ opened: this.state.opened,
1620
+ role: "button"
1622
1621
  }, opener ? opener : openerProps => {
1623
1622
  const {
1624
1623
  opened
@@ -1676,7 +1675,7 @@ const styles$5 = StyleSheet.create({
1676
1675
  }
1677
1676
  });
1678
1677
 
1679
- const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "open", "testId", "aria-required", "onBlur", "onOpenChanged"];
1678
+ const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "open", "testId", "aria-label", "aria-required", "onBlur", "onOpenChanged"];
1680
1679
  const StyledButton = addStyle("button");
1681
1680
  class SelectOpener extends React.Component {
1682
1681
  constructor(props) {
@@ -1688,8 +1687,8 @@ class SelectOpener extends React.Component {
1688
1687
  this.props.onOpenChanged(!open);
1689
1688
  };
1690
1689
  this.handleKeyDown = e => {
1691
- const keyCode = e.key;
1692
- if (keyCode === "Enter" || keyCode === " ") {
1690
+ const keyName = e.key;
1691
+ if (keyName === keys.enter || keyName === keys.space) {
1693
1692
  this.setState({
1694
1693
  pressed: true
1695
1694
  });
@@ -1697,8 +1696,8 @@ class SelectOpener extends React.Component {
1697
1696
  }
1698
1697
  };
1699
1698
  this.handleKeyUp = e => {
1700
- const keyCode = e.key;
1701
- if (keyCode === "Enter" || keyCode === " ") {
1699
+ const keyName = e.key;
1700
+ if (keyName === keys.enter || keyName === keys.space) {
1702
1701
  this.setState({
1703
1702
  pressed: false
1704
1703
  });
@@ -1719,6 +1718,7 @@ class SelectOpener extends React.Component {
1719
1718
  isPlaceholder,
1720
1719
  open,
1721
1720
  testId,
1721
+ "aria-label": ariaLabel,
1722
1722
  "aria-required": ariaRequired,
1723
1723
  onBlur
1724
1724
  } = _this$props,
@@ -1730,19 +1730,22 @@ class SelectOpener extends React.Component {
1730
1730
  "aria-disabled": disabled,
1731
1731
  "aria-expanded": open ? "true" : "false",
1732
1732
  "aria-invalid": error,
1733
+ "aria-label": ariaLabel != null ? ariaLabel : undefined,
1733
1734
  "aria-required": ariaRequired,
1734
1735
  "aria-haspopup": "listbox",
1735
1736
  "data-testid": testId,
1736
1737
  id: id,
1738
+ role: "combobox",
1737
1739
  style: style,
1738
- type: "button",
1739
1740
  onClick: !disabled ? this.handleClick : undefined,
1740
1741
  onKeyDown: !disabled ? this.handleKeyDown : undefined,
1741
1742
  onKeyUp: !disabled ? this.handleKeyUp : undefined,
1742
1743
  onBlur: onBlur
1743
1744
  }), React.createElement(LabelMedium, {
1744
1745
  style: styles$4.text
1745
- }, children || "\u00A0"), React.createElement(PhosphorIcon, {
1746
+ }, children || React.createElement("span", {
1747
+ "aria-hidden": "true"
1748
+ }, "\xA0")), React.createElement(PhosphorIcon, {
1746
1749
  icon: caretDownIcon,
1747
1750
  color: iconColor,
1748
1751
  size: "small",
@@ -1927,7 +1930,7 @@ function useSelectValidation({
1927
1930
  };
1928
1931
  }
1929
1932
 
1930
- const _excluded$1 = ["children", "error", "id", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required", "disabled", "dropdownId", "validate", "onValidate", "required", "showOpenerLabelAsText"];
1933
+ const _excluded$1 = ["children", "error", "id", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-label", "aria-invalid", "aria-required", "disabled", "dropdownId", "validate", "onValidate", "required", "showOpenerLabelAsText"];
1931
1934
  const SingleSelect = props => {
1932
1935
  const selectedIndex = React.useRef(0);
1933
1936
  const {
@@ -1954,6 +1957,7 @@ const SingleSelect = props => {
1954
1957
  opened,
1955
1958
  style,
1956
1959
  className,
1960
+ "aria-label": ariaLabel,
1957
1961
  "aria-invalid": ariaInvalid,
1958
1962
  "aria-required": ariaRequired,
1959
1963
  disabled = false,
@@ -2065,16 +2069,19 @@ const SingleSelect = props => {
2065
2069
  }, uniqueOpenerId => {
2066
2070
  return opener ? React.createElement(DropdownOpener, {
2067
2071
  id: uniqueOpenerId,
2072
+ "aria-label": ariaLabel,
2068
2073
  "aria-controls": dropdownId,
2069
2074
  "aria-haspopup": "listbox",
2070
2075
  onClick: handleClick,
2071
2076
  disabled: isDisabled,
2072
2077
  ref: handleOpenerRef,
2078
+ role: "combobox",
2073
2079
  text: menuText,
2074
2080
  opened: open,
2075
2081
  error: hasError,
2076
2082
  onBlur: onOpenerBlurValidation
2077
2083
  }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
2084
+ "aria-label": ariaLabel,
2078
2085
  "aria-controls": dropdownId,
2079
2086
  disabled: isDisabled,
2080
2087
  id: uniqueOpenerId,
@@ -2121,7 +2128,7 @@ const SingleSelect = props => {
2121
2128
  }));
2122
2129
  };
2123
2130
 
2124
- const _excluded = ["id", "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"];
2131
+ const _excluded = ["id", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-label", "aria-invalid", "aria-required", "disabled", "error", "children", "dropdownId", "showOpenerLabelAsText", "validate", "onValidate", "required"];
2125
2132
  const MultiSelect = props => {
2126
2133
  const {
2127
2134
  id,
@@ -2139,6 +2146,7 @@ const MultiSelect = props => {
2139
2146
  shortcuts = false,
2140
2147
  style,
2141
2148
  className,
2149
+ "aria-label": ariaLabel,
2142
2150
  "aria-invalid": ariaInvalid,
2143
2151
  "aria-required": ariaRequired,
2144
2152
  disabled = false,
@@ -2213,7 +2221,7 @@ const MultiSelect = props => {
2213
2221
  onChange([]);
2214
2222
  onSelectedValuesChangeValidation();
2215
2223
  };
2216
- const getMenuText = children => {
2224
+ const getMenuTextOrNode = children => {
2217
2225
  const {
2218
2226
  noneSelected,
2219
2227
  someSelected,
@@ -2339,33 +2347,36 @@ const MultiSelect = props => {
2339
2347
  const {
2340
2348
  noneSelected
2341
2349
  } = labels;
2342
- const menuText = getMenuText(allChildren);
2350
+ const menuContent = getMenuTextOrNode(allChildren);
2343
2351
  const dropdownOpener = React.createElement(Id, {
2344
2352
  id: id
2345
2353
  }, uniqueOpenerId => {
2346
2354
  return opener ? React.createElement(DropdownOpener, {
2347
2355
  id: uniqueOpenerId,
2348
2356
  error: hasError,
2357
+ "aria-label": ariaLabel,
2349
2358
  "aria-controls": dropdownId,
2350
2359
  "aria-haspopup": "listbox",
2351
2360
  onClick: handleClick,
2352
2361
  onBlur: onOpenerBlurValidation,
2353
2362
  disabled: isDisabled,
2354
2363
  ref: handleOpenerRef,
2355
- text: menuText,
2364
+ role: "combobox",
2365
+ text: menuContent,
2356
2366
  opened: open
2357
2367
  }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
2358
2368
  error: hasError,
2359
2369
  disabled: isDisabled,
2360
2370
  id: uniqueOpenerId,
2371
+ "aria-label": ariaLabel,
2361
2372
  "aria-controls": dropdownId,
2362
- isPlaceholder: menuText === noneSelected,
2373
+ isPlaceholder: menuContent === noneSelected,
2363
2374
  onOpenChanged: handleOpenChanged,
2364
2375
  onBlur: onOpenerBlurValidation,
2365
2376
  open: open,
2366
2377
  ref: handleOpenerRef,
2367
2378
  testId: testId
2368
- }), menuText);
2379
+ }), menuContent);
2369
2380
  });
2370
2381
  return dropdownOpener;
2371
2382
  };
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import SingleSelect from "./components/single-select";
6
6
  import MultiSelect from "./components/multi-select";
7
7
  import Combobox from "./components/combobox";
8
8
  import Listbox from "./components/listbox";
9
- import type { Labels } from "./components/multi-select";
10
- import type { SingleSelectLabels } from "./components/single-select";
9
+ import type { LabelsValues } from "./components/multi-select";
10
+ import type { SingleSelectLabelsValues } from "./components/single-select";
11
11
  export { ActionItem, OptionItem, SeparatorItem, ActionMenu, SingleSelect, MultiSelect, Combobox, Listbox, };
12
- export type { Labels, SingleSelectLabels };
12
+ export type { LabelsValues, SingleSelectLabelsValues };
package/dist/index.js CHANGED
@@ -60,13 +60,6 @@ var xIcon__default = /*#__PURE__*/_interopDefaultLegacy(xIcon);
60
60
  var IconButton__default = /*#__PURE__*/_interopDefaultLegacy(IconButton);
61
61
  var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
62
62
 
63
- const keys = {
64
- escape: "Escape",
65
- tab: "Tab",
66
- space: " ",
67
- up: "ArrowUp",
68
- down: "ArrowDown"
69
- };
70
63
  const selectDropdownStyle = {
71
64
  marginTop: wonderBlocksTokens.spacing.xSmall_8,
72
65
  marginBottom: wonderBlocksTokens.spacing.xSmall_8
@@ -522,6 +515,7 @@ class DropdownOpener extends React__namespace.Component {
522
515
  };
523
516
  }
524
517
  renderAnchorChildren(eventState, clickableChildrenProps) {
518
+ var _childrenProps$ariaL;
525
519
  const {
526
520
  disabled,
527
521
  testId,
@@ -531,6 +525,7 @@ class DropdownOpener extends React__namespace.Component {
531
525
  "aria-haspopup": ariaHasPopUp,
532
526
  "aria-required": ariaRequired,
533
527
  id,
528
+ role,
534
529
  onBlur
535
530
  } = this.props;
536
531
  const renderedChildren = this.props.children(_extends__default["default"]({}, eventState, {
@@ -539,10 +534,13 @@ class DropdownOpener extends React__namespace.Component {
539
534
  }));
540
535
  const childrenProps = renderedChildren.props;
541
536
  const childrenTestId = this.getTestIdFromProps(childrenProps);
537
+ const renderedAriaLabel = (_childrenProps$ariaL = childrenProps["aria-label"]) != null ? _childrenProps$ariaL : this.props["aria-label"];
542
538
  return React__namespace.cloneElement(renderedChildren, _extends__default["default"]({}, clickableChildrenProps, {
539
+ "aria-label": renderedAriaLabel != null ? renderedAriaLabel : undefined,
543
540
  "aria-invalid": this.props.error,
544
541
  disabled,
545
542
  "aria-controls": ariaControls,
543
+ role,
546
544
  id,
547
545
  "aria-expanded": opened ? "true" : "false",
548
546
  "aria-haspopup": ariaHasPopUp,
@@ -897,7 +895,7 @@ class DropdownCore extends React__namespace.Component {
897
895
  this.handleKeyDownDebounced(this.textSuggestion);
898
896
  }
899
897
  if (!open) {
900
- if (key === keys.down) {
898
+ if (key === wonderBlocksCore.keys.down) {
901
899
  event.preventDefault();
902
900
  onOpenChanged(true);
903
901
  return;
@@ -905,24 +903,24 @@ class DropdownCore extends React__namespace.Component {
905
903
  return;
906
904
  }
907
905
  switch (key) {
908
- case keys.tab:
906
+ case wonderBlocksCore.keys.tab:
909
907
  if (this.isSearchFieldFocused() && searchText) {
910
908
  return;
911
909
  }
912
910
  this.restoreTabOrder();
913
911
  onOpenChanged(false);
914
912
  return;
915
- case keys.space:
913
+ case wonderBlocksCore.keys.space:
916
914
  if (this.isSearchFieldFocused()) {
917
915
  return;
918
916
  }
919
917
  event.preventDefault();
920
918
  return;
921
- case keys.up:
919
+ case wonderBlocksCore.keys.up:
922
920
  event.preventDefault();
923
921
  this.focusPreviousItem();
924
922
  return;
925
- case keys.down:
923
+ case wonderBlocksCore.keys.down:
926
924
  event.preventDefault();
927
925
  this.focusNextItem();
928
926
  return;
@@ -935,13 +933,13 @@ class DropdownCore extends React__namespace.Component {
935
933
  } = this.props;
936
934
  const key = event.key;
937
935
  switch (key) {
938
- case keys.space:
936
+ case wonderBlocksCore.keys.space:
939
937
  if (this.isSearchFieldFocused()) {
940
938
  return;
941
939
  }
942
940
  event.preventDefault();
943
941
  return;
944
- case keys.escape:
942
+ case wonderBlocksCore.keys.escape:
945
943
  if (open) {
946
944
  event.stopPropagation();
947
945
  this.restoreTabOrder();
@@ -1653,7 +1651,8 @@ class ActionMenu extends React__namespace.Component {
1653
1651
  text: menuText,
1654
1652
  ref: this.handleOpenerRef,
1655
1653
  testId: opener ? undefined : testId,
1656
- opened: this.state.opened
1654
+ opened: this.state.opened,
1655
+ role: "button"
1657
1656
  }, opener ? opener : openerProps => {
1658
1657
  const {
1659
1658
  opened
@@ -1711,7 +1710,7 @@ const styles$5 = aphrodite.StyleSheet.create({
1711
1710
  }
1712
1711
  });
1713
1712
 
1714
- const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "open", "testId", "aria-required", "onBlur", "onOpenChanged"];
1713
+ const _excluded$2 = ["children", "disabled", "error", "id", "isPlaceholder", "open", "testId", "aria-label", "aria-required", "onBlur", "onOpenChanged"];
1715
1714
  const StyledButton = wonderBlocksCore.addStyle("button");
1716
1715
  class SelectOpener extends React__namespace.Component {
1717
1716
  constructor(props) {
@@ -1723,8 +1722,8 @@ class SelectOpener extends React__namespace.Component {
1723
1722
  this.props.onOpenChanged(!open);
1724
1723
  };
1725
1724
  this.handleKeyDown = e => {
1726
- const keyCode = e.key;
1727
- if (keyCode === "Enter" || keyCode === " ") {
1725
+ const keyName = e.key;
1726
+ if (keyName === wonderBlocksCore.keys.enter || keyName === wonderBlocksCore.keys.space) {
1728
1727
  this.setState({
1729
1728
  pressed: true
1730
1729
  });
@@ -1732,8 +1731,8 @@ class SelectOpener extends React__namespace.Component {
1732
1731
  }
1733
1732
  };
1734
1733
  this.handleKeyUp = e => {
1735
- const keyCode = e.key;
1736
- if (keyCode === "Enter" || keyCode === " ") {
1734
+ const keyName = e.key;
1735
+ if (keyName === wonderBlocksCore.keys.enter || keyName === wonderBlocksCore.keys.space) {
1737
1736
  this.setState({
1738
1737
  pressed: false
1739
1738
  });
@@ -1754,6 +1753,7 @@ class SelectOpener extends React__namespace.Component {
1754
1753
  isPlaceholder,
1755
1754
  open,
1756
1755
  testId,
1756
+ "aria-label": ariaLabel,
1757
1757
  "aria-required": ariaRequired,
1758
1758
  onBlur
1759
1759
  } = _this$props,
@@ -1765,19 +1765,22 @@ class SelectOpener extends React__namespace.Component {
1765
1765
  "aria-disabled": disabled,
1766
1766
  "aria-expanded": open ? "true" : "false",
1767
1767
  "aria-invalid": error,
1768
+ "aria-label": ariaLabel != null ? ariaLabel : undefined,
1768
1769
  "aria-required": ariaRequired,
1769
1770
  "aria-haspopup": "listbox",
1770
1771
  "data-testid": testId,
1771
1772
  id: id,
1773
+ role: "combobox",
1772
1774
  style: style,
1773
- type: "button",
1774
1775
  onClick: !disabled ? this.handleClick : undefined,
1775
1776
  onKeyDown: !disabled ? this.handleKeyDown : undefined,
1776
1777
  onKeyUp: !disabled ? this.handleKeyUp : undefined,
1777
1778
  onBlur: onBlur
1778
1779
  }), React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
1779
1780
  style: styles$4.text
1780
- }, children || "\u00A0"), React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
1781
+ }, children || React__namespace.createElement("span", {
1782
+ "aria-hidden": "true"
1783
+ }, "\xA0")), React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
1781
1784
  icon: caretDownIcon__default["default"],
1782
1785
  color: iconColor,
1783
1786
  size: "small",
@@ -1962,7 +1965,7 @@ function useSelectValidation({
1962
1965
  };
1963
1966
  }
1964
1967
 
1965
- const _excluded$1 = ["children", "error", "id", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-invalid", "aria-required", "disabled", "dropdownId", "validate", "onValidate", "required", "showOpenerLabelAsText"];
1968
+ const _excluded$1 = ["children", "error", "id", "opener", "placeholder", "selectedValue", "testId", "alignment", "autoFocus", "dropdownStyle", "enableTypeAhead", "isFilterable", "labels", "onChange", "onToggle", "opened", "style", "className", "aria-label", "aria-invalid", "aria-required", "disabled", "dropdownId", "validate", "onValidate", "required", "showOpenerLabelAsText"];
1966
1969
  const SingleSelect = props => {
1967
1970
  const selectedIndex = React__namespace.useRef(0);
1968
1971
  const {
@@ -1989,6 +1992,7 @@ const SingleSelect = props => {
1989
1992
  opened,
1990
1993
  style,
1991
1994
  className,
1995
+ "aria-label": ariaLabel,
1992
1996
  "aria-invalid": ariaInvalid,
1993
1997
  "aria-required": ariaRequired,
1994
1998
  disabled = false,
@@ -2100,16 +2104,19 @@ const SingleSelect = props => {
2100
2104
  }, uniqueOpenerId => {
2101
2105
  return opener ? React__namespace.createElement(DropdownOpener, {
2102
2106
  id: uniqueOpenerId,
2107
+ "aria-label": ariaLabel,
2103
2108
  "aria-controls": dropdownId,
2104
2109
  "aria-haspopup": "listbox",
2105
2110
  onClick: handleClick,
2106
2111
  disabled: isDisabled,
2107
2112
  ref: handleOpenerRef,
2113
+ role: "combobox",
2108
2114
  text: menuText,
2109
2115
  opened: open,
2110
2116
  error: hasError,
2111
2117
  onBlur: onOpenerBlurValidation
2112
2118
  }, opener) : React__namespace.createElement(SelectOpener, _extends__default["default"]({}, sharedProps, {
2119
+ "aria-label": ariaLabel,
2113
2120
  "aria-controls": dropdownId,
2114
2121
  disabled: isDisabled,
2115
2122
  id: uniqueOpenerId,
@@ -2156,7 +2163,7 @@ const SingleSelect = props => {
2156
2163
  }));
2157
2164
  };
2158
2165
 
2159
- const _excluded = ["id", "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"];
2166
+ const _excluded = ["id", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className", "aria-label", "aria-invalid", "aria-required", "disabled", "error", "children", "dropdownId", "showOpenerLabelAsText", "validate", "onValidate", "required"];
2160
2167
  const MultiSelect = props => {
2161
2168
  const {
2162
2169
  id,
@@ -2174,6 +2181,7 @@ const MultiSelect = props => {
2174
2181
  shortcuts = false,
2175
2182
  style,
2176
2183
  className,
2184
+ "aria-label": ariaLabel,
2177
2185
  "aria-invalid": ariaInvalid,
2178
2186
  "aria-required": ariaRequired,
2179
2187
  disabled = false,
@@ -2248,7 +2256,7 @@ const MultiSelect = props => {
2248
2256
  onChange([]);
2249
2257
  onSelectedValuesChangeValidation();
2250
2258
  };
2251
- const getMenuText = children => {
2259
+ const getMenuTextOrNode = children => {
2252
2260
  const {
2253
2261
  noneSelected,
2254
2262
  someSelected,
@@ -2374,33 +2382,36 @@ const MultiSelect = props => {
2374
2382
  const {
2375
2383
  noneSelected
2376
2384
  } = labels;
2377
- const menuText = getMenuText(allChildren);
2385
+ const menuContent = getMenuTextOrNode(allChildren);
2378
2386
  const dropdownOpener = React__namespace.createElement(wonderBlocksCore.Id, {
2379
2387
  id: id
2380
2388
  }, uniqueOpenerId => {
2381
2389
  return opener ? React__namespace.createElement(DropdownOpener, {
2382
2390
  id: uniqueOpenerId,
2383
2391
  error: hasError,
2392
+ "aria-label": ariaLabel,
2384
2393
  "aria-controls": dropdownId,
2385
2394
  "aria-haspopup": "listbox",
2386
2395
  onClick: handleClick,
2387
2396
  onBlur: onOpenerBlurValidation,
2388
2397
  disabled: isDisabled,
2389
2398
  ref: handleOpenerRef,
2390
- text: menuText,
2399
+ role: "combobox",
2400
+ text: menuContent,
2391
2401
  opened: open
2392
2402
  }, opener) : React__namespace.createElement(SelectOpener, _extends__default["default"]({}, sharedProps, {
2393
2403
  error: hasError,
2394
2404
  disabled: isDisabled,
2395
2405
  id: uniqueOpenerId,
2406
+ "aria-label": ariaLabel,
2396
2407
  "aria-controls": dropdownId,
2397
- isPlaceholder: menuText === noneSelected,
2408
+ isPlaceholder: menuContent === noneSelected,
2398
2409
  onOpenChanged: handleOpenChanged,
2399
2410
  onBlur: onOpenerBlurValidation,
2400
2411
  open: open,
2401
2412
  ref: handleOpenerRef,
2402
2413
  testId: testId
2403
- }), menuText);
2414
+ }), menuContent);
2404
2415
  });
2405
2416
  return dropdownOpener;
2406
2417
  };
@@ -1,15 +1,4 @@
1
1
  import { ComboboxLabels } from "./types";
2
- /**
3
- * Key value mapping reference:
4
- * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
5
- */
6
- export declare const keys: {
7
- readonly escape: "Escape";
8
- readonly tab: "Tab";
9
- readonly space: " ";
10
- readonly up: "ArrowUp";
11
- readonly down: "ArrowDown";
12
- };
13
2
  export declare const selectDropdownStyle: {
14
3
  readonly marginTop: 8;
15
4
  readonly marginBottom: 8;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "8.0.1",
3
+ "version": "9.0.0",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -17,17 +17,17 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@babel/runtime": "^7.18.6",
20
- "@khanacademy/wonder-blocks-cell": "^4.0.7",
21
- "@khanacademy/wonder-blocks-clickable": "^5.0.7",
22
- "@khanacademy/wonder-blocks-core": "^11.1.0",
23
- "@khanacademy/wonder-blocks-icon": "^5.0.5",
24
- "@khanacademy/wonder-blocks-layout": "^3.0.7",
25
- "@khanacademy/wonder-blocks-modal": "^7.0.5",
26
- "@khanacademy/wonder-blocks-pill": "^3.0.7",
27
- "@khanacademy/wonder-blocks-search-field": "^5.0.1",
20
+ "@khanacademy/wonder-blocks-cell": "^4.0.8",
21
+ "@khanacademy/wonder-blocks-clickable": "^6.0.0",
22
+ "@khanacademy/wonder-blocks-core": "^12.0.0",
23
+ "@khanacademy/wonder-blocks-icon": "^5.0.6",
24
+ "@khanacademy/wonder-blocks-layout": "^3.0.8",
25
+ "@khanacademy/wonder-blocks-modal": "^7.0.7",
26
+ "@khanacademy/wonder-blocks-pill": "^3.0.8",
27
+ "@khanacademy/wonder-blocks-search-field": "^5.0.3",
28
28
  "@khanacademy/wonder-blocks-timing": "^6.0.1",
29
29
  "@khanacademy/wonder-blocks-tokens": "^4.1.0",
30
- "@khanacademy/wonder-blocks-typography": "^3.0.5"
30
+ "@khanacademy/wonder-blocks-typography": "^3.0.6"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@phosphor-icons/core": "^2.0.2",
@@ -41,7 +41,7 @@
41
41
  "react-window": "^1.8.10"
42
42
  },
43
43
  "devDependencies": {
44
- "@khanacademy/wonder-blocks-button": "^7.0.7",
44
+ "@khanacademy/wonder-blocks-button": "^7.0.8",
45
45
  "@khanacademy/wb-dev-build-settings": "^2.0.0"
46
46
  }
47
47
  }