@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.cjs CHANGED
@@ -10601,16 +10601,13 @@ function TreeSelectFieldBase(props) {
10601
10601
  const state = (0, import_react_stately6.useComboBoxState)({
10602
10602
  ...comboBoxProps,
10603
10603
  allowsEmptyCollection: true,
10604
- allowsCustomValue: true
10605
- });
10606
- state.selectionManager.state = (0, import_react_stately6.useMultipleSelectionState)({
10607
10604
  selectionMode: "multiple",
10608
- selectedKeys: fieldState.selectedKeys,
10609
- disabledKeys: Object.keys(disabledOptionsWithReasons),
10610
- onSelectionChange: (newKeys) => {
10611
- if (newKeys === "all") {
10612
- return;
10613
- }
10605
+ // Prevent commitValue from calling onChange with stale displayValue on blur.
10606
+ // Menu close is handled manually in ComboBoxInput's onBlur via state.toggle().
10607
+ shouldCloseOnBlur: false,
10608
+ value: fieldState.selectedKeys,
10609
+ onChange: (newValue) => {
10610
+ const newKeys = new Set(newValue);
10614
10611
  const existingKeys = state.selectionManager.selectedKeys;
10615
10612
  const addedKeys = new Set([...newKeys].filter((x) => !existingKeys.has(x)));
10616
10613
  const removedKeys = new Set([...existingKeys].filter((x) => !newKeys.has(x)));
@@ -10731,7 +10728,9 @@ function TreeSelectFieldBase(props) {
10731
10728
  scrollRef: listBoxRef,
10732
10729
  shouldFlip: true,
10733
10730
  isOpen: state.isOpen,
10734
- onClose: state.close,
10731
+ // Use toggle() instead of close() to avoid commitValue(), which calls onChange(displayValue)
10732
+ // with a potentially stale controlled value. See ComboBoxBase.tsx for details.
10733
+ onClose: () => state.toggle(),
10735
10734
  placement: "bottom left",
10736
10735
  offset: borderless ? 8 : 4
10737
10736
  });
@@ -10775,7 +10774,7 @@ function TreeSelectFieldBase(props) {
10775
10774
  triggerRef,
10776
10775
  popoverRef,
10777
10776
  positionProps,
10778
- onClose: () => state.close(),
10777
+ onClose: () => state.toggle(),
10779
10778
  isOpen: state.isOpen,
10780
10779
  minWidth: 320,
10781
10780
  children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
@@ -10897,46 +10896,31 @@ function ComboBoxInput(props) {
10897
10896
  // Not merging the following as we want them to overwrite existing events
10898
10897
  ...{
10899
10898
  onKeyDown: (e) => {
10900
- if (isMultiSelect) {
10901
- if (isTree) {
10902
- const focusedKey = state.selectionManager.focusedKey;
10903
- if (focusedKey == null) return;
10904
- const item = state.collection.getItem(focusedKey);
10905
- if (item && (e.key === "ArrowRight" || e.key === "ArrowLeft")) {
10906
- if (!isLeveledNode(item)) return;
10907
- const leveledOption = item.value;
10908
- if (!leveledOption) return;
10909
- const [option] = leveledOption;
10910
- e.stopPropagation();
10911
- e.preventDefault();
10912
- if (option && option.children && option.children.length > 0) {
10913
- if (collapsedKeys.includes(item.key) && e.key === "ArrowRight") {
10914
- setCollapsedKeys((prevKeys) => prevKeys.filter((k) => k !== item.key));
10915
- } else if (!collapsedKeys.includes(item.key) && e.key === "ArrowLeft") {
10916
- setCollapsedKeys((prevKeys) => [...prevKeys, item.key]);
10917
- }
10899
+ if (isMultiSelect && isTree) {
10900
+ const focusedKey = state.selectionManager.focusedKey;
10901
+ if (focusedKey == null) return;
10902
+ const item = state.collection.getItem(focusedKey);
10903
+ if (item && (e.key === "ArrowRight" || e.key === "ArrowLeft")) {
10904
+ if (!isLeveledNode(item)) return;
10905
+ const leveledOption = item.value;
10906
+ if (!leveledOption) return;
10907
+ const [option] = leveledOption;
10908
+ e.stopPropagation();
10909
+ e.preventDefault();
10910
+ if (option && option.children && option.children.length > 0) {
10911
+ if (collapsedKeys.includes(item.key) && e.key === "ArrowRight") {
10912
+ setCollapsedKeys((prevKeys) => prevKeys.filter((k) => k !== item.key));
10913
+ } else if (!collapsedKeys.includes(item.key) && e.key === "ArrowLeft") {
10914
+ setCollapsedKeys((prevKeys) => [...prevKeys, item.key]);
10918
10915
  }
10919
- return;
10920
10916
  }
10921
- }
10922
- if (e.key === "Enter") {
10923
- if (state.isOpen) {
10924
- e.preventDefault();
10925
- }
10926
- const focusedKey = state.selectionManager.focusedKey;
10927
- if (focusedKey != null) {
10928
- state.selectionManager.toggleSelection(focusedKey);
10929
- }
10930
- return;
10931
- }
10932
- if (e.key === "Escape") {
10933
- state.close();
10934
10917
  return;
10935
10918
  }
10936
10919
  }
10937
- if (e.key === "Escape") {
10938
- state.close();
10939
- resetField();
10920
+ if (e.key === "Enter" && state.isOpen) {
10921
+ e.preventDefault();
10922
+ }
10923
+ if (isMultiSelect && e.key === "Tab") {
10940
10924
  return;
10941
10925
  }
10942
10926
  inputProps.onKeyDown && inputProps.onKeyDown(e);
@@ -10952,7 +10936,10 @@ function ComboBoxInput(props) {
10952
10936
  }
10953
10937
  setIsFocused(false);
10954
10938
  maybeCall(onBlur);
10955
- state.close();
10939
+ state.setFocused(false);
10940
+ if (isMultiSelect && state.isOpen) {
10941
+ state.toggle();
10942
+ }
10956
10943
  resetField();
10957
10944
  },
10958
10945
  onFocus: () => {
@@ -11064,27 +11051,6 @@ function ComboBoxBase(props) {
11064
11051
  function resetField() {
11065
11052
  setFieldState((prevState) => ({ ...prevState, searchValue: void 0 }));
11066
11053
  }
11067
- function onSelectionChange(keys) {
11068
- if (keys === "all") {
11069
- return;
11070
- }
11071
- const selectionChanged = !(keys.size === state.selectionManager.selectedKeys.size && [...keys].every((value) => state.selectionManager.selectedKeys.has(value)));
11072
- if (multiselect && keys.size === 0) {
11073
- selectionChanged && onSelect([], []);
11074
- return;
11075
- }
11076
- const selectedKeys2 = [...keys.values()];
11077
- const selectedOptions2 = options.filter((o) => selectedKeys2.includes(valueToKey(getOptionValue(o))));
11078
- if (!multiselect && selectedOptions2[0] === addNewOption && onAddNew) {
11079
- onAddNew(fieldState.inputValue);
11080
- state.close();
11081
- return;
11082
- }
11083
- selectionChanged && onSelect(selectedKeys2.map(keyToValue), selectedOptions2);
11084
- if (!multiselect) {
11085
- state.close();
11086
- }
11087
- }
11088
11054
  function onInputChange(value) {
11089
11055
  if (value !== fieldState.inputValue) {
11090
11056
  setFieldState((prevState) => ({ ...prevState, inputValue: value, searchValue: value }));
@@ -11118,6 +11084,9 @@ function ComboBoxBase(props) {
11118
11084
  (item) => /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_react_stately7.Item, { textValue: getOptionLabel(item), children: getOptionMenuLabel(item) }, valueToKey(getOptionValue(item))),
11119
11085
  [getOptionValue, getOptionLabel, getOptionMenuLabel]
11120
11086
  );
11087
+ const selectedKeys = (0, import_react49.useMemo)(() => {
11088
+ return selectedOptions.map((o) => valueToKey(getOptionValue(o)));
11089
+ }, [selectedOptions, getOptionValue]);
11121
11090
  const comboBoxProps = {
11122
11091
  ...otherProps,
11123
11092
  disabledKeys: Object.keys(disabledOptionsWithReasons),
@@ -11132,29 +11101,36 @@ function ComboBoxBase(props) {
11132
11101
  const state = (0, import_react_stately7.useComboBoxState)({
11133
11102
  ...comboBoxProps,
11134
11103
  allowsEmptyCollection: true,
11135
- // We don't really allow custom values, as we reset the input value once a user `blur`s the input field.
11136
- // Though, setting `allowsCustomValue: true` prevents React-Aria/Stately from attempting to reset the input field's value when the menu closes.
11137
- allowsCustomValue: true,
11138
- // useComboBoxState.onSelectionChange will be executed if a keyboard interaction (Enter key) is used to select an item
11139
- onSelectionChange: (key) => {
11140
- if (key) {
11141
- const selectedKeys2 = state.selectionManager.selectedKeys;
11142
- const newSelection = new Set(!multiselect ? [key] : [...selectedKeys2, key]);
11143
- state.selectionManager.setSelectedKeys(newSelection);
11104
+ selectionMode: multiselect ? "multiple" : "single",
11105
+ // For multi-select, disable close-on-blur to prevent `commitValue` from calling `onChange`
11106
+ // with a stale `displayValue` (the controlled `value` prop before React re-renders).
11107
+ // We handle menu close manually in ComboBoxInput's onBlur via `state.toggle()`.
11108
+ ...multiselect ? { shouldCloseOnBlur: false } : {},
11109
+ // Use the new value/onChange API for native multi-select support
11110
+ value: multiselect ? selectedKeys : selectedKeys[0] ?? null,
11111
+ // Don't call state.close() inside onChange `state.close` is mapped to `commitValue` which
11112
+ // re-triggers onChange, causing an infinite loop. The native state handles menu close automatically
11113
+ // for single-select (closes on value change) and multi-select (stays open).
11114
+ onChange: (newValue) => {
11115
+ if (multiselect) {
11116
+ const keys = newValue ?? [];
11117
+ const newSelectedOptions = options.filter((o) => keys.includes(valueToKey(getOptionValue(o))));
11118
+ onSelect(keys.map(keyToValue), newSelectedOptions);
11119
+ } else {
11120
+ const key = newValue;
11121
+ if (key === null || key === void 0) {
11122
+ onSelect([], []);
11123
+ return;
11124
+ }
11125
+ const selectedOption = options.find((o) => valueToKey(getOptionValue(o)) === key);
11126
+ if (selectedOption === addNewOption && onAddNew) {
11127
+ onAddNew(fieldState.inputValue);
11128
+ return;
11129
+ }
11130
+ onSelect([keyToValue(key)], selectedOption ? [selectedOption] : []);
11144
11131
  }
11145
11132
  }
11146
11133
  });
11147
- const selectedKeys = (0, import_react49.useMemo)(() => {
11148
- return selectedOptions.map((o) => valueToKey(getOptionValue(o)));
11149
- }, [selectedOptions, getOptionValue]);
11150
- state.selectionManager.state = (0, import_react_stately7.useMultipleSelectionState)({
11151
- selectionMode: multiselect ? "multiple" : "single",
11152
- // Do not allow an empty selection if single select mode
11153
- disallowEmptySelection: !multiselect,
11154
- selectedKeys,
11155
- onSelectionChange,
11156
- disabledKeys: Object.keys(disabledOptionsWithReasons)
11157
- });
11158
11134
  const [debouncedSearch] = (0, import_use_debounce5.useDebounce)(searchValue, 300);
11159
11135
  (0, import_react49.useEffect)(() => {
11160
11136
  if (state.isOpen && multiselect && !debouncedSearch) {
@@ -11196,7 +11172,10 @@ function ComboBoxBase(props) {
11196
11172
  scrollRef: listBoxRef,
11197
11173
  shouldFlip: true,
11198
11174
  isOpen: state.isOpen,
11199
- onClose: state.close,
11175
+ // For multi-select, use toggle() instead of close() to avoid commitValue(), which
11176
+ // calls onChange(displayValue) with a potentially stale controlled value. For single-select,
11177
+ // close() (i.e. commitValue) is correct — it commits the input text to the selected value.
11178
+ onClose: multiselect ? () => state.toggle() : state.close,
11200
11179
  placement: "bottom left",
11201
11180
  offset: borderless ? 8 : 4
11202
11181
  });
@@ -11238,7 +11217,7 @@ function ComboBoxBase(props) {
11238
11217
  triggerRef,
11239
11218
  popoverRef,
11240
11219
  positionProps,
11241
- onClose: () => state.close(),
11220
+ onClose: () => multiselect ? state.toggle() : state.close(),
11242
11221
  isOpen: state.isOpen,
11243
11222
  minWidth: 200,
11244
11223
  children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(