202 lines
7.9 KiB
JavaScript
202 lines
7.9 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "useComboboxBaseState", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return useComboboxBaseState;
|
|
}
|
|
});
|
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
|
const _reactdom = /*#__PURE__*/ _interop_require_wildcard._(require("react-dom"));
|
|
const _reactutilities = require("@fluentui/react-utilities");
|
|
const _useOptionCollection = require("../utils/useOptionCollection");
|
|
const _useSelection = require("../utils/useSelection");
|
|
const useComboboxBaseState = (props)=>{
|
|
'use no memo';
|
|
const { appearance = 'outline', disableAutoFocus, children, clearable = false, editable = false, inlinePopup = false, mountNode = undefined, multiselect, onOpenChange, size = 'medium', activeDescendantController, freeform = false, disabled = false, onActiveOptionChange = null } = props;
|
|
const optionCollection = (0, _useOptionCollection.useOptionCollection)();
|
|
const { getOptionsMatchingValue } = optionCollection;
|
|
const { getOptionById } = optionCollection;
|
|
const getActiveOption = _react.useCallback(()=>{
|
|
const activeOptionId = activeDescendantController.active();
|
|
return activeOptionId ? getOptionById(activeOptionId) : undefined;
|
|
}, [
|
|
activeDescendantController,
|
|
getOptionById
|
|
]);
|
|
// Keeping some kind of backwards compatible functionality here
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
const UNSAFE_activeOption = getActiveOption();
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
const UNSAFE_setActiveOption = _react.useCallback((option)=>{
|
|
let nextOption = undefined;
|
|
if (typeof option === 'function') {
|
|
const activeOption = getActiveOption();
|
|
nextOption = option(activeOption);
|
|
}
|
|
if (nextOption) {
|
|
activeDescendantController.focus(nextOption.id);
|
|
} else {
|
|
activeDescendantController.blur();
|
|
}
|
|
}, [
|
|
activeDescendantController,
|
|
getActiveOption
|
|
]);
|
|
// track whether keyboard focus outline should be shown
|
|
// tabster/keyborg doesn't work here, since the actual keyboard focus target doesn't move
|
|
const [focusVisible, setFocusVisible] = _react.useState(false);
|
|
// track focused state to conditionally render collapsed listbox
|
|
// when the trigger is focused - the listbox should but hidden until the open state is changed
|
|
const [hasFocus, setHasFocus] = _react.useState(false);
|
|
const ignoreNextBlur = _react.useRef(false);
|
|
// calculate value based on props, internal value changes, and selected options
|
|
const isFirstMount = (0, _reactutilities.useFirstMount)();
|
|
const [controllableValue, setValue] = (0, _reactutilities.useControllableState)({
|
|
state: props.value,
|
|
initialState: undefined
|
|
});
|
|
const { selectedOptions, selectOption: baseSelectOption, clearSelection } = (0, _useSelection.useSelection)(props);
|
|
// reset any typed value when an option is selected
|
|
const selectOption = _react.useCallback((ev, option)=>{
|
|
_reactdom.unstable_batchedUpdates(()=>{
|
|
setValue(undefined);
|
|
baseSelectOption(ev, option);
|
|
});
|
|
}, [
|
|
setValue,
|
|
baseSelectOption
|
|
]);
|
|
const value = _react.useMemo(()=>{
|
|
// don't compute the value if it is defined through props or setValue,
|
|
if (controllableValue !== undefined) {
|
|
return controllableValue;
|
|
}
|
|
// handle defaultValue here, so it is overridden by selection
|
|
if (isFirstMount && props.defaultValue !== undefined) {
|
|
return props.defaultValue;
|
|
}
|
|
const selectedOptionsText = getOptionsMatchingValue((optionValue)=>{
|
|
return selectedOptions.includes(optionValue);
|
|
}).map((option)=>option.text);
|
|
if (multiselect) {
|
|
// editable inputs should not display multiple selected options in the input as text
|
|
return editable ? '' : selectedOptionsText.join(', ');
|
|
}
|
|
return selectedOptionsText[0];
|
|
// do not change value after isFirstMount changes,
|
|
// we do not want to accidentally override defaultValue on a second render
|
|
// unless another value is intentionally set
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
controllableValue,
|
|
editable,
|
|
getOptionsMatchingValue,
|
|
multiselect,
|
|
props.defaultValue,
|
|
selectedOptions
|
|
]);
|
|
// Handle open state, which is shared with options in context
|
|
const [open, setOpenState] = (0, _reactutilities.useControllableState)({
|
|
state: props.open,
|
|
defaultState: props.defaultOpen,
|
|
initialState: false
|
|
});
|
|
const setOpen = _react.useCallback((event, newState)=>{
|
|
if (disabled) {
|
|
return;
|
|
}
|
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(event, {
|
|
open: newState
|
|
});
|
|
_reactdom.unstable_batchedUpdates(()=>{
|
|
if (!newState && !freeform) {
|
|
setValue(undefined);
|
|
}
|
|
setOpenState(newState);
|
|
});
|
|
}, [
|
|
onOpenChange,
|
|
setOpenState,
|
|
setValue,
|
|
freeform,
|
|
disabled
|
|
]);
|
|
// update active option based on change in open state
|
|
_react.useEffect(()=>{
|
|
if (open) {
|
|
// if it is single-select and there is a selected option, start at the selected option
|
|
if (!multiselect && selectedOptions.length > 0) {
|
|
const selectedOption = getOptionsMatchingValue((v)=>v === selectedOptions[0]).pop();
|
|
if (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.id) {
|
|
activeDescendantController.focus(selectedOption.id);
|
|
}
|
|
}
|
|
} else {
|
|
activeDescendantController.blur();
|
|
}
|
|
// this should only be run in response to changes in the open state
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
open,
|
|
activeDescendantController
|
|
]);
|
|
// Fallback focus when children are updated in an open popover results in no item being focused
|
|
_react.useEffect(()=>{
|
|
if (open && !disableAutoFocus && !activeDescendantController.active()) {
|
|
activeDescendantController.first();
|
|
}
|
|
// this should only be run in response to changes in the open state or children
|
|
}, [
|
|
open,
|
|
children,
|
|
disableAutoFocus,
|
|
activeDescendantController,
|
|
getOptionById
|
|
]);
|
|
const onActiveDescendantChange = (0, _reactutilities.useEventCallback)((event)=>{
|
|
const previousOption = event.detail.previousId ? optionCollection.getOptionById(event.detail.previousId) : null;
|
|
const nextOption = optionCollection.getOptionById(event.detail.id);
|
|
onActiveOptionChange === null || onActiveOptionChange === void 0 ? void 0 : onActiveOptionChange(event, {
|
|
event,
|
|
type: 'change',
|
|
previousOption,
|
|
nextOption
|
|
});
|
|
});
|
|
return {
|
|
...optionCollection,
|
|
freeform,
|
|
disabled,
|
|
selectOption,
|
|
clearSelection,
|
|
selectedOptions,
|
|
activeOption: UNSAFE_activeOption,
|
|
appearance,
|
|
clearable,
|
|
focusVisible,
|
|
ignoreNextBlur,
|
|
inlinePopup,
|
|
mountNode,
|
|
open,
|
|
hasFocus,
|
|
setActiveOption: UNSAFE_setActiveOption,
|
|
setFocusVisible,
|
|
setHasFocus,
|
|
setOpen,
|
|
setValue,
|
|
size,
|
|
value,
|
|
multiselect,
|
|
onOptionClick: (0, _reactutilities.useEventCallback)((e)=>{
|
|
if (!multiselect) {
|
|
setOpen(e, false);
|
|
}
|
|
}),
|
|
onActiveDescendantChange
|
|
};
|
|
};
|