@khanacademy/wonder-blocks-dropdown 5.2.1 → 5.3.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
@@ -118,13 +118,13 @@ class ActionItem extends React__namespace.Component {
118
118
  style,
119
119
  testId
120
120
  } = this.props;
121
- const defaultStyle = [styles$8.wrapper, style];
121
+ const defaultStyle = [styles$9.wrapper, style];
122
122
  const labelComponent = typeof label === "string" ? React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
123
123
  lang: lang,
124
- style: styles$8.label
124
+ style: styles$9.label
125
125
  }, label) : React__namespace.cloneElement(label, _extends({
126
126
  lang,
127
- style: styles$8.label
127
+ style: styles$9.label
128
128
  }, label.props));
129
129
  return React__namespace.createElement(wonderBlocksCell.CompactCell, {
130
130
  disabled: disabled,
@@ -132,7 +132,7 @@ class ActionItem extends React__namespace.Component {
132
132
  rootStyle: defaultStyle,
133
133
  leftAccessory: leftAccessory,
134
134
  rightAccessory: rightAccessory,
135
- style: [styles$8.shared, indent && styles$8.indent],
135
+ style: [styles$9.shared, indent && styles$9.indent],
136
136
  role: role,
137
137
  testId: testId,
138
138
  title: labelComponent,
@@ -149,7 +149,7 @@ ActionItem.defaultProps = {
149
149
  role: "menuitem"
150
150
  };
151
151
  ActionItem.__IS_ACTION_ITEM__ = true;
152
- const styles$8 = aphrodite.StyleSheet.create({
152
+ const styles$9 = aphrodite.StyleSheet.create({
153
153
  wrapper: {
154
154
  minHeight: DROPDOWN_ITEM_HEIGHT,
155
155
  touchAction: "manipulation",
@@ -206,10 +206,10 @@ const Check = function Check(props) {
206
206
  return React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
207
207
  icon: checkIcon__default["default"],
208
208
  size: "small",
209
- style: [styles$7.bounds, !selected && styles$7.hide]
209
+ style: [styles$8.bounds, !selected && styles$8.hide]
210
210
  });
211
211
  };
212
- const styles$7 = aphrodite.StyleSheet.create({
212
+ const styles$8 = aphrodite.StyleSheet.create({
213
213
  bounds: {
214
214
  alignSelf: "center",
215
215
  height: tokens.spacing.medium_16,
@@ -233,7 +233,7 @@ const Checkbox = function Checkbox(props) {
233
233
  } = props;
234
234
  return React__namespace.createElement(wonderBlocksCore.View, {
235
235
  className: "checkbox",
236
- style: [styles$6.checkbox, selected && !disabled && styles$6.noBorder, disabled && styles$6.disabledCheckbox]
236
+ style: [styles$7.checkbox, selected && !disabled && styles$7.noBorder, disabled && styles$7.disabledCheckbox]
237
237
  }, selected && React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
238
238
  icon: checkIcon__default["default"],
239
239
  size: "small",
@@ -242,10 +242,10 @@ const Checkbox = function Checkbox(props) {
242
242
  width: tokens.spacing.small_12,
243
243
  height: tokens.spacing.small_12,
244
244
  margin: tokens.spacing.xxxxSmall_2
245
- }, disabled && selected && styles$6.disabledCheckFormatting]
245
+ }, disabled && selected && styles$7.disabledCheckFormatting]
246
246
  }));
247
247
  };
248
- const styles$6 = aphrodite.StyleSheet.create({
248
+ const styles$7 = aphrodite.StyleSheet.create({
249
249
  checkbox: {
250
250
  alignSelf: "center",
251
251
  minHeight: tokens.spacing.medium_16,
@@ -270,7 +270,8 @@ const styles$6 = aphrodite.StyleSheet.create({
270
270
  }
271
271
  });
272
272
 
273
- const _excluded$5 = ["disabled", "label", "role", "selected", "testId", "style", "leftAccessory", "horizontalRule", "rightAccessory", "subtitle1", "subtitle2", "value", "onClick", "onToggle", "variant"];
273
+ const _excluded$5 = ["disabled", "label", "selected", "testId", "leftAccessory", "horizontalRule", "parentComponent", "rightAccessory", "style", "subtitle1", "subtitle2", "value", "onClick", "onToggle", "variant", "role"];
274
+ const StyledListItem = wonderBlocksCore.addStyle("li");
274
275
  class OptionItem extends React__namespace.Component {
275
276
  constructor(...args) {
276
277
  super(...args);
@@ -296,31 +297,32 @@ class OptionItem extends React__namespace.Component {
296
297
  return Checkbox;
297
298
  }
298
299
  }
299
- render() {
300
+ renderCell() {
300
301
  const _this$props = this.props,
301
302
  {
302
303
  disabled,
303
304
  label,
304
- role,
305
305
  selected,
306
306
  testId,
307
- style,
308
307
  leftAccessory,
309
308
  horizontalRule,
309
+ parentComponent,
310
310
  rightAccessory,
311
+ style,
311
312
  subtitle1,
312
- subtitle2
313
+ subtitle2,
314
+ role
313
315
  } = _this$props,
314
316
  sharedProps = _objectWithoutPropertiesLoose(_this$props, _excluded$5);
315
317
  const CheckComponent = this.getCheckComponent();
316
- const defaultStyle = [styles$5.item, style];
318
+ const defaultStyle = [styles$6.item, style];
317
319
  return React__namespace.createElement(wonderBlocksCell.DetailCell, _extends({
318
320
  disabled: disabled,
319
321
  horizontalRule: horizontalRule,
320
- rootStyle: defaultStyle,
321
- style: styles$5.itemContainer,
322
- "aria-selected": selected ? "true" : "false",
323
- role: role,
322
+ rootStyle: parentComponent === "listbox" ? styles$6.listboxItem : defaultStyle,
323
+ style: styles$6.itemContainer,
324
+ "aria-selected": parentComponent !== "listbox" && selected ? "true" : "false",
325
+ role: parentComponent !== "listbox" ? role : undefined,
324
326
  testId: testId,
325
327
  leftAccessory: React__namespace.createElement(React__namespace.Fragment, null, leftAccessory ? React__namespace.createElement(wonderBlocksCore.View, {
326
328
  style: {
@@ -340,17 +342,42 @@ class OptionItem extends React__namespace.Component {
340
342
  className: "subtitle"
341
343
  }, subtitle1) : undefined,
342
344
  title: React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
343
- style: styles$5.label
345
+ style: styles$6.label
344
346
  }, label),
345
347
  subtitle2: subtitle2 ? React__namespace.createElement(wonderBlocksTypography.LabelSmall, {
346
348
  className: "subtitle"
347
349
  }, subtitle2) : undefined,
348
- onClick: this.handleClick
350
+ onClick: parentComponent !== "listbox" ? this.handleClick : undefined
349
351
  }, sharedProps));
350
352
  }
353
+ render() {
354
+ const {
355
+ disabled,
356
+ focused,
357
+ parentComponent,
358
+ role,
359
+ selected
360
+ } = this.props;
361
+ if (parentComponent === "listbox") {
362
+ return React__namespace.createElement(StyledListItem, {
363
+ onMouseDown: e => {
364
+ e.preventDefault();
365
+ },
366
+ onClick: this.handleClick,
367
+ style: [styles$6.reset, styles$6.item, focused && styles$6.itemFocused, disabled && styles$6.itemDisabled],
368
+ role: role,
369
+ "aria-selected": selected ? "true" : "false",
370
+ "aria-disabled": disabled ? "true" : "false",
371
+ id: this.props.id,
372
+ tabIndex: -1
373
+ }, this.renderCell());
374
+ }
375
+ return this.renderCell();
376
+ }
351
377
  }
352
378
  OptionItem.defaultProps = {
353
379
  disabled: false,
380
+ focused: false,
354
381
  horizontalRule: "none",
355
382
  onToggle: () => void 0,
356
383
  role: "option",
@@ -362,14 +389,33 @@ const {
362
389
  white,
363
390
  offBlack
364
391
  } = tokens.color;
365
- const styles$5 = aphrodite.StyleSheet.create({
392
+ const focusedStyle = {
393
+ borderRadius: tokens.spacing.xxxSmall_4,
394
+ outline: `${tokens.spacing.xxxxSmall_2}px solid ${tokens.color.blue}`,
395
+ outlineOffset: -tokens.spacing.xxxxSmall_2
396
+ };
397
+ const styles$6 = aphrodite.StyleSheet.create({
398
+ reset: {
399
+ margin: 0,
400
+ padding: 0,
401
+ border: 0,
402
+ background: "none",
403
+ outline: "none",
404
+ fontSize: "100%",
405
+ verticalAlign: "baseline",
406
+ textAlign: "left",
407
+ textDecoration: "none",
408
+ listStyle: "none",
409
+ cursor: "pointer"
410
+ },
411
+ listboxItem: {
412
+ backgroundColor: "transparent",
413
+ color: "inherit"
414
+ },
366
415
  item: {
416
+ backgroundColor: tokens.color.white,
367
417
  minHeight: "unset",
368
- ":focus": {
369
- borderRadius: tokens.spacing.xxxSmall_4,
370
- outline: `${tokens.spacing.xxxxSmall_2}px solid ${tokens.color.blue}`,
371
- outlineOffset: -tokens.spacing.xxxxSmall_2
372
- },
418
+ ":focus": focusedStyle,
373
419
  ":focus-visible": {
374
420
  overflow: "visible"
375
421
  },
@@ -377,6 +423,16 @@ const styles$5 = aphrodite.StyleSheet.create({
377
423
  color: white,
378
424
  background: blue
379
425
  },
426
+ [":active[aria-selected=false]"]: {},
427
+ [":hover[aria-disabled=true]"]: {
428
+ cursor: "not-allowed"
429
+ },
430
+ [":is([aria-disabled=true])"]: {
431
+ color: tokens.color.offBlack32,
432
+ ":focus-visible": {
433
+ outline: "none"
434
+ }
435
+ },
380
436
  ["@media not (hover: hover)"]: {
381
437
  [":hover[aria-disabled=false]"]: {
382
438
  color: white,
@@ -412,6 +468,10 @@ const styles$5 = aphrodite.StyleSheet.create({
412
468
  color: tokens.mix(tokens.color.fadedBlue16, white)
413
469
  }
414
470
  },
471
+ itemFocused: focusedStyle,
472
+ itemDisabled: {
473
+ outlineColor: tokens.color.offBlack32
474
+ },
415
475
  itemContainer: {
416
476
  minHeight: "unset",
417
477
  padding: `${tokens.spacing.xSmall_8 + tokens.spacing.xxxxSmall_2}px ${tokens.spacing.xSmall_8}px`,
@@ -435,13 +495,13 @@ class SeparatorItem extends React__namespace.Component {
435
495
  }
436
496
  render() {
437
497
  return React__namespace.createElement(wonderBlocksCore.View, {
438
- style: [styles$4.separator, this.props.style],
498
+ style: [styles$5.separator, this.props.style],
439
499
  "aria-hidden": "true"
440
500
  });
441
501
  }
442
502
  }
443
503
  SeparatorItem.__IS_SEPARATOR_ITEM__ = true;
444
- const styles$4 = aphrodite.StyleSheet.create({
504
+ const styles$5 = aphrodite.StyleSheet.create({
445
505
  separator: {
446
506
  boxShadow: `0 -1px ${tokens.color.offBlack16}`,
447
507
  height: 1,
@@ -1107,7 +1167,7 @@ class DropdownCore extends React__namespace.Component {
1107
1167
  const numResults = items.length;
1108
1168
  if (numResults === 0) {
1109
1169
  return React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
1110
- style: styles$3.noResult,
1170
+ style: styles$4.noResult,
1111
1171
  testId: "dropdown-core-no-results"
1112
1172
  }, noResults);
1113
1173
  }
@@ -1179,7 +1239,7 @@ class DropdownCore extends React__namespace.Component {
1179
1239
  onChange: this.handleSearchTextChanged,
1180
1240
  placeholder: labels.filter,
1181
1241
  ref: this.searchFieldRef,
1182
- style: styles$3.searchInputStyle,
1242
+ style: styles$4.searchInputStyle,
1183
1243
  value: searchText || ""
1184
1244
  });
1185
1245
  }
@@ -1197,11 +1257,11 @@ class DropdownCore extends React__namespace.Component {
1197
1257
  const minDropdownWidth = openerStyle ? openerStyle.getPropertyValue("width") : 0;
1198
1258
  return React__namespace.createElement(wonderBlocksCore.View, {
1199
1259
  onMouseUp: this.handleDropdownMouseUp,
1200
- style: [styles$3.dropdown, light && styles$3.light, isReferenceHidden && styles$3.hidden, dropdownStyle],
1260
+ style: [styles$4.dropdown, light && styles$4.light, isReferenceHidden && styles$4.hidden, dropdownStyle],
1201
1261
  testId: "dropdown-core-container"
1202
1262
  }, isFilterable && this.renderSearchField(), React__namespace.createElement(wonderBlocksCore.View, {
1203
1263
  role: role,
1204
- style: [styles$3.listboxOrMenu, {
1264
+ style: [styles$4.listboxOrMenu, {
1205
1265
  minWidth: minDropdownWidth
1206
1266
  }],
1207
1267
  "aria-invalid": role === "listbox" ? ariaInvalid : undefined,
@@ -1235,7 +1295,7 @@ class DropdownCore extends React__namespace.Component {
1235
1295
  "aria-live": "polite",
1236
1296
  "aria-atomic": "true",
1237
1297
  "aria-relevant": "additions text",
1238
- style: styles$3.srOnly,
1298
+ style: styles$4.srOnly,
1239
1299
  "data-testid": "dropdown-live-region"
1240
1300
  }, open && labels.someResults(totalItems));
1241
1301
  }
@@ -1249,7 +1309,7 @@ class DropdownCore extends React__namespace.Component {
1249
1309
  return React__namespace.createElement(wonderBlocksCore.View, {
1250
1310
  onKeyDown: this.handleKeyDown,
1251
1311
  onKeyUp: this.handleKeyUp,
1252
- style: [styles$3.menuWrapper, style],
1312
+ style: [styles$4.menuWrapper, style],
1253
1313
  className: className
1254
1314
  }, this.renderLiveRegion(), opener, open && this.renderDropdown());
1255
1315
  }
@@ -1267,7 +1327,7 @@ DropdownCore.defaultProps = {
1267
1327
  light: false,
1268
1328
  selectionType: "single"
1269
1329
  };
1270
- const styles$3 = aphrodite.StyleSheet.create({
1330
+ const styles$4 = aphrodite.StyleSheet.create({
1271
1331
  menuWrapper: {
1272
1332
  width: "fit-content"
1273
1333
  },
@@ -1400,11 +1460,11 @@ const sharedStyles = aphrodite.StyleSheet.create({
1400
1460
  position: "absolute"
1401
1461
  }
1402
1462
  });
1403
- const styles$2 = {};
1463
+ const styles$3 = {};
1404
1464
  const _generateStyles$1 = localColor => {
1405
1465
  const buttonType = localColor;
1406
- if (styles$2[buttonType]) {
1407
- return styles$2[buttonType];
1466
+ if (styles$3[buttonType]) {
1467
+ return styles$3[buttonType];
1408
1468
  }
1409
1469
  const {
1410
1470
  offBlack32
@@ -1436,8 +1496,8 @@ const _generateStyles$1 = localColor => {
1436
1496
  cursor: "default"
1437
1497
  }
1438
1498
  };
1439
- styles$2[buttonType] = aphrodite.StyleSheet.create(newStyles);
1440
- return styles$2[buttonType];
1499
+ styles$3[buttonType] = aphrodite.StyleSheet.create(newStyles);
1500
+ return styles$3[buttonType];
1441
1501
  };
1442
1502
 
1443
1503
  const _excluded$3 = ["text", "opened"];
@@ -1573,7 +1633,7 @@ class ActionMenu extends React__namespace.Component {
1573
1633
  items: items,
1574
1634
  openerElement: this.openerElement,
1575
1635
  onOpenChanged: this.handleOpenChanged,
1576
- dropdownStyle: [styles$1.menuTopSpace, dropdownStyle]
1636
+ dropdownStyle: [styles$2.menuTopSpace, dropdownStyle]
1577
1637
  });
1578
1638
  }
1579
1639
  }
@@ -1581,7 +1641,7 @@ ActionMenu.defaultProps = {
1581
1641
  alignment: "left",
1582
1642
  disabled: false
1583
1643
  };
1584
- const styles$1 = aphrodite.StyleSheet.create({
1644
+ const styles$2 = aphrodite.StyleSheet.create({
1585
1645
  caret: {
1586
1646
  marginLeft: 4
1587
1647
  },
@@ -1633,7 +1693,7 @@ class SelectOpener extends React__namespace.Component {
1633
1693
  pressed
1634
1694
  } = state;
1635
1695
  const iconColor = light ? disabled || pressed ? "currentColor" : tokens__namespace.color.white : disabled ? tokens__namespace.color.offBlack32 : tokens__namespace.color.offBlack64;
1636
- const style = [styles.shared, stateStyles.default, disabled && stateStyles.disabled, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus)];
1696
+ const style = [styles$1.shared, stateStyles.default, disabled && stateStyles.disabled, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus)];
1637
1697
  return React__namespace.createElement(StyledButton, _extends({}, sharedProps, {
1638
1698
  "aria-expanded": open ? "true" : "false",
1639
1699
  "aria-haspopup": "listbox",
@@ -1643,12 +1703,12 @@ class SelectOpener extends React__namespace.Component {
1643
1703
  style: style,
1644
1704
  type: "button"
1645
1705
  }, childrenProps), React__namespace.createElement(wonderBlocksTypography.LabelMedium, {
1646
- style: styles.text
1706
+ style: styles$1.text
1647
1707
  }, children || "\u00A0"), React__namespace.createElement(wonderBlocksIcon.PhosphorIcon, {
1648
1708
  icon: caretDownIcon__default["default"],
1649
1709
  color: iconColor,
1650
1710
  size: "small",
1651
- style: styles.caret,
1711
+ style: styles$1.caret,
1652
1712
  "aria-hidden": "true"
1653
1713
  }));
1654
1714
  });
@@ -1663,7 +1723,7 @@ SelectOpener.defaultProps = {
1663
1723
  light: false,
1664
1724
  isPlaceholder: false
1665
1725
  };
1666
- const styles = aphrodite.StyleSheet.create({
1726
+ const styles$1 = aphrodite.StyleSheet.create({
1667
1727
  shared: {
1668
1728
  position: "relative",
1669
1729
  display: "inline-flex",
@@ -2260,8 +2320,212 @@ MultiSelect.defaultProps = {
2260
2320
  selectedValues: []
2261
2321
  };
2262
2322
 
2323
+ function updateMultipleSelection(previousSelection, value = "") {
2324
+ if (!previousSelection) {
2325
+ return [value];
2326
+ }
2327
+ return previousSelection.includes(value) ? previousSelection.filter(item => item !== value) : [...previousSelection, value];
2328
+ }
2329
+
2330
+ function useListbox({
2331
+ children: options,
2332
+ disabled,
2333
+ id,
2334
+ selectionType = "single",
2335
+ value
2336
+ }) {
2337
+ const selectedValueIndex = React__namespace.useMemo(() => {
2338
+ const firstValue = Array.isArray(value) ? value[0] : value;
2339
+ if (!firstValue || firstValue === "") {
2340
+ return 0;
2341
+ }
2342
+ return options.findIndex(item => item.props.value === firstValue);
2343
+ }, [options, value]);
2344
+ const [focusedIndex, setFocusedIndex] = React__namespace.useState(selectedValueIndex);
2345
+ const [isListboxFocused, setIsListboxFocused] = React__namespace.useState(false);
2346
+ const [selected, setSelected] = React__namespace.useState(value);
2347
+ const focusItem = index => {
2348
+ setFocusedIndex(index);
2349
+ };
2350
+ const focusPreviousItem = React__namespace.useCallback(() => {
2351
+ if (focusedIndex === 0) {
2352
+ focusItem(options.length - 1);
2353
+ } else {
2354
+ focusItem(focusedIndex - 1);
2355
+ }
2356
+ }, [options, focusedIndex]);
2357
+ const focusNextItem = React__namespace.useCallback(() => {
2358
+ if (focusedIndex === options.length - 1) {
2359
+ focusItem(0);
2360
+ } else {
2361
+ focusItem(focusedIndex + 1);
2362
+ }
2363
+ }, [options, focusedIndex]);
2364
+ const selectOption = React__namespace.useCallback(index => {
2365
+ const optionItem = options[index];
2366
+ if (optionItem.props.disabled) {
2367
+ return;
2368
+ }
2369
+ if (selectionType === "single") {
2370
+ setSelected(optionItem.props.value);
2371
+ } else {
2372
+ setSelected(prevSelected => {
2373
+ const newSelectedValue = updateMultipleSelection(prevSelected, optionItem.props.value);
2374
+ return newSelectedValue;
2375
+ });
2376
+ }
2377
+ }, [options, selectionType]);
2378
+ const handleKeyDown = React__namespace.useCallback(event => {
2379
+ const {
2380
+ key
2381
+ } = event;
2382
+ switch (key) {
2383
+ case "ArrowUp":
2384
+ event.preventDefault();
2385
+ focusPreviousItem();
2386
+ return;
2387
+ case "ArrowDown":
2388
+ event.preventDefault();
2389
+ focusNextItem();
2390
+ return;
2391
+ case "Home":
2392
+ event.preventDefault();
2393
+ focusItem(0);
2394
+ return;
2395
+ case "End":
2396
+ event.preventDefault();
2397
+ focusItem(options.length - 1);
2398
+ return;
2399
+ case "Enter":
2400
+ case " ":
2401
+ event.preventDefault();
2402
+ selectOption(focusedIndex);
2403
+ return;
2404
+ }
2405
+ }, [focusNextItem, focusPreviousItem, focusedIndex, options, selectOption]);
2406
+ const handleKeyUp = React__namespace.useCallback(event => {
2407
+ switch (event.key) {
2408
+ case " ":
2409
+ event.preventDefault();
2410
+ return;
2411
+ }
2412
+ }, []);
2413
+ const handleFocus = React__namespace.useCallback(() => {
2414
+ if (!disabled) {
2415
+ setIsListboxFocused(true);
2416
+ }
2417
+ }, [disabled]);
2418
+ const handleBlur = React__namespace.useCallback(() => {
2419
+ if (!disabled) {
2420
+ setIsListboxFocused(false);
2421
+ }
2422
+ }, [disabled]);
2423
+ const handleClick = React__namespace.useCallback(value => {
2424
+ const index = options.findIndex(item => item.props.value === value);
2425
+ const isOptionDisabled = options[index].props.disabled;
2426
+ if (disabled || isOptionDisabled) {
2427
+ return;
2428
+ }
2429
+ focusItem(index);
2430
+ selectOption(index);
2431
+ }, [disabled, options, selectOption]);
2432
+ const renderList = React__namespace.useMemo(() => {
2433
+ return options.map((component, index) => {
2434
+ const isSelected = (selected == null ? void 0 : selected.includes(component.props.value)) || false;
2435
+ const optionId = id ? `${id}-option-${index}` : `option-${index}`;
2436
+ return React__namespace.cloneElement(component, {
2437
+ key: index,
2438
+ focused: isListboxFocused && index === focusedIndex,
2439
+ disabled: component.props.disabled || disabled || false,
2440
+ selected: isSelected,
2441
+ variant: selectionType === "single" ? "check" : "checkbox",
2442
+ parentComponent: "listbox",
2443
+ id: optionId,
2444
+ onToggle: () => {
2445
+ handleClick(component.props.value);
2446
+ },
2447
+ role: "option"
2448
+ });
2449
+ });
2450
+ }, [options, selected, id, isListboxFocused, focusedIndex, disabled, selectionType, handleClick]);
2451
+ return {
2452
+ isListboxFocused,
2453
+ focusedIndex,
2454
+ renderList,
2455
+ selected,
2456
+ handleKeyDown,
2457
+ handleKeyUp,
2458
+ handleFocus,
2459
+ handleBlur
2460
+ };
2461
+ }
2462
+
2463
+ function Listbox(props) {
2464
+ const {
2465
+ children,
2466
+ disabled,
2467
+ id,
2468
+ onChange,
2469
+ selectionType = "single",
2470
+ style,
2471
+ tabIndex = 0,
2472
+ testId,
2473
+ value,
2474
+ "aria-label": ariaLabel,
2475
+ "aria-labelledby": ariaLabelledby
2476
+ } = props;
2477
+ const ids = wonderBlocksCore.useUniqueIdWithMock("listbox");
2478
+ const uniqueId = id != null ? id : ids.get("id");
2479
+ const {
2480
+ focusedIndex,
2481
+ isListboxFocused,
2482
+ renderList,
2483
+ selected,
2484
+ handleKeyDown,
2485
+ handleKeyUp,
2486
+ handleFocus,
2487
+ handleBlur
2488
+ } = useListbox({
2489
+ children,
2490
+ disabled,
2491
+ id: uniqueId,
2492
+ selectionType,
2493
+ value
2494
+ });
2495
+ React__namespace.useEffect(() => {
2496
+ if (selected && selected !== value) {
2497
+ onChange == null ? void 0 : onChange(selected);
2498
+ }
2499
+ }, [onChange, selected, value]);
2500
+ return React__namespace.createElement(wonderBlocksCore.View, {
2501
+ role: "listbox",
2502
+ "aria-disabled": disabled,
2503
+ id: uniqueId,
2504
+ style: [styles.listbox, style, disabled && styles.disabled],
2505
+ tabIndex: tabIndex,
2506
+ onKeyDown: handleKeyDown,
2507
+ onKeyUp: handleKeyUp,
2508
+ onFocus: handleFocus,
2509
+ onBlur: handleBlur,
2510
+ testId: testId,
2511
+ "aria-activedescendant": isListboxFocused ? renderList[focusedIndex].props.id : undefined,
2512
+ "aria-label": ariaLabel,
2513
+ "aria-labelledby": ariaLabelledby,
2514
+ "aria-multiselectable": selectionType === "multiple"
2515
+ }, renderList);
2516
+ }
2517
+ const styles = aphrodite.StyleSheet.create({
2518
+ listbox: {
2519
+ outline: "none"
2520
+ },
2521
+ disabled: {
2522
+ color: tokens.color.offBlack64
2523
+ }
2524
+ });
2525
+
2263
2526
  exports.ActionItem = ActionItem;
2264
2527
  exports.ActionMenu = ActionMenu;
2528
+ exports.Listbox = Listbox;
2265
2529
  exports.MultiSelect = MultiSelect;
2266
2530
  exports.OptionItem = OptionItem;
2267
2531
  exports.SeparatorItem = SeparatorItem;
@@ -0,0 +1,2 @@
1
+ import { MaybeString } from "./types";
2
+ export declare function updateMultipleSelection(previousSelection: Array<MaybeString> | null | undefined, value?: string): MaybeString[];
@@ -26,4 +26,10 @@ export type OpenerProps = ClickableState & {
26
26
  text: OptionLabel;
27
27
  opened: boolean;
28
28
  };
29
- export type OptionItemComponentArray = React.ReactElement<React.ComponentProps<typeof OptionItem>>[];
29
+ export type OptionItemComponent = React.ReactElement<PropsFor<typeof OptionItem>>;
30
+ export type OptionItemComponentArray = OptionItemComponent[];
31
+ /**
32
+ * Allows optional values to be passed to the listbox.
33
+ */
34
+ export type MaybeString = string | null | undefined;
35
+ export type MaybeValueOrValues = MaybeString | Array<MaybeString>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -16,16 +16,16 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@babel/runtime": "^7.18.6",
19
- "@khanacademy/wonder-blocks-cell": "^3.3.5",
20
- "@khanacademy/wonder-blocks-clickable": "^4.2.1",
21
- "@khanacademy/wonder-blocks-core": "^6.4.0",
22
- "@khanacademy/wonder-blocks-icon": "^4.1.0",
23
- "@khanacademy/wonder-blocks-layout": "^2.0.32",
24
- "@khanacademy/wonder-blocks-modal": "^5.1.1",
25
- "@khanacademy/wonder-blocks-search-field": "^2.2.10",
26
- "@khanacademy/wonder-blocks-timing": "^4.0.2",
19
+ "@khanacademy/wonder-blocks-cell": "^3.3.6",
20
+ "@khanacademy/wonder-blocks-clickable": "^4.2.2",
21
+ "@khanacademy/wonder-blocks-core": "^6.4.1",
22
+ "@khanacademy/wonder-blocks-icon": "^4.1.1",
23
+ "@khanacademy/wonder-blocks-layout": "^2.0.33",
24
+ "@khanacademy/wonder-blocks-modal": "^5.1.3",
25
+ "@khanacademy/wonder-blocks-search-field": "^2.2.11",
26
+ "@khanacademy/wonder-blocks-timing": "^5.0.0",
27
27
  "@khanacademy/wonder-blocks-tokens": "^1.3.0",
28
- "@khanacademy/wonder-blocks-typography": "^2.1.11"
28
+ "@khanacademy/wonder-blocks-typography": "^2.1.12"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@phosphor-icons/core": "^2.0.2",
@@ -39,7 +39,7 @@
39
39
  "react-window": "^1.8.5"
40
40
  },
41
41
  "devDependencies": {
42
- "@khanacademy/wonder-blocks-button": "^6.3.1",
42
+ "@khanacademy/wonder-blocks-button": "^6.3.2",
43
43
  "@khanacademy/wb-dev-build-settings": "^1.0.0"
44
44
  }
45
45
  }