Socket
Socket
Sign inDemoInstall

@react-stately/combobox

Package Overview
Dependencies
Maintainers
2
Versions
680
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@react-stately/combobox - npm Package Compare versions

Comparing version 3.0.0-nightly.2813 to 3.0.0-nightly-641446f65-240905

dist/import.mjs

328

dist/main.js

@@ -1,320 +0,22 @@

var {
useMenuTriggerState
} = require("@react-stately/menu");
var $e563f9c9469ad14c$exports = require("./useComboBoxState.main.js");
var {
useEffect,
useMemo,
useRef,
useState
} = require("react");
var {
useControlledState
} = require("@react-stately/utils");
var {
ListCollection,
useSingleSelectListState
} = require("@react-stately/list");
var _babelRuntimeHelpersExtends = $parcel$interopDefault(require("@babel/runtime/helpers/extends"));
function $parcel$interopDefault(a) {
return a && a.__esModule ? a.default : a;
function $parcel$export(e, n, v, s) {
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
}
/**
* Provides state management for a combo box component. Handles building a collection
* of items from props and manages the option selection state of the combo box. In addition, it tracks the input value,
* focus state, and other properties of the combo box.
*/
function useComboBoxState(props) {
var _props$defaultInputVa, _props$items, _ref, _props$selectedKey, _collection$getItem$t2, _collection$getItem2;
$parcel$export(module.exports, "useComboBoxState", () => $e563f9c9469ad14c$exports.useComboBoxState);
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
let {
defaultFilter,
menuTrigger = 'input',
allowsEmptyCollection = false,
allowsCustomValue,
shouldCloseOnBlur = true
} = props;
let [showAllItems, setShowAllItems] = useState(false);
let [isFocused, setFocusedState] = useState(false);
let [inputValue, setInputValue] = useControlledState(props.inputValue, (_props$defaultInputVa = props.defaultInputValue) != null ? _props$defaultInputVa : '', props.onInputChange);
let onSelectionChange = key => {
if (props.onSelectionChange) {
props.onSelectionChange(key);
} // If key is the same, reset the inputValue and close the menu
// (scenario: user clicks on already selected option)
if (key === selectedKey) {
resetInputValue();
triggerState.close();
}
};
let {
collection,
selectionManager,
selectedKey,
setSelectedKey,
selectedItem,
disabledKeys
} = useSingleSelectListState(_babelRuntimeHelpersExtends({}, props, {
onSelectionChange,
items: (_props$items = props.items) != null ? _props$items : props.defaultItems
})); // Preserve original collection so we can show all items on demand
let originalCollection = collection;
let filteredCollection = useMemo(() => // No default filter if items are controlled.
props.items != null || !defaultFilter ? collection : $a21047c5b4305a7d5e80cc48828d4cd$var$filterCollection(collection, inputValue, defaultFilter), [collection, inputValue, defaultFilter, props.items]); // Track what action is attempting to open the menu
let menuOpenTrigger = useRef('focus');
let onOpenChange = open => {
if (props.onOpenChange) {
props.onOpenChange(open, open ? menuOpenTrigger.current : undefined);
}
};
let triggerState = useMenuTriggerState(_babelRuntimeHelpersExtends({}, props, {
onOpenChange,
isOpen: undefined,
defaultOpen: undefined
}));
let open = (focusStrategy, trigger) => {
let displayAllItems = trigger === 'manual' || trigger === 'focus' && menuTrigger === 'focus'; // Prevent open operations from triggering if there is nothing to display
// Also prevent open operations from triggering if items are uncontrolled but defaultItems is empty, even if displayAllItems is true.
// This is to prevent comboboxes with empty defaultItems from opening but allow controlled items comboboxes to open even if the inital list is empty (assumption is user will provide swap the empty list with a base list via onOpenChange returning `menuTrigger` manual)
if (allowsEmptyCollection || filteredCollection.size > 0 || displayAllItems && originalCollection.size > 0 || props.items) {
if (displayAllItems && !triggerState.isOpen && props.items === undefined) {
// Show all items if menu is manually opened. Only care about this if items are undefined
setShowAllItems(true);
}
menuOpenTrigger.current = trigger;
triggerState.open(focusStrategy);
}
};
let toggle = (focusStrategy, trigger) => {
let displayAllItems = trigger === 'manual' || trigger === 'focus' && menuTrigger === 'focus'; // If the menu is closed and there is nothing to display, early return so toggle isn't called to prevent extraneous onOpenChange
if (!(allowsEmptyCollection || filteredCollection.size > 0 || displayAllItems && originalCollection.size > 0 || props.items) && !triggerState.isOpen) {
return;
}
if (displayAllItems && !triggerState.isOpen && props.items === undefined) {
// Show all items if menu is toggled open. Only care about this if items are undefined
setShowAllItems(true);
} // Only update the menuOpenTrigger if menu is currently closed
if (!triggerState.isOpen) {
menuOpenTrigger.current = trigger;
}
triggerState.toggle(focusStrategy);
};
let lastValue = useRef(inputValue);
let resetInputValue = () => {
var _collection$getItem$t, _collection$getItem;
let itemText = (_collection$getItem$t = (_collection$getItem = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem.textValue) != null ? _collection$getItem$t : '';
lastValue.current = itemText;
setInputValue(itemText);
};
let isInitialRender = useRef(true);
let lastSelectedKey = useRef((_ref = (_props$selectedKey = props.selectedKey) != null ? _props$selectedKey : props.defaultSelectedKey) != null ? _ref : null);
let lastSelectedKeyText = useRef((_collection$getItem$t2 = (_collection$getItem2 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem2.textValue) != null ? _collection$getItem$t2 : ''); // intentional omit dependency array, want this to happen on every render
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
var _collection$getItem$t3, _collection$getItem3;
// Open and close menu automatically when the input value changes if the input is focused,
// and there are items in the collection or allowEmptyCollection is true.
if (isFocused && (filteredCollection.size > 0 || allowsEmptyCollection) && !triggerState.isOpen && inputValue !== lastValue.current && menuTrigger !== 'manual') {
open(null, 'input');
} // Close the menu if the collection is empty. Don't close menu if filtered collection size is 0
// but we are currently showing all items via button press
if (!showAllItems && !allowsEmptyCollection && triggerState.isOpen && filteredCollection.size === 0) {
triggerState.close();
} // Close when an item is selected.
if (selectedKey != null && selectedKey !== lastSelectedKey.current) {
triggerState.close();
} // Clear focused key when input value changes and display filtered collection again.
if (inputValue !== lastValue.current) {
selectionManager.setFocusedKey(null);
setShowAllItems(false); // Set selectedKey to null when the user clears the input.
// If controlled, this is the application developer's responsibility.
if (inputValue === '' && (props.inputValue === undefined || props.selectedKey === undefined)) {
setSelectedKey(null);
}
} // If it is the intial render and inputValue isn't controlled nor has an intial value, set input to match current selected key if any
if (isInitialRender.current && props.inputValue === undefined && props.defaultInputValue === undefined) {
resetInputValue();
} // If the selectedKey changed, update the input value.
// Do nothing if both inputValue and selectedKey are controlled.
// In this case, it's the user's responsibility to update inputValue in onSelectionChange.
if (selectedKey !== lastSelectedKey.current && (props.inputValue === undefined || props.selectedKey === undefined)) {
resetInputValue();
} else {
lastValue.current = inputValue;
} // Update the inputValue if the selected item's text changes from its last tracked value.
// This is to handle cases where a selectedKey is specified but the items aren't available (async loading) or the selected item's text value updates.
// Only reset if the user isn't currently within the field so we don't erroneously modify user input.
// If inputValue is controlled, it is the user's responsibility to update the inputValue when items change.
let selectedItemText = (_collection$getItem$t3 = (_collection$getItem3 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem3.textValue) != null ? _collection$getItem$t3 : '';
if (!isFocused && selectedKey != null && props.inputValue === undefined && selectedKey === lastSelectedKey.current) {
if (lastSelectedKeyText.current !== selectedItemText) {
lastValue.current = selectedItemText;
setInputValue(selectedItemText);
}
}
isInitialRender.current = false;
lastSelectedKey.current = selectedKey;
lastSelectedKeyText.current = selectedItemText;
});
useEffect(() => {
// Reset focused key when the menu closes
if (!triggerState.isOpen) {
selectionManager.setFocusedKey(null);
}
}, [triggerState.isOpen, selectionManager]); // Revert input value and close menu
let revert = () => {
if (allowsCustomValue && selectedKey == null) {
commitCustomValue();
} else {
commitSelection();
}
};
let commitCustomValue = () => {
lastSelectedKey.current = null;
setSelectedKey(null);
triggerState.close();
};
let commitSelection = () => {
// If multiple things are controlled, call onSelectionChange
if (props.selectedKey !== undefined && props.inputValue !== undefined) {
var _collection$getItem$t4, _collection$getItem4;
props.onSelectionChange(selectedKey); // Stop menu from reopening from useEffect
let itemText = (_collection$getItem$t4 = (_collection$getItem4 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem4.textValue) != null ? _collection$getItem$t4 : '';
lastValue.current = itemText;
triggerState.close();
} else {
// If only a single aspect of combobox is controlled, reset input value and close menu for the user
resetInputValue();
triggerState.close();
}
};
let commit = () => {
if (triggerState.isOpen && selectionManager.focusedKey != null) {
// Reset inputValue and close menu here if the selected key is already the focused key. Otherwise
// fire onSelectionChange to allow the application to control the closing.
if (selectedKey === selectionManager.focusedKey) {
commitSelection();
} else {
setSelectedKey(selectionManager.focusedKey);
}
} else if (allowsCustomValue) {
commitCustomValue();
} else {
// Reset inputValue and close menu if no item is focused but user triggers a commit
commitSelection();
}
};
let setFocused = isFocused => {
if (isFocused) {
if (menuTrigger === 'focus') {
open(null, 'focus');
}
} else if (shouldCloseOnBlur) {
var _collection$getItem$t5, _collection$getItem5;
let itemText = (_collection$getItem$t5 = (_collection$getItem5 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem5.textValue) != null ? _collection$getItem$t5 : '';
if (allowsCustomValue && inputValue !== itemText) {
commitCustomValue();
} else {
commitSelection();
}
}
setFocusedState(isFocused);
};
return _babelRuntimeHelpersExtends({}, triggerState, {
toggle,
open,
selectionManager,
selectedKey,
setSelectedKey,
disabledKeys,
isFocused,
setFocused,
selectedItem,
collection: showAllItems ? originalCollection : filteredCollection,
inputValue,
setInputValue,
commit,
revert
});
}
exports.useComboBoxState = useComboBoxState;
function $a21047c5b4305a7d5e80cc48828d4cd$var$filterCollection(collection, inputValue, filter) {
return new ListCollection($a21047c5b4305a7d5e80cc48828d4cd$var$filterNodes(collection, inputValue, filter));
}
function $a21047c5b4305a7d5e80cc48828d4cd$var$filterNodes(nodes, inputValue, filter) {
let filteredNode = [];
for (let node of nodes) {
if (node.type === 'section' && node.hasChildNodes) {
let filtered = $a21047c5b4305a7d5e80cc48828d4cd$var$filterNodes(node.childNodes, inputValue, filter);
if ([...filtered].length > 0) {
filteredNode.push(_babelRuntimeHelpersExtends({}, node, {
childNodes: filtered
}));
}
} else if (node.type !== 'section' && filter(node.textValue, inputValue)) {
filteredNode.push(_babelRuntimeHelpersExtends({}, node));
}
}
return filteredNode;
}
//# sourceMappingURL=main.js.map

@@ -1,298 +0,17 @@

import { useMenuTriggerState } from "@react-stately/menu";
import { useEffect, useMemo, useRef, useState } from "react";
import { useControlledState } from "@react-stately/utils";
import { ListCollection, useSingleSelectListState } from "@react-stately/list";
import _babelRuntimeHelpersEsmExtends from "@babel/runtime/helpers/esm/extends";
import {useComboBoxState as $a9e7382a7d111cb5$export$b453a3bfd4a5fa9e} from "./useComboBoxState.module.js";
/**
* Provides state management for a combo box component. Handles building a collection
* of items from props and manages the option selection state of the combo box. In addition, it tracks the input value,
* focus state, and other properties of the combo box.
*/
export function useComboBoxState(props) {
var _props$defaultInputVa, _props$items, _ref, _props$selectedKey, _collection$getItem$t2, _collection$getItem2;
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
let {
defaultFilter,
menuTrigger = 'input',
allowsEmptyCollection = false,
allowsCustomValue,
shouldCloseOnBlur = true
} = props;
let [showAllItems, setShowAllItems] = useState(false);
let [isFocused, setFocusedState] = useState(false);
let [inputValue, setInputValue] = useControlledState(props.inputValue, (_props$defaultInputVa = props.defaultInputValue) != null ? _props$defaultInputVa : '', props.onInputChange);
let onSelectionChange = key => {
if (props.onSelectionChange) {
props.onSelectionChange(key);
} // If key is the same, reset the inputValue and close the menu
// (scenario: user clicks on already selected option)
if (key === selectedKey) {
resetInputValue();
triggerState.close();
}
};
let {
collection,
selectionManager,
selectedKey,
setSelectedKey,
selectedItem,
disabledKeys
} = useSingleSelectListState(_babelRuntimeHelpersEsmExtends({}, props, {
onSelectionChange,
items: (_props$items = props.items) != null ? _props$items : props.defaultItems
})); // Preserve original collection so we can show all items on demand
let originalCollection = collection;
let filteredCollection = useMemo(() => // No default filter if items are controlled.
props.items != null || !defaultFilter ? collection : $b1b6afed698df4428b7ed319af4b90$var$filterCollection(collection, inputValue, defaultFilter), [collection, inputValue, defaultFilter, props.items]); // Track what action is attempting to open the menu
let menuOpenTrigger = useRef('focus');
let onOpenChange = open => {
if (props.onOpenChange) {
props.onOpenChange(open, open ? menuOpenTrigger.current : undefined);
}
};
let triggerState = useMenuTriggerState(_babelRuntimeHelpersEsmExtends({}, props, {
onOpenChange,
isOpen: undefined,
defaultOpen: undefined
}));
let open = (focusStrategy, trigger) => {
let displayAllItems = trigger === 'manual' || trigger === 'focus' && menuTrigger === 'focus'; // Prevent open operations from triggering if there is nothing to display
// Also prevent open operations from triggering if items are uncontrolled but defaultItems is empty, even if displayAllItems is true.
// This is to prevent comboboxes with empty defaultItems from opening but allow controlled items comboboxes to open even if the inital list is empty (assumption is user will provide swap the empty list with a base list via onOpenChange returning `menuTrigger` manual)
if (allowsEmptyCollection || filteredCollection.size > 0 || displayAllItems && originalCollection.size > 0 || props.items) {
if (displayAllItems && !triggerState.isOpen && props.items === undefined) {
// Show all items if menu is manually opened. Only care about this if items are undefined
setShowAllItems(true);
}
menuOpenTrigger.current = trigger;
triggerState.open(focusStrategy);
}
};
let toggle = (focusStrategy, trigger) => {
let displayAllItems = trigger === 'manual' || trigger === 'focus' && menuTrigger === 'focus'; // If the menu is closed and there is nothing to display, early return so toggle isn't called to prevent extraneous onOpenChange
if (!(allowsEmptyCollection || filteredCollection.size > 0 || displayAllItems && originalCollection.size > 0 || props.items) && !triggerState.isOpen) {
return;
}
if (displayAllItems && !triggerState.isOpen && props.items === undefined) {
// Show all items if menu is toggled open. Only care about this if items are undefined
setShowAllItems(true);
} // Only update the menuOpenTrigger if menu is currently closed
if (!triggerState.isOpen) {
menuOpenTrigger.current = trigger;
}
triggerState.toggle(focusStrategy);
};
let lastValue = useRef(inputValue);
let resetInputValue = () => {
var _collection$getItem$t, _collection$getItem;
let itemText = (_collection$getItem$t = (_collection$getItem = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem.textValue) != null ? _collection$getItem$t : '';
lastValue.current = itemText;
setInputValue(itemText);
};
let isInitialRender = useRef(true);
let lastSelectedKey = useRef((_ref = (_props$selectedKey = props.selectedKey) != null ? _props$selectedKey : props.defaultSelectedKey) != null ? _ref : null);
let lastSelectedKeyText = useRef((_collection$getItem$t2 = (_collection$getItem2 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem2.textValue) != null ? _collection$getItem$t2 : ''); // intentional omit dependency array, want this to happen on every render
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
var _collection$getItem$t3, _collection$getItem3;
// Open and close menu automatically when the input value changes if the input is focused,
// and there are items in the collection or allowEmptyCollection is true.
if (isFocused && (filteredCollection.size > 0 || allowsEmptyCollection) && !triggerState.isOpen && inputValue !== lastValue.current && menuTrigger !== 'manual') {
open(null, 'input');
} // Close the menu if the collection is empty. Don't close menu if filtered collection size is 0
// but we are currently showing all items via button press
if (!showAllItems && !allowsEmptyCollection && triggerState.isOpen && filteredCollection.size === 0) {
triggerState.close();
} // Close when an item is selected.
if (selectedKey != null && selectedKey !== lastSelectedKey.current) {
triggerState.close();
} // Clear focused key when input value changes and display filtered collection again.
if (inputValue !== lastValue.current) {
selectionManager.setFocusedKey(null);
setShowAllItems(false); // Set selectedKey to null when the user clears the input.
// If controlled, this is the application developer's responsibility.
if (inputValue === '' && (props.inputValue === undefined || props.selectedKey === undefined)) {
setSelectedKey(null);
}
} // If it is the intial render and inputValue isn't controlled nor has an intial value, set input to match current selected key if any
if (isInitialRender.current && props.inputValue === undefined && props.defaultInputValue === undefined) {
resetInputValue();
} // If the selectedKey changed, update the input value.
// Do nothing if both inputValue and selectedKey are controlled.
// In this case, it's the user's responsibility to update inputValue in onSelectionChange.
if (selectedKey !== lastSelectedKey.current && (props.inputValue === undefined || props.selectedKey === undefined)) {
resetInputValue();
} else {
lastValue.current = inputValue;
} // Update the inputValue if the selected item's text changes from its last tracked value.
// This is to handle cases where a selectedKey is specified but the items aren't available (async loading) or the selected item's text value updates.
// Only reset if the user isn't currently within the field so we don't erroneously modify user input.
// If inputValue is controlled, it is the user's responsibility to update the inputValue when items change.
let selectedItemText = (_collection$getItem$t3 = (_collection$getItem3 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem3.textValue) != null ? _collection$getItem$t3 : '';
if (!isFocused && selectedKey != null && props.inputValue === undefined && selectedKey === lastSelectedKey.current) {
if (lastSelectedKeyText.current !== selectedItemText) {
lastValue.current = selectedItemText;
setInputValue(selectedItemText);
}
}
isInitialRender.current = false;
lastSelectedKey.current = selectedKey;
lastSelectedKeyText.current = selectedItemText;
});
useEffect(() => {
// Reset focused key when the menu closes
if (!triggerState.isOpen) {
selectionManager.setFocusedKey(null);
}
}, [triggerState.isOpen, selectionManager]); // Revert input value and close menu
let revert = () => {
if (allowsCustomValue && selectedKey == null) {
commitCustomValue();
} else {
commitSelection();
}
};
let commitCustomValue = () => {
lastSelectedKey.current = null;
setSelectedKey(null);
triggerState.close();
};
let commitSelection = () => {
// If multiple things are controlled, call onSelectionChange
if (props.selectedKey !== undefined && props.inputValue !== undefined) {
var _collection$getItem$t4, _collection$getItem4;
props.onSelectionChange(selectedKey); // Stop menu from reopening from useEffect
let itemText = (_collection$getItem$t4 = (_collection$getItem4 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem4.textValue) != null ? _collection$getItem$t4 : '';
lastValue.current = itemText;
triggerState.close();
} else {
// If only a single aspect of combobox is controlled, reset input value and close menu for the user
resetInputValue();
triggerState.close();
}
};
let commit = () => {
if (triggerState.isOpen && selectionManager.focusedKey != null) {
// Reset inputValue and close menu here if the selected key is already the focused key. Otherwise
// fire onSelectionChange to allow the application to control the closing.
if (selectedKey === selectionManager.focusedKey) {
commitSelection();
} else {
setSelectedKey(selectionManager.focusedKey);
}
} else if (allowsCustomValue) {
commitCustomValue();
} else {
// Reset inputValue and close menu if no item is focused but user triggers a commit
commitSelection();
}
};
let setFocused = isFocused => {
if (isFocused) {
if (menuTrigger === 'focus') {
open(null, 'focus');
}
} else if (shouldCloseOnBlur) {
var _collection$getItem$t5, _collection$getItem5;
let itemText = (_collection$getItem$t5 = (_collection$getItem5 = collection.getItem(selectedKey)) == null ? void 0 : _collection$getItem5.textValue) != null ? _collection$getItem$t5 : '';
if (allowsCustomValue && inputValue !== itemText) {
commitCustomValue();
} else {
commitSelection();
}
}
setFocusedState(isFocused);
};
return _babelRuntimeHelpersEsmExtends({}, triggerState, {
toggle,
open,
selectionManager,
selectedKey,
setSelectedKey,
disabledKeys,
isFocused,
setFocused,
selectedItem,
collection: showAllItems ? originalCollection : filteredCollection,
inputValue,
setInputValue,
commit,
revert
});
}
function $b1b6afed698df4428b7ed319af4b90$var$filterCollection(collection, inputValue, filter) {
return new ListCollection($b1b6afed698df4428b7ed319af4b90$var$filterNodes(collection, inputValue, filter));
}
function $b1b6afed698df4428b7ed319af4b90$var$filterNodes(nodes, inputValue, filter) {
let filteredNode = [];
for (let node of nodes) {
if (node.type === 'section' && node.hasChildNodes) {
let filtered = $b1b6afed698df4428b7ed319af4b90$var$filterNodes(node.childNodes, inputValue, filter);
if ([...filtered].length > 0) {
filteredNode.push(_babelRuntimeHelpersEsmExtends({}, node, {
childNodes: filtered
}));
}
} else if (node.type !== 'section' && filter(node.textValue, inputValue)) {
filteredNode.push(_babelRuntimeHelpersEsmExtends({}, node));
}
}
return filteredNode;
}
export {$a9e7382a7d111cb5$export$b453a3bfd4a5fa9e as useComboBoxState};
//# sourceMappingURL=module.js.map

@@ -1,5 +0,6 @@

import { FocusStrategy } from "@react-types/shared";
import { CollectionStateBase, FocusStrategy } from "@react-types/shared";
import { ComboBoxProps, MenuTriggerAction } from "@react-types/combobox";
import { FormValidationState } from "@react-stately/form";
import { SelectState } from "@react-stately/select";
export interface ComboBoxState<T> extends SelectState<T> {
export interface ComboBoxState<T> extends SelectState<T>, FormValidationState {
/** The current value of the combo box input. */

@@ -11,2 +12,4 @@ inputValue: string;

commit(): void;
/** Controls which item will be auto focused when the menu opens. */
readonly focusStrategy: FocusStrategy | null;
/** Opens the menu. */

@@ -20,3 +23,3 @@ open(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void;

type FilterFn = (textValue: string, inputValue: string) => boolean;
interface ComboBoxStateProps<T> extends ComboBoxProps<T> {
export interface ComboBoxStateOptions<T> extends Omit<ComboBoxProps<T>, 'children'>, CollectionStateBase<T> {
/** The filter function used to determine if a option should be included in the combo box list. */

@@ -34,4 +37,4 @@ defaultFilter?: FilterFn;

*/
export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>): ComboBoxState<T>;
export function useComboBoxState<T extends object>(props: ComboBoxStateOptions<T>): ComboBoxState<T>;
//# sourceMappingURL=types.d.ts.map
{
"name": "@react-stately/combobox",
"version": "3.0.0-nightly.2813+a05cbb945",
"version": "3.0.0-nightly-641446f65-240905",
"description": "Spectrum UI components in React",

@@ -8,2 +8,7 @@ "license": "Apache-2.0",

"module": "dist/module.js",
"exports": {
"types": "./dist/types.d.ts",
"import": "./dist/import.mjs",
"require": "./dist/main.js"
},
"types": "dist/types.d.ts",

@@ -21,12 +26,14 @@ "source": "src/index.ts",

"dependencies": {
"@babel/runtime": "^7.6.2",
"@react-stately/list": "3.2.4-nightly.2813+a05cbb945",
"@react-stately/menu": "3.2.3-nightly.2813+a05cbb945",
"@react-stately/select": "3.1.3-nightly.2813+a05cbb945",
"@react-stately/utils": "3.0.0-nightly.1128+a05cbb945",
"@react-types/combobox": "3.0.0-nightly.2813+a05cbb945",
"@react-types/shared": "3.0.0-nightly.1128+a05cbb945"
"@react-stately/collections": "^3.0.0-nightly-641446f65-240905",
"@react-stately/form": "^3.0.0-nightly-641446f65-240905",
"@react-stately/list": "^3.0.0-nightly-641446f65-240905",
"@react-stately/overlays": "^3.0.0-nightly-641446f65-240905",
"@react-stately/select": "^3.0.0-nightly-641446f65-240905",
"@react-stately/utils": "^3.0.0-nightly-641446f65-240905",
"@react-types/combobox": "^3.0.0-nightly-641446f65-240905",
"@react-types/shared": "^3.0.0-nightly-641446f65-240905",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1"
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
},

@@ -36,3 +43,3 @@ "publishConfig": {

},
"gitHead": "a05cbb945c22d66c84eea5b1288fe7fc0e9cbce4"
}
"stableVersion": "3.9.2"
}

@@ -13,2 +13,4 @@ /*

export * from './useComboBoxState';
export {useComboBoxState} from './useComboBoxState';
export type {ComboBoxStateOptions, ComboBoxState} from './useComboBoxState';

@@ -13,11 +13,13 @@ /*

import {Collection, FocusStrategy, Node} from '@react-types/shared';
import {Collection, CollectionStateBase, FocusStrategy, Node} from '@react-types/shared';
import {ComboBoxProps, MenuTriggerAction} from '@react-types/combobox';
import {FormValidationState, useFormValidationState} from '@react-stately/form';
import {getChildNodes} from '@react-stately/collections';
import {ListCollection, useSingleSelectListState} from '@react-stately/list';
import {SelectState} from '@react-stately/select';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useControlledState} from '@react-stately/utils';
import {useEffect, useMemo, useRef, useState} from 'react';
import {useMenuTriggerState} from '@react-stately/menu';
import {useOverlayTriggerState} from '@react-stately/overlays';
export interface ComboBoxState<T> extends SelectState<T> {
export interface ComboBoxState<T> extends SelectState<T>, FormValidationState{
/** The current value of the combo box input. */

@@ -29,2 +31,4 @@ inputValue: string,

commit(): void,
/** Controls which item will be auto focused when the menu opens. */
readonly focusStrategy: FocusStrategy | null,
/** Opens the menu. */

@@ -39,3 +43,4 @@ open(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void,

type FilterFn = (textValue: string, inputValue: string) => boolean;
interface ComboBoxStateProps<T> extends ComboBoxProps<T> {
export interface ComboBoxStateOptions<T> extends Omit<ComboBoxProps<T>, 'children'>, CollectionStateBase<T> {
/** The filter function used to determine if a option should be included in the combo box list. */

@@ -54,3 +59,3 @@ defaultFilter?: FilterFn,

*/
export function useComboBoxState<T extends object>(props: ComboBoxStateProps<T>): ComboBoxState<T> {
export function useComboBoxState<T extends object>(props: ComboBoxStateOptions<T>): ComboBoxState<T> {
let {

@@ -66,7 +71,3 @@ defaultFilter,

let [isFocused, setFocusedState] = useState(false);
let [inputValue, setInputValue] = useControlledState(
props.inputValue,
props.defaultInputValue ?? '',
props.onInputChange
);
let [focusStrategy, setFocusStrategy] = useState<FocusStrategy | null>(null);

@@ -82,7 +83,13 @@ let onSelectionChange = (key) => {

resetInputValue();
triggerState.close();
closeMenu();
}
};
let {collection, selectionManager, selectedKey, setSelectedKey, selectedItem, disabledKeys} = useSingleSelectListState({
let {collection,
selectionManager,
selectedKey,
setSelectedKey,
selectedItem,
disabledKeys
} = useSingleSelectListState({
...props,

@@ -92,3 +99,17 @@ onSelectionChange,

});
let defaultInputValue: string | null | undefined = props.defaultInputValue;
if (defaultInputValue == null) {
if (selectedKey == null) {
defaultInputValue = '';
} else {
defaultInputValue = collection.getItem(selectedKey)?.textValue ?? '';
}
}
let [inputValue, setInputValue] = useControlledState(
props.inputValue,
defaultInputValue!,
props.onInputChange
);
// Preserve original collection so we can show all items on demand

@@ -102,5 +123,6 @@ let originalCollection = collection;

), [collection, inputValue, defaultFilter, props.items]);
let [lastCollection, setLastCollection] = useState(filteredCollection);
// Track what action is attempting to open the menu
let menuOpenTrigger = useRef('focus' as MenuTriggerAction);
let menuOpenTrigger = useRef<MenuTriggerAction | undefined>('focus');
let onOpenChange = (open: boolean) => {

@@ -110,6 +132,11 @@ if (props.onOpenChange) {

}
selectionManager.setFocused(open);
if (!open) {
selectionManager.setFocusedKey(null);
}
};
let triggerState = useMenuTriggerState({...props, onOpenChange, isOpen: undefined, defaultOpen: undefined});
let open = (focusStrategy?: FocusStrategy, trigger?: MenuTriggerAction) => {
let triggerState = useOverlayTriggerState({...props, onOpenChange, isOpen: undefined, defaultOpen: undefined});
let open = (focusStrategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
let displayAllItems = (trigger === 'manual' || (trigger === 'focus' && menuTrigger === 'focus'));

@@ -126,7 +153,8 @@ // Prevent open operations from triggering if there is nothing to display

menuOpenTrigger.current = trigger;
triggerState.open(focusStrategy);
setFocusStrategy(focusStrategy);
triggerState.open();
}
};
let toggle = (focusStrategy?: FocusStrategy, trigger?: MenuTriggerAction) => {
let toggle = (focusStrategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
let displayAllItems = (trigger === 'manual' || (trigger === 'focus' && menuTrigger === 'focus'));

@@ -148,15 +176,38 @@ // If the menu is closed and there is nothing to display, early return so toggle isn't called to prevent extraneous onOpenChange

triggerState.toggle(focusStrategy);
toggleMenu(focusStrategy);
};
let lastValue = useRef(inputValue);
let updateLastCollection = useCallback(() => {
setLastCollection(showAllItems ? originalCollection : filteredCollection);
}, [showAllItems, originalCollection, filteredCollection]);
// If menu is going to close, save the current collection so we can freeze the displayed collection when the
// user clicks outside the popover to close the menu. Prevents the menu contents from updating as the menu closes.
let toggleMenu = useCallback((focusStrategy: FocusStrategy | null = null) => {
if (triggerState.isOpen) {
updateLastCollection();
}
setFocusStrategy(focusStrategy);
triggerState.toggle();
}, [triggerState, updateLastCollection]);
let closeMenu = useCallback(() => {
if (triggerState.isOpen) {
updateLastCollection();
triggerState.close();
}
}, [triggerState, updateLastCollection]);
let [lastValue, setLastValue] = useState(inputValue);
let resetInputValue = () => {
let itemText = collection.getItem(selectedKey)?.textValue ?? '';
lastValue.current = itemText;
let itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
setLastValue(itemText);
setInputValue(itemText);
};
let isInitialRender = useRef(true);
let lastSelectedKey = useRef(props.selectedKey ?? props.defaultSelectedKey ?? null);
let lastSelectedKeyText = useRef(collection.getItem(selectedKey)?.textValue ?? '');
let lastSelectedKeyText = useRef(
selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : ''
);
// intentional omit dependency array, want this to happen on every render

@@ -171,3 +222,3 @@ // eslint-disable-next-line react-hooks/exhaustive-deps

!triggerState.isOpen &&
inputValue !== lastValue.current &&
inputValue !== lastValue &&
menuTrigger !== 'manual'

@@ -186,3 +237,3 @@ ) {

) {
triggerState.close();
closeMenu();
}

@@ -195,7 +246,7 @@

) {
triggerState.close();
closeMenu();
}
// Clear focused key when input value changes and display filtered collection again.
if (inputValue !== lastValue.current) {
if (inputValue !== lastValue) {
selectionManager.setFocusedKey(null);

@@ -211,7 +262,2 @@ setShowAllItems(false);

// If it is the intial render and inputValue isn't controlled nor has an intial value, set input to match current selected key if any
if (isInitialRender.current && (props.inputValue === undefined && props.defaultInputValue === undefined)) {
resetInputValue();
}
// If the selectedKey changed, update the input value.

@@ -225,4 +271,4 @@ // Do nothing if both inputValue and selectedKey are controlled.

resetInputValue();
} else {
lastValue.current = inputValue;
} else if (lastValue !== inputValue) {
setLastValue(inputValue);
}

@@ -234,6 +280,6 @@

// If inputValue is controlled, it is the user's responsibility to update the inputValue when items change.
let selectedItemText = collection.getItem(selectedKey)?.textValue ?? '';
let selectedItemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
if (!isFocused && selectedKey != null && props.inputValue === undefined && selectedKey === lastSelectedKey.current) {
if (lastSelectedKeyText.current !== selectedItemText) {
lastValue.current = selectedItemText;
setLastValue(selectedItemText);
setInputValue(selectedItemText);

@@ -243,3 +289,2 @@ }

isInitialRender.current = false;
lastSelectedKey.current = selectedKey;

@@ -249,8 +294,6 @@ lastSelectedKeyText.current = selectedItemText;

useEffect(() => {
// Reset focused key when the menu closes
if (!triggerState.isOpen) {
selectionManager.setFocusedKey(null);
}
}, [triggerState.isOpen, selectionManager]);
let validation = useFormValidationState({
...props,
value: useMemo(() => ({inputValue, selectedKey}), [inputValue, selectedKey])
});

@@ -269,3 +312,3 @@ // Revert input value and close menu

setSelectedKey(null);
triggerState.close();
closeMenu();
};

@@ -276,15 +319,25 @@

if (props.selectedKey !== undefined && props.inputValue !== undefined) {
props.onSelectionChange(selectedKey);
props.onSelectionChange?.(selectedKey);
// Stop menu from reopening from useEffect
let itemText = collection.getItem(selectedKey)?.textValue ?? '';
lastValue.current = itemText;
triggerState.close();
let itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
setLastValue(itemText);
closeMenu();
} else {
// If only a single aspect of combobox is controlled, reset input value and close menu for the user
resetInputValue();
triggerState.close();
closeMenu();
}
};
const commitValue = () => {
if (allowsCustomValue) {
const itemText = selectedKey != null ? collection.getItem(selectedKey)?.textValue ?? '' : '';
(inputValue === itemText) ? commitSelection() : commitCustomValue();
} else {
// Reset inputValue and close menu
commitSelection();
}
};
let commit = () => {

@@ -299,22 +352,22 @@ if (triggerState.isOpen && selectionManager.focusedKey != null) {

}
} else if (allowsCustomValue) {
commitCustomValue();
} else {
// Reset inputValue and close menu if no item is focused but user triggers a commit
commitSelection();
commitValue();
}
};
let valueOnFocus = useRef(inputValue);
let setFocused = (isFocused: boolean) => {
if (isFocused) {
if (menuTrigger === 'focus') {
valueOnFocus.current = inputValue;
if (menuTrigger === 'focus' && !props.isReadOnly) {
open(null, 'focus');
}
} else if (shouldCloseOnBlur) {
let itemText = collection.getItem(selectedKey)?.textValue ?? '';
if (allowsCustomValue && inputValue !== itemText) {
commitCustomValue();
} else {
commitSelection();
} else {
if (shouldCloseOnBlur) {
commitValue();
}
if (inputValue !== valueOnFocus.current) {
validation.commitValidation();
}
}

@@ -325,6 +378,21 @@

let displayedCollection = useMemo(() => {
if (triggerState.isOpen) {
if (showAllItems) {
return originalCollection;
} else {
return filteredCollection;
}
} else {
return lastCollection;
}
}, [triggerState.isOpen, originalCollection, filteredCollection, showAllItems, lastCollection]);
return {
...validation,
...triggerState,
focusStrategy,
toggle,
open,
close: commitValue,
selectionManager,

@@ -337,3 +405,3 @@ selectedKey,

selectedItem,
collection: showAllItems ? originalCollection : filteredCollection,
collection: displayedCollection,
inputValue,

@@ -347,15 +415,17 @@ setInputValue,

function filterCollection<T extends object>(collection: Collection<Node<T>>, inputValue: string, filter: FilterFn): Collection<Node<T>> {
return new ListCollection(filterNodes(collection, inputValue, filter));
return new ListCollection(filterNodes(collection, collection, inputValue, filter));
}
function filterNodes<T>(nodes: Iterable<Node<T>>, inputValue: string, filter: FilterFn): Iterable<Node<T>> {
let filteredNode = [];
function filterNodes<T>(collection: Collection<Node<T>>, nodes: Iterable<Node<T>>, inputValue: string, filter: FilterFn): Iterable<Node<T>> {
let filteredNode: Node<T>[] = [];
for (let node of nodes) {
if (node.type === 'section' && node.hasChildNodes) {
let filtered = filterNodes(node.childNodes, inputValue, filter);
if ([...filtered].length > 0) {
let filtered = filterNodes(collection, getChildNodes(node, collection), inputValue, filter);
if ([...filtered].some(node => node.type === 'item')) {
filteredNode.push({...node, childNodes: filtered});
}
} else if (node.type !== 'section' && filter(node.textValue, inputValue)) {
} else if (node.type === 'item' && filter(node.textValue, inputValue)) {
filteredNode.push({...node});
} else if (node.type !== 'item') {
filteredNode.push({...node});
}

@@ -362,0 +432,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc