@khanacademy/wonder-blocks-dropdown 5.3.8 → 5.4.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,43 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 5.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4e82c4c2: Improves support for providing ids to the opener and dropdown elements. These ids are auto-generated if they are not provided.
8
+
9
+ Also applies attributes to elements automatically for improved accessibility (`aria-controls`, `aria-haspopup`, `aria-expanded`).
10
+
11
+ - `ActionMenu`
12
+ - Adds new `dropdownId` and `id` props. If these are not provided, these ids will be generated automatically
13
+ - `aria-controls` is set to the dropdown id for both default and custom openers
14
+ - Ensure `aria-haspopup` and `aria-expanded` attributes are set on both default and custom openers
15
+ - `SingleSelect` and `MultiSelect`
16
+ - Adds new `dropdownId` prop. If this is not provided, an id will be generated automatically
17
+ - If the `id` prop is not provided, an id for the component is now generated automatically
18
+ - `aria-controls` is set to the dropdown id for both default and custom openers
19
+ - Ensure `id`, `aria-haspopup` and `aria-expanded` attributes are set on both default and custom openers
20
+
21
+ ### Patch Changes
22
+
23
+ - f099cf87: Improves accessibility of the checked status on `OptionItem` components used
24
+ within the `ActionMenu` component. The checked status is communicated to
25
+ screenreaders by using a `menuitemcheckbox` role with the `aria-checked`
26
+ attribute (instead of `aria-selected`). - `CellCore` (used by `CompactCell` and `DetailCell`) has a new optional
27
+ prop for `aria-checked` - `ClickableRole` type now supports the `menuitemcheckbox` role - `OptionItem`'s `role` prop now also supports the `menuitemcheckbox` role
28
+ - Updated dependencies [f099cf87]
29
+ - @khanacademy/wonder-blocks-clickable@4.2.3
30
+ - @khanacademy/wonder-blocks-cell@3.4.0
31
+ - @khanacademy/wonder-blocks-search-field@2.2.18
32
+ - @khanacademy/wonder-blocks-modal@5.1.6
33
+
34
+ ## 5.3.9
35
+
36
+ ### Patch Changes
37
+
38
+ - c8b273f0: Update default/resting border color to fix a color contrast issue
39
+ - @khanacademy/wonder-blocks-search-field@2.2.17
40
+
3
41
  ## 5.3.8
4
42
 
