@khanacademy/wonder-blocks-dropdown 5.5.1 → 5.5.3

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,17 @@
1
1
  # @khanacademy/wonder-blocks-dropdown
2
2
 
3
+ ## 5.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - @khanacademy/wonder-blocks-search-field@2.2.27
8
+
9
+ ## 5.5.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 1e097c20: Change useListbox logic to allow passing a handler for when the selected value changes (instead of relying on useEffect in the caller)
14
+
3
15
  ## 5.5.1
4
16
 
5
17
  ### Patch Changes
package/dist/es/index.js CHANGED
@@ -2398,6 +2398,7 @@ function useListbox({
2398
2398
  disabled,
2399
2399
  disableSpaceSelection,
2400
2400
  id,
2401
+ onChange,
2401
2402
  selectionType = "single",
2402
2403
  value
2403
2404
  }) {
@@ -2415,7 +2416,7 @@ function useListbox({
2415
2416
  setFocusedIndex(index);
2416
2417
  };
2417
2418
  const focusPreviousItem = React.useCallback(() => {
2418
- if (focusedIndex === 0) {
2419
+ if (focusedIndex <= 0) {
2419
2420
  focusItem(options.length - 1);
2420
2421
  } else {
2421
2422
  focusItem(focusedIndex - 1);
@@ -2435,13 +2436,19 @@ function useListbox({
2435
2436
  }
2436
2437
  if (selectionType === "single") {
2437
2438
  setSelected(optionItem.props.value);
2439
+ if (onChange) {
2440
+ onChange(optionItem.props.value);
2441
+ }
2438
2442
  } else {
2439
2443
  setSelected(prevSelected => {
2440
2444
  const newSelectedValue = updateMultipleSelection(prevSelected, optionItem.props.value);
2445
+ if (onChange) {
2446
+ onChange(newSelectedValue);
2447
+ }
2441
2448
  return newSelectedValue;
2442
2449
  });
2443
2450
  }
2444
- }, [options, selectionType]);
2451
+ }, [onChange, options, selectionType]);
2445
2452
  const handleKeyDown = React.useCallback(event => {
2446
2453
  const {
2447
2454
  key
@@ -2860,6 +2867,7 @@ function Combobox({
2860
2867
  children: currentOptions,
2861
2868
  disabled,
2862
2869
  id: uniqueId,
2870
+ onChange: value => handleChange(value),
2863
2871
  value: valueState,
2864
2872
  disableSpaceSelection: true,
2865
2873
  selectionType
@@ -2868,7 +2876,15 @@ function Combobox({
2868
2876
  const labelFromSelected = itemFromSelected ? getLabel(itemFromSelected) : "";
2869
2877
  const initialValue = typeof value === "string" ? labelFromSelected : "";
2870
2878
  const [inputValue, setInputValue] = React.useState(initialValue);
2871
- const updateOpenState = React.useCallback(newState => {
2879
+ const {
2880
+ focusedMultiSelectIndex,
2881
+ handleKeyDown: handleMultipleSelectionKeyDown
2882
+ } = useMultipleSelection({
2883
+ inputValue,
2884
+ selected,
2885
+ setSelected
2886
+ });
2887
+ const updateOpenState = React.useCallback((newState, selectedLabel = labelFromSelected) => {
2872
2888
  if (disabled || newState === openState) {
2873
2889
  return;
2874
2890
  }
@@ -2877,53 +2893,39 @@ function Combobox({
2877
2893
  }
2878
2894
  if (!newState) {
2879
2895
  setFocusedIndex(-1);
2880
- const isSingleSelection = selectionType === "single" && typeof selected === "string";
2881
- if (selectionType === "multiple" || isSingleSelection && (selected == null ? void 0 : selected.length) === 0) {
2896
+ if (selectionType === "multiple") {
2882
2897
  setInputValue("");
2883
- }
2884
- if (isSingleSelection && (selected == null ? void 0 : selected.length) > 0) {
2885
- setInputValue(labelFromSelected);
2898
+ } else {
2899
+ setInputValue(selectedLabel != null ? selectedLabel : "");
2886
2900
  }
2887
2901
  setCurrentOptions(children);
2888
2902
  }
2889
2903
  onToggle == null ? void 0 : onToggle(newState);
2890
- }, [children, disabled, isControlled, labelFromSelected, onToggle, openState, selected, selectionType, setFocusedIndex]);
2891
- React.useEffect(() => {
2892
- if (openState) {
2893
- var _comboboxRef$current;
2894
- (_comboboxRef$current = comboboxRef.current) == null ? void 0 : _comboboxRef$current.focus();
2904
+ }, [children, disabled, isControlled, labelFromSelected, onToggle, openState, selectionType, setFocusedIndex]);
2905
+ const handleChange = React.useCallback(value => {
2906
+ if (value !== valueState) {
2907
+ setSelectedValue(value);
2908
+ onChange == null ? void 0 : onChange(value);
2895
2909
  }
2896
- if (selected === valueState) {
2897
- return;
2898
- }
2899
- if (selectionType === "single" && typeof selected === "string") {
2910
+ if (selectionType === "single" && typeof value === "string") {
2900
2911
  var _renderList$find2;
2901
- const itemFromSelected = (_renderList$find2 = renderList.find(item => item.props.value === selected)) == null ? void 0 : _renderList$find2.props;
2912
+ const itemFromSelected = (_renderList$find2 = renderList.find(item => item.props.value === value)) == null ? void 0 : _renderList$find2.props;
2902
2913
  const labelFromSelected = itemFromSelected ? getLabel(itemFromSelected) : "";
2903
- setInputValue(labelFromSelected);
2904
- setSelectedValue(selected);
2905
- onChange == null ? void 0 : onChange(selected);
2906
- updateOpenState(false);
2907
- } else if (Array.isArray(selected)) {
2914
+ updateOpenState(false, labelFromSelected);
2915
+ } else if (Array.isArray(value)) {
2908
2916
  setInputValue("");
2909
- setSelectedValue(selected);
2910
- onChange == null ? void 0 : onChange(selected);
2911
2917
  setCurrentOptions(children);
2912
2918
  }
2913
- }, [renderList, onChange, openState, selected, selectionType, updateOpenState, value, valueState, children]);
2914
- const {
2915
- focusedMultiSelectIndex,
2916
- handleKeyDown: handleMultipleSelectionKeyDown
2917
- } = useMultipleSelection({
2918
- inputValue,
2919
- selected,
2920
- setSelected
2921
- });
2919
+ }, [children, onChange, renderList, selectionType, updateOpenState, valueState]);
2922
2920
  const focusOnFilteredItem = React.useCallback((filtered, value) => {
2923
2921
  const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2924
2922
  const itemIndex = filtered.findIndex(item => getLabel(item.props).normalize("NFC").toLowerCase().trim().includes(lowercasedSearchText));
2925
2923
  setFocusedIndex(itemIndex);
2926
2924
  }, [setFocusedIndex]);
2925
+ const filterItems = React.useCallback(value => {
2926
+ const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2927
+ return children.filter(item => getLabel(item.props).normalize("NFC").trim().toLowerCase().indexOf(lowercasedSearchText) > -1);
2928
+ }, [children]);
2927
2929
  const onKeyDown = event => {
2928
2930
  const {
2929
2931
  key
@@ -2942,24 +2944,29 @@ function Combobox({
2942
2944
  }
2943
2945
  handleKeyDown(event);
2944
2946
  };
2945
- const selectedLabels = React.useMemo(() => {
2946
- return children.filter(item => selected == null ? void 0 : selected.includes(item.props.value)).map(item => getLabel(item.props));
2947
- }, [children, selected]);
2948
2947
  const handleOnRemove = React.useCallback(value => {
2949
2948
  const selectedValues = selected;
2950
2949
  const newValues = selectedValues.filter(selectedValue => selectedValue !== value);
2951
2950
  setSelected(newValues);
2952
2951
  }, [selected, setSelected]);
2952
+ React.useEffect(() => {
2953
+ if (openState) {
2954
+ var _comboboxRef$current;
2955
+ (_comboboxRef$current = comboboxRef.current) == null ? void 0 : _comboboxRef$current.focus();
2956
+ }
2957
+ }, [openState]);
2958
+ const selectedLabels = React.useMemo(() => {
2959
+ if (Array.isArray(selected)) {
2960
+ return selected.map(value => {
2961
+ const item = children.find(item => item.props.value === value);
2962
+ return item ? getLabel(item == null ? void 0 : item.props) : "";
2963
+ });
2964
+ }
2965
+ return [labelFromSelected];
2966
+ }, [children, labelFromSelected, selected]);
2953
2967
  const pillIdPrefix = id ? `${id}-pill-` : ids.get("pill");
2954
2968
  const currentActiveDescendant = !openState ? undefined : focusedIndex >= 0 ? (_renderList$focusedIn = renderList[focusedIndex]) == null ? void 0 : (_renderList$focusedIn2 = _renderList$focusedIn.props) == null ? void 0 : _renderList$focusedIn2.id : pillIdPrefix + focusedMultiSelectIndex;
2955
2969
  const controlledWidget = !openState ? undefined : focusedIndex >= 0 ? uniqueId : pillIdPrefix;
2956
- const filterItems = React.useCallback(value => {
2957
- return children.filter(item => {
2958
- const lowerCasedLabel = getLabel(item.props).normalize("NFC").trim().toLowerCase();
2959
- const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2960
- return lowerCasedLabel.indexOf(lowercasedSearchText) > -1;
2961
- });
2962
- }, [children]);
2963
2970
  return React.createElement(React.Fragment, null, React.createElement(View, {
2964
2971
  onClick: () => {
2965
2972
  updateOpenState(true);
@@ -28,6 +28,10 @@ type Props = {
28
28
  * The type of selection that the listbox supports.
29
29
  */
30
30
  selectionType: "single" | "multiple";
31
+ /**
32
+ * Callback that is called when the value of the listbox changes.
33
+ */
34
+ onChange?: (value: MaybeValueOrValues) => void;
31
35
  };
32
36
  /**
33
37
  * Hook for managing the state of a listbox.
@@ -38,7 +42,7 @@ type Props = {
38
42
  * - Keyboard navigation.
39
43
  * - Selection management.
40
44
  */
41
- export declare function useListbox({ children: options, disabled, disableSpaceSelection, id, selectionType, value, }: Props): {
45
+ export declare function useListbox({ children: options, disabled, disableSpaceSelection, id, onChange, selectionType, value, }: Props): {
42
46
  isListboxFocused: boolean;
43
47
  focusedIndex: number;
44
48
  setFocusedIndex: React.Dispatch<React.SetStateAction<number>>;
package/dist/index.js CHANGED
@@ -2434,6 +2434,7 @@ function useListbox({
2434
2434
  disabled,
2435
2435
  disableSpaceSelection,
2436
2436
  id,
2437
+ onChange,
2437
2438
  selectionType = "single",
2438
2439
  value
2439
2440
  }) {
@@ -2451,7 +2452,7 @@ function useListbox({
2451
2452
  setFocusedIndex(index);
2452
2453
  };
2453
2454
  const focusPreviousItem = React__namespace.useCallback(() => {
2454
- if (focusedIndex === 0) {
2455
+ if (focusedIndex <= 0) {
2455
2456
  focusItem(options.length - 1);
2456
2457
  } else {
2457
2458
  focusItem(focusedIndex - 1);
@@ -2471,13 +2472,19 @@ function useListbox({
2471
2472
  }
2472
2473
  if (selectionType === "single") {
2473
2474
  setSelected(optionItem.props.value);
2475
+ if (onChange) {
2476
+ onChange(optionItem.props.value);
2477
+ }
2474
2478
  } else {
2475
2479
  setSelected(prevSelected => {
2476
2480
  const newSelectedValue = updateMultipleSelection(prevSelected, optionItem.props.value);
2481
+ if (onChange) {
2482
+ onChange(newSelectedValue);
2483
+ }
2477
2484
  return newSelectedValue;
2478
2485
  });
2479
2486
  }
2480
- }, [options, selectionType]);
2487
+ }, [onChange, options, selectionType]);
2481
2488
  const handleKeyDown = React__namespace.useCallback(event => {
2482
2489
  const {
2483
2490
  key
@@ -2896,6 +2903,7 @@ function Combobox({
2896
2903
  children: currentOptions,
2897
2904
  disabled,
2898
2905
  id: uniqueId,
2906
+ onChange: value => handleChange(value),
2899
2907
  value: valueState,
2900
2908
  disableSpaceSelection: true,
2901
2909
  selectionType
@@ -2904,7 +2912,15 @@ function Combobox({
2904
2912
  const labelFromSelected = itemFromSelected ? getLabel(itemFromSelected) : "";
2905
2913
  const initialValue = typeof value === "string" ? labelFromSelected : "";
2906
2914
  const [inputValue, setInputValue] = React__namespace.useState(initialValue);
2907
- const updateOpenState = React__namespace.useCallback(newState => {
2915
+ const {
2916
+ focusedMultiSelectIndex,
2917
+ handleKeyDown: handleMultipleSelectionKeyDown
2918
+ } = useMultipleSelection({
2919
+ inputValue,
2920
+ selected,
2921
+ setSelected
2922
+ });
2923
+ const updateOpenState = React__namespace.useCallback((newState, selectedLabel = labelFromSelected) => {
2908
2924
  if (disabled || newState === openState) {
2909
2925
  return;
2910
2926
  }
@@ -2913,53 +2929,39 @@ function Combobox({
2913
2929
  }
2914
2930
  if (!newState) {
2915
2931
  setFocusedIndex(-1);
2916
- const isSingleSelection = selectionType === "single" && typeof selected === "string";
2917
- if (selectionType === "multiple" || isSingleSelection && (selected == null ? void 0 : selected.length) === 0) {
2932
+ if (selectionType === "multiple") {
2918
2933
  setInputValue("");
2919
- }
2920
- if (isSingleSelection && (selected == null ? void 0 : selected.length) > 0) {
2921
- setInputValue(labelFromSelected);
2934
+ } else {
2935
+ setInputValue(selectedLabel != null ? selectedLabel : "");
2922
2936
  }
2923
2937
  setCurrentOptions(children);
2924
2938
  }
2925
2939
  onToggle == null ? void 0 : onToggle(newState);
2926
- }, [children, disabled, isControlled, labelFromSelected, onToggle, openState, selected, selectionType, setFocusedIndex]);
2927
- React__namespace.useEffect(() => {
2928
- if (openState) {
2929
- var _comboboxRef$current;
2930
- (_comboboxRef$current = comboboxRef.current) == null ? void 0 : _comboboxRef$current.focus();
2940
+ }, [children, disabled, isControlled, labelFromSelected, onToggle, openState, selectionType, setFocusedIndex]);
2941
+ const handleChange = React__namespace.useCallback(value => {
2942
+ if (value !== valueState) {
2943
+ setSelectedValue(value);
2944
+ onChange == null ? void 0 : onChange(value);
2931
2945
  }
2932
- if (selected === valueState) {
2933
- return;
2934
- }
2935
- if (selectionType === "single" && typeof selected === "string") {
2946
+ if (selectionType === "single" && typeof value === "string") {
2936
2947
  var _renderList$find2;
2937
- const itemFromSelected = (_renderList$find2 = renderList.find(item => item.props.value === selected)) == null ? void 0 : _renderList$find2.props;
2948
+ const itemFromSelected = (_renderList$find2 = renderList.find(item => item.props.value === value)) == null ? void 0 : _renderList$find2.props;
2938
2949
  const labelFromSelected = itemFromSelected ? getLabel(itemFromSelected) : "";
2939
- setInputValue(labelFromSelected);
2940
- setSelectedValue(selected);
2941
- onChange == null ? void 0 : onChange(selected);
2942
- updateOpenState(false);
2943
- } else if (Array.isArray(selected)) {
2950
+ updateOpenState(false, labelFromSelected);
2951
+ } else if (Array.isArray(value)) {
2944
2952
  setInputValue("");
2945
- setSelectedValue(selected);
2946
- onChange == null ? void 0 : onChange(selected);
2947
2953
  setCurrentOptions(children);
2948
2954
  }
2949
- }, [renderList, onChange, openState, selected, selectionType, updateOpenState, value, valueState, children]);
2950
- const {
2951
- focusedMultiSelectIndex,
2952
- handleKeyDown: handleMultipleSelectionKeyDown
2953
- } = useMultipleSelection({
2954
- inputValue,
2955
- selected,
2956
- setSelected
2957
- });
2955
+ }, [children, onChange, renderList, selectionType, updateOpenState, valueState]);
2958
2956
  const focusOnFilteredItem = React__namespace.useCallback((filtered, value) => {
2959
2957
  const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2960
2958
  const itemIndex = filtered.findIndex(item => getLabel(item.props).normalize("NFC").toLowerCase().trim().includes(lowercasedSearchText));
2961
2959
  setFocusedIndex(itemIndex);
2962
2960
  }, [setFocusedIndex]);
2961
+ const filterItems = React__namespace.useCallback(value => {
2962
+ const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2963
+ return children.filter(item => getLabel(item.props).normalize("NFC").trim().toLowerCase().indexOf(lowercasedSearchText) > -1);
2964
+ }, [children]);
2963
2965
  const onKeyDown = event => {
2964
2966
  const {
2965
2967
  key
@@ -2978,24 +2980,29 @@ function Combobox({
2978
2980
  }
2979
2981
  handleKeyDown(event);
2980
2982
  };
2981
- const selectedLabels = React__namespace.useMemo(() => {
2982
- return children.filter(item => selected == null ? void 0 : selected.includes(item.props.value)).map(item => getLabel(item.props));
2983
- }, [children, selected]);
2984
2983
  const handleOnRemove = React__namespace.useCallback(value => {
2985
2984
  const selectedValues = selected;
2986
2985
  const newValues = selectedValues.filter(selectedValue => selectedValue !== value);
2987
2986
  setSelected(newValues);
2988
2987
  }, [selected, setSelected]);
2988
+ React__namespace.useEffect(() => {
2989
+ if (openState) {
2990
+ var _comboboxRef$current;
2991
+ (_comboboxRef$current = comboboxRef.current) == null ? void 0 : _comboboxRef$current.focus();
2992
+ }
2993
+ }, [openState]);
2994
+ const selectedLabels = React__namespace.useMemo(() => {
2995
+ if (Array.isArray(selected)) {
2996
+ return selected.map(value => {
2997
+ const item = children.find(item => item.props.value === value);
2998
+ return item ? getLabel(item == null ? void 0 : item.props) : "";
2999
+ });
3000
+ }
3001
+ return [labelFromSelected];
3002
+ }, [children, labelFromSelected, selected]);
2989
3003
  const pillIdPrefix = id ? `${id}-pill-` : ids.get("pill");
2990
3004
  const currentActiveDescendant = !openState ? undefined : focusedIndex >= 0 ? (_renderList$focusedIn = renderList[focusedIndex]) == null ? void 0 : (_renderList$focusedIn2 = _renderList$focusedIn.props) == null ? void 0 : _renderList$focusedIn2.id : pillIdPrefix + focusedMultiSelectIndex;
2991
3005
  const controlledWidget = !openState ? undefined : focusedIndex >= 0 ? uniqueId : pillIdPrefix;
2992
- const filterItems = React__namespace.useCallback(value => {
2993
- return children.filter(item => {
2994
- const lowerCasedLabel = getLabel(item.props).normalize("NFC").trim().toLowerCase();
2995
- const lowercasedSearchText = value.normalize("NFC").toLowerCase();
2996
- return lowerCasedLabel.indexOf(lowercasedSearchText) > -1;
2997
- });
2998
- }, [children]);
2999
3006
  return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksCore.View, {
3000
3007
  onClick: () => {
3001
3008
  updateOpenState(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "5.5.1",
3
+ "version": "5.5.3",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "@khanacademy/wonder-blocks-layout": "^2.2.1",
24
24
  "@khanacademy/wonder-blocks-modal": "^5.1.12",
25
25
  "@khanacademy/wonder-blocks-pill": "^2.5.1",
26
- "@khanacademy/wonder-blocks-search-field": "^2.2.26",
26
+ "@khanacademy/wonder-blocks-search-field": "^2.2.27",
27
27
  "@khanacademy/wonder-blocks-timing": "^5.0.2",
28
28
  "@khanacademy/wonder-blocks-tokens": "^2.0.1",
29
29
  "@khanacademy/wonder-blocks-typography": "^2.1.16"