@homebound/beam 2.416.7 → 2.417.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/dist/index.js CHANGED
@@ -8993,7 +8993,7 @@ function Popover(props) {
8993
8993
  // src/inputs/internal/ComboBoxBase.tsx
8994
8994
  import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo17, useRef as useRef24, useState as useState21 } from "react";
8995
8995
  import { useButton as useButton7, useComboBox as useComboBox2, useFilter as useFilter4, useOverlayPosition as useOverlayPosition4 } from "react-aria";
8996
- import { Item as Item4, useComboBoxState as useComboBoxState2, useMultipleSelectionState as useMultipleSelectionState2 } from "react-stately";
8996
+ import { Item as Item4, useComboBoxState as useComboBoxState2 } from "react-stately";
8997
8997
 
8998
8998
  // src/inputs/internal/ComboBoxInput.tsx
8999
8999
  import {
@@ -9071,7 +9071,7 @@ import React9, {
9071
9071
  useState as useState19
9072
9072
  } from "react";
9073
9073
  import { useButton as useButton6, useComboBox, useFilter as useFilter3, useOverlayPosition as useOverlayPosition3 } from "react-aria";
9074
- import { Item as Item3, useComboBoxState, useMultipleSelectionState } from "react-stately";
9074
+ import { Item as Item3, useComboBoxState } from "react-stately";
9075
9075
 
9076
9076
  // src/inputs/internal/ListBox.tsx
9077
9077
  import { useEffect as useEffect13, useRef as useRef22, useState as useState18 } from "react";
@@ -10241,16 +10241,13 @@ function TreeSelectFieldBase(props) {
10241
10241
  const state = useComboBoxState({
10242
10242
  ...comboBoxProps,
10243
10243
  allowsEmptyCollection: true,
10244
- allowsCustomValue: true
10245
- });
10246
- state.selectionManager.state = useMultipleSelectionState({
10247
10244
  selectionMode: "multiple",
10248
- selectedKeys: fieldState.selectedKeys,
10249
- disabledKeys: Object.keys(disabledOptionsWithReasons),
10250
- onSelectionChange: (newKeys) => {
10251
- if (newKeys === "all") {
10252
- return;
10253
- }
10245
+ // Prevent commitValue from calling onChange with stale displayValue on blur.
10246
+ // Menu close is handled manually in ComboBoxInput's onBlur via state.toggle().
10247
+ shouldCloseOnBlur: false,
10248
+ value: fieldState.selectedKeys,
10249
+ onChange: (newValue) => {
10250
+ const newKeys = new Set(newValue);
10254
10251
  const existingKeys = state.selectionManager.selectedKeys;
10255
10252
  const addedKeys = new Set([...newKeys].filter((x) => !existingKeys.has(x)));
10256
10253
  const removedKeys = new Set([...existingKeys].filter((x) => !newKeys.has(x)));
@@ -10371,7 +10368,9 @@ function TreeSelectFieldBase(props) {
10371
10368
  scrollRef: listBoxRef,
10372
10369
  shouldFlip: true,
10373
10370
  isOpen: state.isOpen,
10374
- onClose: state.close,
10371
+ // Use toggle() instead of close() to avoid commitValue(), which calls onChange(displayValue)
10372
+ // with a potentially stale controlled value. See ComboBoxBase.tsx for details.
10373
+ onClose: () => state.toggle(),
10375
10374
  placement: "bottom left",
10376
10375
  offset: borderless ? 8 : 4
10377
10376
  });
@@ -10415,7 +10414,7 @@ function TreeSelectFieldBase(props) {
10415
10414
  triggerRef,
10416
10415
  popoverRef,
10417
10416
  positionProps,
10418
- onClose: () => state.close(),
10417
+ onClose: () => state.toggle(),
10419
10418
  isOpen: state.isOpen,
10420
10419
  minWidth: 320,
10421
10420
  children: /* @__PURE__ */ jsx55(
@@ -10537,46 +10536,31 @@ function ComboBoxInput(props) {
10537
10536
  // Not merging the following as we want them to overwrite existing events
10538
10537
  ...{
10539
10538
  onKeyDown: (e) => {
10540
- if (isMultiSelect) {
10541
- if (isTree) {
10542
- const focusedKey = state.selectionManager.focusedKey;
10543
- if (focusedKey == null) return;
10544
- const item = state.collection.getItem(focusedKey);
10545
- if (item && (e.key === "ArrowRight" || e.key === "ArrowLeft")) {
10546
- if (!isLeveledNode(item)) return;
10547
- const leveledOption = item.value;
10548
- if (!leveledOption) return;
10549
- const [option] = leveledOption;
10550
- e.stopPropagation();
10551
- e.preventDefault();
10552
- if (option && option.children && option.children.length > 0) {
10553
- if (collapsedKeys.includes(item.key) && e.key === "ArrowRight") {
10554
- setCollapsedKeys((prevKeys) => prevKeys.filter((k) => k !== item.key));
10555
- } else if (!collapsedKeys.includes(item.key) && e.key === "ArrowLeft") {
10556
- setCollapsedKeys((prevKeys) => [...prevKeys, item.key]);
10557
- }
10539
+ if (isMultiSelect && isTree) {
10540
+ const focusedKey = state.selectionManager.focusedKey;
10541
+ if (focusedKey == null) return;
10542
+ const item = state.collection.getItem(focusedKey);
10543
+ if (item && (e.key === "ArrowRight" || e.key === "ArrowLeft")) {
10544
+ if (!isLeveledNode(item)) return;
10545
+ const leveledOption = item.value;
10546
+ if (!leveledOption) return;
10547
+ const [option] = leveledOption;
10548
+ e.stopPropagation();
10549
+ e.preventDefault();
10550
+ if (option && option.children && option.children.length > 0) {
10551
+ if (collapsedKeys.includes(item.key) && e.key === "ArrowRight") {
10552
+ setCollapsedKeys((prevKeys) => prevKeys.filter((k) => k !== item.key));
10553
+ } else if (!collapsedKeys.includes(item.key) && e.key === "ArrowLeft") {
10554
+ setCollapsedKeys((prevKeys) => [...prevKeys, item.key]);
10558
10555
  }
10559
- return;
10560
10556
  }
10561
- }
10562
- if (e.key === "Enter") {
10563
- if (state.isOpen) {
10564
- e.preventDefault();
10565
- }
10566
- const focusedKey = state.selectionManager.focusedKey;
10567
- if (focusedKey != null) {
10568
- state.selectionManager.toggleSelection(focusedKey);
10569
- }
10570
- return;
10571
- }
10572
- if (e.key === "Escape") {
10573
- state.close();
10574
10557
  return;
10575
10558
  }
10576
10559
  }
10577
- if (e.key === "Escape") {
10578
- state.close();
10579
- resetField();
10560
+ if (e.key === "Enter" && state.isOpen) {
10561
+ e.preventDefault();
10562
+ }
10563
+ if (isMultiSelect && e.key === "Tab") {
10580
10564
  return;
10581
10565
  }
10582
10566
  inputProps.onKeyDown && inputProps.onKeyDown(e);
@@ -10592,7 +10576,10 @@ function ComboBoxInput(props) {
10592
10576
  }
10593
10577
  setIsFocused(false);
10594
10578
  maybeCall(onBlur);
10595
- state.close();
10579
+ state.setFocused(false);
10580
+ if (isMultiSelect && state.isOpen) {
10581
+ state.toggle();
10582
+ }
10596
10583
  resetField();
10597
10584
  },
10598
10585
  onFocus: () => {
@@ -10704,27 +10691,6 @@ function ComboBoxBase(props) {
10704
10691
  function resetField() {
10705
10692
  setFieldState((prevState) => ({ ...prevState, searchValue: void 0 }));
10706
10693
  }
10707
- function onSelectionChange(keys) {
10708
- if (keys === "all") {
10709
- return;
10710
- }
10711
- const selectionChanged = !(keys.size === state.selectionManager.selectedKeys.size && [...keys].every((value) => state.selectionManager.selectedKeys.has(value)));
10712
- if (multiselect && keys.size === 0) {
10713
- selectionChanged && onSelect([], []);
10714
- return;
10715
- }
10716
- const selectedKeys2 = [...keys.values()];
10717
- const selectedOptions2 = options.filter((o) => selectedKeys2.includes(valueToKey(getOptionValue(o))));
10718
- if (!multiselect && selectedOptions2[0] === addNewOption && onAddNew) {
10719
- onAddNew(fieldState.inputValue);
10720
- state.close();
10721
- return;
10722
- }
10723
- selectionChanged && onSelect(selectedKeys2.map(keyToValue), selectedOptions2);
10724
- if (!multiselect) {
10725
- state.close();
10726
- }
10727
- }
10728
10694
  function onInputChange(value) {
10729
10695
  if (value !== fieldState.inputValue) {
10730
10696
  setFieldState((prevState) => ({ ...prevState, inputValue: value, searchValue: value }));
@@ -10758,6 +10724,9 @@ function ComboBoxBase(props) {
10758
10724
  (item) => /* @__PURE__ */ jsx57(Item4, { textValue: getOptionLabel(item), children: getOptionMenuLabel(item) }, valueToKey(getOptionValue(item))),
10759
10725
  [getOptionValue, getOptionLabel, getOptionMenuLabel]
10760
10726
  );
10727
+ const selectedKeys = useMemo17(() => {
10728
+ return selectedOptions.map((o) => valueToKey(getOptionValue(o)));
10729
+ }, [selectedOptions, getOptionValue]);
10761
10730
  const comboBoxProps = {
10762
10731
  ...otherProps,
10763
10732
  disabledKeys: Object.keys(disabledOptionsWithReasons),
@@ -10772,29 +10741,36 @@ function ComboBoxBase(props) {
10772
10741
  const state = useComboBoxState2({
10773
10742
  ...comboBoxProps,
10774
10743
  allowsEmptyCollection: true,
10775
- // We don't really allow custom values, as we reset the input value once a user `blur`s the input field.
10776
- // Though, setting `allowsCustomValue: true` prevents React-Aria/Stately from attempting to reset the input field's value when the menu closes.
10777
- allowsCustomValue: true,
10778
- // useComboBoxState.onSelectionChange will be executed if a keyboard interaction (Enter key) is used to select an item
10779
- onSelectionChange: (key) => {
10780
- if (key) {
10781
- const selectedKeys2 = state.selectionManager.selectedKeys;
10782
- const newSelection = new Set(!multiselect ? [key] : [...selectedKeys2, key]);
10783
- state.selectionManager.setSelectedKeys(newSelection);
10744
+ selectionMode: multiselect ? "multiple" : "single",
10745
+ // For multi-select, disable close-on-blur to prevent `commitValue` from calling `onChange`
10746
+ // with a stale `displayValue` (the controlled `value` prop before React re-renders).
10747
+ // We handle menu close manually in ComboBoxInput's onBlur via `state.toggle()`.
10748
+ ...multiselect ? { shouldCloseOnBlur: false } : {},
10749
+ // Use the new value/onChange API for native multi-select support
10750
+ value: multiselect ? selectedKeys : selectedKeys[0] ?? null,
10751
+ // Don't call state.close() inside onChange `state.close` is mapped to `commitValue` which
10752
+ // re-triggers onChange, causing an infinite loop. The native state handles menu close automatically
10753
+ // for single-select (closes on value change) and multi-select (stays open).
10754
+ onChange: (newValue) => {
10755
+ if (multiselect) {
10756
+ const keys = newValue ?? [];
10757
+ const newSelectedOptions = options.filter((o) => keys.includes(valueToKey(getOptionValue(o))));
10758
+ onSelect(keys.map(keyToValue), newSelectedOptions);
10759
+ } else {
10760
+ const key = newValue;
10761
+ if (key === null || key === void 0) {
10762
+ onSelect([], []);
10763
+ return;
10764
+ }
10765
+ const selectedOption = options.find((o) => valueToKey(getOptionValue(o)) === key);
10766
+ if (selectedOption === addNewOption && onAddNew) {
10767
+ onAddNew(fieldState.inputValue);
10768
+ return;
10769
+ }
10770
+ onSelect([keyToValue(key)], selectedOption ? [selectedOption] : []);
10784
10771
  }
10785
10772
  }
10786
10773
  });
10787
- const selectedKeys = useMemo17(() => {
10788
- return selectedOptions.map((o) => valueToKey(getOptionValue(o)));
10789
- }, [selectedOptions, getOptionValue]);
10790
- state.selectionManager.state = useMultipleSelectionState2({
10791
- selectionMode: multiselect ? "multiple" : "single",
10792
- // Do not allow an empty selection if single select mode
10793
- disallowEmptySelection: !multiselect,
10794
- selectedKeys,
10795
- onSelectionChange,
10796
- disabledKeys: Object.keys(disabledOptionsWithReasons)
10797
- });
10798
10774
  const [debouncedSearch] = useDebounce(searchValue, 300);
10799
10775
  useEffect15(() => {
10800
10776
  if (state.isOpen && multiselect && !debouncedSearch) {
@@ -10836,7 +10812,10 @@ function ComboBoxBase(props) {
10836
10812
  scrollRef: listBoxRef,
10837
10813
  shouldFlip: true,
10838
10814
  isOpen: state.isOpen,
10839
- onClose: state.close,
10815
+ // For multi-select, use toggle() instead of close() to avoid commitValue(), which
10816
+ // calls onChange(displayValue) with a potentially stale controlled value. For single-select,
10817
+ // close() (i.e. commitValue) is correct — it commits the input text to the selected value.
10818
+ onClose: multiselect ? () => state.toggle() : state.close,
10840
10819
  placement: "bottom left",
10841
10820
  offset: borderless ? 8 : 4
10842
10821
  });
@@ -10878,7 +10857,7 @@ function ComboBoxBase(props) {
10878
10857
  triggerRef,
10879
10858
  popoverRef,
10880
10859
  positionProps,
10881
- onClose: () => state.close(),
10860
+ onClose: () => multiselect ? state.toggle() : state.close(),
10882
10861
  isOpen: state.isOpen,
10883
10862
  minWidth: 200,
10884
10863
  children: /* @__PURE__ */ jsx57(