5
43
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
2
+ import { type AriaProps, type StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import DropdownOpener from "./dropdown-opener";
4
4
  import type { Item, DropdownItem, OpenerProps } from "../util/types";
5
5
  type Props = AriaProps & Readonly<{
@@ -63,6 +63,19 @@ type Props = AriaProps & Readonly<{
63
63
  * element to access pointer event state.
64
64
  */
65
65
  opener?: (openerProps: OpenerProps) => React.ReactElement<any>;
66
+ /**
67
+ * Unique identifier attached to the menu dropdown. If used, we need to
68
+ * guarantee that the ID is unique within everything rendered on a page.
69
+ * If one is not provided, one is auto-generated. It is used for the
70
+ * opener's `aria-controls` attribute for screenreaders.
71
+ */
72
+ dropdownId?: string;
73
+ /**
74
+ * Unique identifier attached to the field control. If used, we need to
75
+ * guarantee that the ID is unique within everything rendered on a page.
76
+ * If one is not provided, one is auto-generated.
77
+ */
78
+ id?: string;
66
79
  }>;
67
80
  type State = Readonly<{
68
81
  /**
@@ -103,7 +116,7 @@ export default class ActionMenu extends React.Component<Props, State> {
103
116
  getMenuItems(): Array<DropdownItem>;
104
117
  handleOpenerRef: (node?: any) => void;
105
118
  handleClick: (e: React.SyntheticEvent) => void;
106
- renderOpener(numItems: number): React.ReactElement<React.ComponentProps<typeof DropdownOpener>>;
119
+ renderOpener(numItems: number, dropdownId: string): React.ReactElement<React.ComponentProps<typeof DropdownOpener>>;
107
120
  render(): React.ReactNode;
108
121
  }
109
122
  export {};
@@ -81,6 +81,10 @@ declare const _default: React.ComponentType<Readonly<{
81
81
  * Whether the dropdown and it's interactions should be disabled.
82
82
  */
83
83
  disabled?: boolean | undefined;
84
+ /**
85
+ * Unique identifier attached to the dropdown.
86
+ */
87
+ id?: string | undefined;
84
88
  /**
85
89
  * Whether this menu should be left-aligned or right-aligned with the
86
90
  * opener component. Defaults to left-aligned.
@@ -31,6 +31,10 @@ type Props = Partial<Omit<AriaProps, "aria-disabled">> & {
31
31
  * Whether the dropdown is opened.
32
32
  */
33
33
  opened: boolean;
34
+ /**
35
+ * The unique identifier for the opener.
36
+ */
37
+ id?: string;
34
38
  };
35
39
  type DefaultProps = {
36
40
  disabled: Props["disabled"];
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
2
+ import { type AriaProps, type StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import DropdownOpener from "./dropdown-opener";
4
4
  import SelectOpener from "./select-opener";
5
5
  import OptionItem from "./option-item";
@@ -129,6 +129,13 @@ type Props = AriaProps & DefaultProps & Readonly<{
129
129
  * Test ID used for e2e testing.
130
130
  */
131
131
  testId?: string;
132
+ /**
133
+ * Unique identifier attached to the listbox dropdown. If used, we need to
134
+ * guarantee that the ID is unique within everything rendered on a page.
135
+ * If one is not provided, one is auto-generated. It is used for the
136
+ * opener's `aria-controls` attribute for screenreaders.
137
+ */
138
+ dropdownId?: string;
132
139
  }>;
133
140
  type State = Readonly<{
134
141
  /**
@@ -196,7 +203,7 @@ export default class MultiSelect extends React.Component<Props, State> {
196
203
  handleOpenerRef: (node?: any) => void;
197
204
  handleSearchTextChanged: (searchText: string) => void;
198
205
  handleClick: (e: React.SyntheticEvent) => void;
199
- renderOpener(allChildren: React.ReactElement<React.ComponentProps<typeof OptionItem>>[], isDisabled: boolean): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
206
+ renderOpener(allChildren: React.ReactElement<React.ComponentProps<typeof OptionItem>>[], isDisabled: boolean, dropdownId: string): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
200
207
  render(): React.ReactNode;
201
208
  }
202
209
  export {};
@@ -48,7 +48,7 @@ type OptionProps = AriaProps & {
48
48
  /**
49
49
  * Aria role to use, defaults to "option".
50
50
  */
51
- role: "menuitem" | "option";
51
+ role: "menuitem" | "option" | "menuitemcheckbox";
52
52
  /**
53
53
  * Test ID used for e2e testing.
54
54
  */
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
2
+ import { type AriaProps, type StyleType } from "@khanacademy/wonder-blocks-core";
3
3
  import DropdownOpener from "./dropdown-opener";
4
4
  import SelectOpener from "./select-opener";
5
5
  import OptionItem from "./option-item";
@@ -125,6 +125,13 @@ type Props = AriaProps & DefaultProps & Readonly<{
125
125
  * top. The items will be filtered by the input.
126
126
  */
127
127
  isFilterable?: boolean;
128
+ /**
129
+ * Unique identifier attached to the listbox dropdown. If used, we need to
130
+ * guarantee that the ID is unique within everything rendered on a page.
131
+ * If one is not provided, one is auto-generated. It is used for the
132
+ * opener's `aria-controls` attribute for screenreaders.
133
+ */
134
+ dropdownId?: string;
128
135
  }>;
129
136
  type State = Readonly<{
130
137
  /**
@@ -204,7 +211,7 @@ export default class SingleSelect extends React.Component<Props, State> {
204
211
  handleSearchTextChanged: (searchText: string) => void;
205
212
  handleOpenerRef: (node?: any) => void;
206
213
  handleClick: (e: React.SyntheticEvent) => void;
207
- renderOpener(isDisabled: boolean): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
214
+ renderOpener(isDisabled: boolean, dropdownId: string): React.ReactElement<React.ComponentProps<typeof DropdownOpener>> | React.ReactElement<React.ComponentProps<typeof SelectOpener>>;
208
215
  render(): React.ReactNode;
209
216
  }
210
217
  export {};
package/dist/es/index.js CHANGED
@@ -4,7 +4,7 @@ import { CompactCell, DetailCell } from '@khanacademy/wonder-blocks-cell';
4
4
  import * as tokens from '@khanacademy/wonder-blocks-tokens';
5
5
  import { spacing, color, mix, fade } from '@khanacademy/wonder-blocks-tokens';
6
6
  import { LabelMedium, LabelSmall, LabelLarge } from '@khanacademy/wonder-blocks-typography';
7
- import { View, addStyle, useUniqueIdWithMock } from '@khanacademy/wonder-blocks-core';
7
+ import { View, addStyle, IDProvider, useUniqueIdWithMock } from '@khanacademy/wonder-blocks-core';
8
8
  import { Strut } from '@khanacademy/wonder-blocks-layout';
9
9
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
10
10
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
@@ -492,7 +492,10 @@ class DropdownOpener extends React.Component {
492
492
  disabled,
493
493
  testId,
494
494
  text,
495
- opened
495
+ opened,
496
+ "aria-controls": ariaControls,
497
+ "aria-haspopup": ariaHasPopUp,
498
+ id
496
499
  } = this.props;
497
500
  const renderedChildren = this.props.children(_extends({}, eventState, {
498
501
  text,
@@ -502,6 +505,10 @@ class DropdownOpener extends React.Component {
502
505
  const childrenTestId = this.getTestIdFromProps(childrenProps);
503
506
  return React.cloneElement(renderedChildren, _extends({}, clickableChildrenProps, {
504
507
  disabled,
508
+ "aria-controls": ariaControls,
509
+ id,
510
+ "aria-expanded": opened ? "true" : "false",
511
+ "aria-haspopup": ariaHasPopUp,
505
512
  onClick: childrenProps.onClick ? e => {
506
513
  childrenProps.onClick(e);
507
514
  clickableChildrenProps.onClick(e);
@@ -1169,7 +1176,7 @@ class DropdownCore extends React.Component {
1169
1176
  this.handleItemClick(focusIndex, item);
1170
1177
  },
1171
1178
  ref: focusable ? currentRef : null,
1172
- role: itemRole
1179
+ role: populatedProps.role || itemRole
1173
1180
  }));
1174
1181
  });
1175
1182
  }
@@ -1177,12 +1184,15 @@ class DropdownCore extends React.Component {
1177
1184
  let focusCounter = 0;
1178
1185
  const itemRole = this.getItemRole();
1179
1186
  return this.props.items.map((item, index) => {
1187
+ const {
1188
+ populatedProps
1189
+ } = item;
1180
1190
  if (!SeparatorItem.isClassOf(item.component) && item.focusable) {
1181
1191
  focusCounter += 1;
1182
1192
  }
1183
1193
  const focusIndex = focusCounter - 1;
1184
1194
  return _extends({}, item, {
1185
- role: itemRole,
1195
+ role: populatedProps.role || itemRole,
1186
1196
  ref: item.focusable ? this.state.itemRefs[focusIndex] ? this.state.itemRefs[focusIndex].ref : null : null,
1187
1197
  onClick: () => {
1188
1198
  this.handleItemClick(focusIndex, item);
@@ -1221,7 +1231,8 @@ class DropdownCore extends React.Component {
1221
1231
  isFilterable,
1222
1232
  light,
1223
1233
  openerElement,
1224
- role
1234
+ role,
1235
+ id
1225
1236
  } = this.props;
1226
1237
  const openerStyle = openerElement && window.getComputedStyle(openerElement);
1227
1238
  const minDropdownWidth = openerStyle ? openerStyle.getPropertyValue("width") : 0;
@@ -1230,6 +1241,7 @@ class DropdownCore extends React.Component {
1230
1241
  style: [styles$4.dropdown, light && styles$4.light, isReferenceHidden && styles$4.hidden, dropdownStyle],
1231
1242
  testId: "dropdown-core-container"
1232
1243
  }, isFilterable && this.renderSearchField(), React.createElement(View, {
1244
+ id: id,
1233
1245
  role: role,
1234
1246
  style: [styles$4.listboxOrMenu, {
1235
1247
  minWidth: minDropdownWidth
@@ -1547,11 +1559,15 @@ class ActionMenu extends React.Component {
1547
1559
  }
1548
1560
  });
1549
1561
  } else if (OptionItem.isClassOf(item)) {
1562
+ const selected = selectedValues ? selectedValues.includes(value) : false;
1550
1563
  return _extends({}, itemObject, {
1551
1564
  populatedProps: {
1552
1565
  onToggle: this.handleOptionSelected,
1553
- selected: selectedValues ? selectedValues.includes(value) : false,
1554
- variant: "check"
1566
+ selected,
1567
+ variant: "check",
1568
+ role: "menuitemcheckbox",
1569
+ "aria-checked": selected,
1570
+ "aria-selected": undefined
1555
1571
  }
1556
1572
  });
1557
1573
  } else {
@@ -1559,14 +1575,21 @@ class ActionMenu extends React.Component {
1559
1575
  }
1560
1576
  });
1561
1577
  }
1562
- renderOpener(numItems) {
1578
+ renderOpener(numItems, dropdownId) {
1563
1579
  const {
1564
1580
  disabled,
1565
1581
  menuText,
1566
1582
  opener,
1567
- testId
1583
+ testId,
1584
+ id
1568
1585
  } = this.props;
1569
- return React.createElement(DropdownOpener, {
1586
+ return React.createElement(IDProvider, {
1587
+ id: id,
1588
+ scope: "action-menu-opener"
1589
+ }, uniqueOpenerId => React.createElement(DropdownOpener, {
1590
+ id: uniqueOpenerId,
1591
+ "aria-controls": dropdownId,
1592
+ "aria-haspopup": "menu",
1570
1593
  onClick: this.handleClick,
1571
1594
  disabled: numItems === 0 || disabled,
1572
1595
  text: menuText,
@@ -1583,29 +1606,33 @@ class ActionMenu extends React.Component {
1583
1606
  opened: !!opened,
1584
1607
  testId: testId
1585
1608
  }), menuText);
1586
- });
1609
+ }));
1587
1610
  }
1588
1611
  render() {
1589
1612
  const {
1590
1613
  alignment,
1591
1614
  dropdownStyle,
1592
1615
  style,
1593
- className
1616
+ className,
1617
+ dropdownId
1594
1618
  } = this.props;
1595
1619
  const items = this.getMenuItems();
1596
- const dropdownOpener = this.renderOpener(items.length);
1597
- return React.createElement(DropdownCore$1, {
1620
+ return React.createElement(IDProvider, {
1621
+ id: dropdownId,
1622
+ scope: "action-menu-dropdown"
1623
+ }, uniqueDropdownId => React.createElement(DropdownCore$1, {
1624
+ id: uniqueDropdownId,
1598
1625
  role: "menu",
1599
1626
  style: style,
1600
1627
  className: className,
1601
- opener: dropdownOpener,
1628
+ opener: this.renderOpener(items.length, uniqueDropdownId),
1602
1629
  alignment: alignment,
1603
1630
  open: this.state.opened,
1604
1631
  items: items,
1605
1632
  openerElement: this.openerElement,
1606
1633
  onOpenChanged: this.handleOpenChanged,
1607
1634
  dropdownStyle: [styles$2.menuTopSpace, dropdownStyle]
1608
- });
1635
+ }));
1609
1636
  }
1610
1637
  }
1611
1638
  ActionMenu.defaultProps = {
@@ -1804,13 +1831,13 @@ const _generateStyles = (light, placeholder, error) => {
1804
1831
  newStyles = {
1805
1832
  default: {
1806
1833
  background: error ? tokens.color.fadedRed8 : tokens.color.white,
1807
- borderColor: error ? tokens.color.red : tokens.color.offBlack16,
1834
+ borderColor: error ? tokens.color.red : tokens.color.offBlack50,
1808
1835
  borderWidth: tokens.border.width.hairline,
1809
1836
  color: placeholder ? tokens.color.offBlack64 : tokens.color.offBlack,
1810
1837
  ":hover:not([aria-disabled=true])": focusHoverStyling,
1811
1838
  ["@media not (hover: hover)"]: {
1812
1839
  ":hover:not([aria-disabled=true])": {
1813
- borderColor: error ? tokens.color.red : tokens.color.offBlack16,
1840
+ borderColor: error ? tokens.color.red : tokens.color.offBlack50,
1814
1841
  borderWidth: tokens.border.width.hairline,
1815
1842
  paddingLeft: tokens.spacing.medium_16,
1816
1843
  paddingRight: tokens.spacing.small_12
@@ -1932,7 +1959,7 @@ class SingleSelect extends React.Component {
1932
1959
  } = this.props;
1933
1960
  return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
1934
1961
  }
1935
- renderOpener(isDisabled) {
1962
+ renderOpener(isDisabled, dropdownId) {
1936
1963
  const _this$props = this.props,
1937
1964
  {
1938
1965
  children,
@@ -1948,23 +1975,32 @@ class SingleSelect extends React.Component {
1948
1975
  const items = React.Children.toArray(children);
1949
1976
  const selectedItem = items.find(option => option.props.value === selectedValue);
1950
1977
  const menuText = selectedItem ? getLabel(selectedItem.props) : placeholder;
1951
- const dropdownOpener = opener ? React.createElement(DropdownOpener, {
1952
- onClick: this.handleClick,
1953
- disabled: isDisabled,
1954
- ref: this.handleOpenerRef,
1955
- text: menuText,
1956
- opened: this.state.open
1957
- }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
1958
- disabled: isDisabled,
1978
+ const dropdownOpener = React.createElement(IDProvider, {
1959
1979
  id: id,
1960
- error: error,
1961
- isPlaceholder: !selectedItem,
1962
- light: light,
1963
- onOpenChanged: this.handleOpenChanged,
1964
- open: this.state.open,
1965
- ref: this.handleOpenerRef,
1966
- testId: testId
1967
- }), menuText);
1980
+ scope: "single-select-opener"
1981
+ }, uniqueOpenerId => {
1982
+ return opener ? React.createElement(DropdownOpener, {
1983
+ id: uniqueOpenerId,
1984
+ "aria-controls": dropdownId,
1985
+ "aria-haspopup": "listbox",
1986
+ onClick: this.handleClick,
1987
+ disabled: isDisabled,
1988
+ ref: this.handleOpenerRef,
1989
+ text: menuText,
1990
+ opened: this.state.open
1991
+ }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
1992
+ "aria-controls": dropdownId,
1993
+ disabled: isDisabled,
1994
+ id: uniqueOpenerId,
1995
+ error: error,
1996
+ isPlaceholder: !selectedItem,
1997
+ light: light,
1998
+ onOpenChanged: this.handleOpenChanged,
1999
+ open: this.state.open,
2000
+ ref: this.handleOpenerRef,
2001
+ testId: testId
2002
+ }), menuText);
2003
+ });
1968
2004
  return dropdownOpener;
1969
2005
  }
1970
2006
  render() {
@@ -1981,7 +2017,8 @@ class SingleSelect extends React.Component {
1981
2017
  style,
1982
2018
  "aria-invalid": ariaInvalid,
1983
2019
  "aria-required": ariaRequired,
1984
- disabled
2020
+ disabled,
2021
+ dropdownId
1985
2022
  } = this.props;
1986
2023
  const {
1987
2024
  searchText
@@ -1990,8 +2027,11 @@ class SingleSelect extends React.Component {
1990
2027
  const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
1991
2028
  const items = this.getMenuItems(allChildren);
1992
2029
  const isDisabled = numEnabledOptions === 0 || disabled;
1993
- const opener = this.renderOpener(isDisabled);
1994
- return React.createElement(DropdownCore$1, {
2030
+ return React.createElement(IDProvider, {
2031
+ id: dropdownId,
2032
+ scope: "single-select-dropdown"
2033
+ }, uniqueDropdownId => React.createElement(DropdownCore$1, {
2034
+ id: uniqueDropdownId,
1995
2035
  role: "listbox",
1996
2036
  selectionType: "single",
1997
2037
  alignment: alignment,
@@ -2003,7 +2043,7 @@ class SingleSelect extends React.Component {
2003
2043
  light: light,
2004
2044
  onOpenChanged: this.handleOpenChanged,
2005
2045
  open: this.state.open,
2006
- opener: opener,
2046
+ opener: this.renderOpener(isDisabled, uniqueDropdownId),
2007
2047
  openerElement: this.state.openerElement,
2008
2048
  style: style,
2009
2049
  className: className,
@@ -2014,7 +2054,7 @@ class SingleSelect extends React.Component {
2014
2054
  "aria-invalid": ariaInvalid,
2015
2055
  "aria-required": ariaRequired,
2016
2056
  disabled: isDisabled
2017
- });
2057
+ }));
2018
2058
  }
2019
2059
  }
2020
2060
  SingleSelect.defaultProps = {
@@ -2239,7 +2279,7 @@ class MultiSelect extends React.Component {
2239
2279
  }
2240
2280
  return [...lastSelectedItems, ...restOfTheChildren.map(this.mapOptionItemToDropdownItem)];
2241
2281
  }
2242
- renderOpener(allChildren, isDisabled) {
2282
+ renderOpener(allChildren, isDisabled, dropdownId) {
2243
2283
  const _this$props = this.props,
2244
2284
  {
2245
2285
  id,
@@ -2252,22 +2292,31 @@ class MultiSelect extends React.Component {
2252
2292
  noneSelected
2253
2293
  } = this.state.labels;
2254
2294
  const menuText = this.getMenuText(allChildren);
2255
- const dropdownOpener = opener ? React.createElement(DropdownOpener, {
2256
- onClick: this.handleClick,
2257
- disabled: isDisabled,
2258
- ref: this.handleOpenerRef,
2259
- text: menuText,
2260
- opened: this.state.open
2261
- }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
2262
- disabled: isDisabled,
2295
+ const dropdownOpener = React.createElement(IDProvider, {
2263
2296
  id: id,
2264
- isPlaceholder: menuText === noneSelected,
2265
- light: light,
2266
- onOpenChanged: this.handleOpenChanged,
2267
- open: this.state.open,
2268
- ref: this.handleOpenerRef,
2269
- testId: testId
2270
- }), menuText);
2297
+ scope: "multi-select-opener"
2298
+ }, uniqueOpenerId => {
2299
+ return opener ? React.createElement(DropdownOpener, {
2300
+ id: uniqueOpenerId,
2301
+ "aria-controls": dropdownId,
2302
+ "aria-haspopup": "listbox",
2303
+ onClick: this.handleClick,
2304
+ disabled: isDisabled,
2305
+ ref: this.handleOpenerRef,
2306
+ text: menuText,
2307
+ opened: this.state.open
2308
+ }, opener) : React.createElement(SelectOpener, _extends({}, sharedProps, {
2309
+ disabled: isDisabled,
2310
+ id: uniqueOpenerId,
2311
+ "aria-controls": dropdownId,
2312
+ isPlaceholder: menuText === noneSelected,
2313
+ light: light,
2314
+ onOpenChanged: this.handleOpenChanged,
2315
+ open: this.state.open,
2316
+ ref: this.handleOpenerRef,
2317
+ testId: testId
2318
+ }), menuText);
2319
+ });
2271
2320
  return dropdownOpener;
2272
2321
  }
2273
2322
  render() {
@@ -2281,7 +2330,8 @@ class MultiSelect extends React.Component {
2281
2330
  isFilterable,
2282
2331
  "aria-invalid": ariaInvalid,
2283
2332
  "aria-required": ariaRequired,
2284
- disabled
2333
+ disabled,
2334
+ dropdownId
2285
2335
  } = this.props;
2286
2336
  const {
2287
2337
  open,
@@ -2297,8 +2347,11 @@ class MultiSelect extends React.Component {
2297
2347
  const numEnabledOptions = allChildren.filter(option => !option.props.disabled).length;
2298
2348
  const filteredItems = this.getMenuItems(allChildren);
2299
2349
  const isDisabled = numEnabledOptions === 0 || disabled;
2300
- const opener = this.renderOpener(allChildren, isDisabled);
2301
- return React.createElement(DropdownCore$1, {
2350
+ return React.createElement(IDProvider, {
2351
+ id: dropdownId,
2352
+ scope: "multi-select-dropdown"
2353
+ }, uniqueDropdownId => React.createElement(DropdownCore$1, {
2354
+ id: uniqueDropdownId,
2302
2355
  role: "listbox",
2303
2356
  alignment: alignment,
2304
2357
  dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
@@ -2307,7 +2360,7 @@ class MultiSelect extends React.Component {
2307
2360
  light: light,
2308
2361
  onOpenChanged: this.handleOpenChanged,
2309
2362
  open: open,
2310
- opener: opener,
2363
+ opener: this.renderOpener(allChildren, isDisabled, uniqueDropdownId),
2311
2364
  openerElement: this.state.openerElement,
2312
2365
  selectionType: "multi",
2313
2366
  style: style,
@@ -2323,7 +2376,7 @@ class MultiSelect extends React.Component {
2323
2376
  "aria-invalid": ariaInvalid,
2324
2377
  "aria-required": ariaRequired,
2325
2378
  disabled: isDisabled
2326
- });
2379
+ }));
2327
2380
  }
2328
2381
  }
2329
2382
  MultiSelect.defaultProps = {
@@ -45,7 +45,7 @@ export declare function useListbox({ children: options, disabled, id, selectionT
45
45
  onToggle: (value: string) => unknown;
46
46
  selected: boolean;
47
47
  focused: boolean;
48
- role: "menuitem" | "option";
48
+ role: "menuitem" | "option" | "menuitemcheckbox";
49
49
  testId?: string | undefined;
50
50
  variant?: "checkbox" | "check" | undefined;
51
51
  style?: import("@khanacademy/wonder-blocks-core").StyleType;
@@ -58,7 +58,7 @@ export declare function useListbox({ children: options, disabled, id, selectionT
58
58
  subtitle2?: import("../../../wonder-blocks-cell/src/util/types").TypographyText | undefined;
59
59
  }, "style" | "label" | "id" | "value" | "onClick" | keyof import("../../../wonder-blocks-core/src/util/aria-types").AriaAttributes | "testId" | "leftAccessory" | "rightAccessory" | "labelAsText" | "variant" | "parentComponent" | "subtitle1" | "subtitle2"> & {
60
60
  disabled?: boolean | undefined;
61
- role?: "menuitem" | "option" | undefined;
61
+ role?: "menuitem" | "option" | "menuitemcheckbox" | undefined;
62
62
  selected?: boolean | undefined;
63
63
  onToggle?: ((value: string) => unknown) | undefined;
64
64
  focused?: boolean | undefined;