@itwin/itwinui-react
Version:
A react component library for iTwinUI
511 lines (510 loc) • 15.1 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', {
value: true,
});
Object.defineProperty(exports, 'Select', {
enumerable: true,
get: function () {
return Select;
},
});
const _interop_require_default = require('@swc/helpers/_/_interop_require_default');
const _interop_require_wildcard = require('@swc/helpers/_/_interop_require_wildcard');
const _react = /*#__PURE__*/ _interop_require_wildcard._(require('react'));
const _classnames = /*#__PURE__*/ _interop_require_default._(
require('classnames'),
);
const _MenuItem = require('../Menu/MenuItem.js');
const _index = require('../../utils/index.js');
const _SelectTag = require('./SelectTag.js');
const _SelectTagContainer = require('./SelectTagContainer.js');
const _Icon = require('../Icon/Icon.js');
const _Popover = require('../Popover/Popover.js');
const _List = require('../List/List.js');
const _react1 = require('@floating-ui/react');
const Select = _react.forwardRef((props, forwardedRef) => {
let { native, ...rest } = props;
let Component = native ? NativeSelect : CustomSelect;
return _react.createElement(Component, {
...rest,
ref: forwardedRef,
});
});
if ('development' === process.env.NODE_ENV) Select.displayName = 'Select';
const NativeSelect = _react.forwardRef((props, forwardedRef) => {
let {
triggerProps,
options,
disabled,
placeholder,
defaultValue: defaultValueProp = void 0 !== placeholder ? '' : void 0,
value: valueProp,
onChange: onChangeProp,
size,
status,
styleType,
required,
...rest
} = props;
return _react.createElement(
_index.InputWithIcon,
{
...rest,
ref: forwardedRef,
},
_react.createElement(
SelectButton,
{
as: 'select',
size: size,
status: status,
styleType: styleType,
disabled: disabled,
defaultValue: void 0 === valueProp ? defaultValueProp : void 0,
value: null === valueProp ? '' : valueProp,
required: required,
...triggerProps,
onKeyDown: (0, _index.mergeEventHandlers)(
triggerProps?.onKeyDown,
(event) => {
if ('Enter' === event.key) event.currentTarget.showPicker?.();
},
),
onChange: (0, _index.mergeEventHandlers)(
triggerProps?.onChange,
(event) => {
onChangeProp?.(event.currentTarget.value, event);
},
),
},
'borderless' !== styleType && void 0 !== placeholder
? _react.createElement(
'option',
{
value: '',
disabled: true,
},
placeholder,
)
: null,
options.map((option) =>
_react.createElement(
'option',
{
key: option.value,
...option,
},
option.label,
),
),
),
_react.createElement(SelectEndIcon, {
disabled: disabled,
}),
);
});
const CustomSelect = _react.forwardRef((props, forwardedRef) => {
let uid = (0, _index.useId)();
let {
options,
value: valueProp,
onChange: onChangeProp,
placeholder,
disabled = false,
size,
itemRenderer,
selectedItemRenderer,
menuClassName,
menuStyle,
multiple = false,
triggerProps,
status,
popoverProps: { portal = true, ...popoverProps } = {},
styleType,
...rest
} = props;
let [isOpen, setIsOpen] = _react.useState(false);
let [liveRegionSelection, setLiveRegionSelection] = _react.useState('');
let [uncontrolledValue, setUncontrolledValue] = _react.useState();
let value = void 0 !== valueProp ? valueProp : uncontrolledValue;
let onChangeRef = (0, _index.useLatestRef)(onChangeProp);
let selectRef = _react.useRef(null);
let show = _react.useCallback(() => {
if (disabled) return;
setIsOpen(true);
popoverProps?.onVisibleChange?.(true);
}, [disabled, popoverProps]);
let hide = _react.useCallback(() => {
setIsOpen(false);
selectRef.current?.focus({
preventScroll: true,
});
popoverProps?.onVisibleChange?.(false);
}, [popoverProps]);
let handleOptionSelection = _react.useCallback(
(option, { isSelected = false } = {}) => {
if (isSingleOnChange(onChangeRef.current, multiple)) {
setUncontrolledValue(option.value);
onChangeRef.current?.(option.value);
hide();
} else {
setUncontrolledValue((prev) =>
isSelected
? prev?.filter((i) => option.value !== i)
: [...(prev ?? []), option.value],
);
onChangeRef.current?.(option.value, isSelected ? 'removed' : 'added');
}
if (isMultipleEnabled(value, multiple)) {
let prevSelectedValue = value || [];
let newSelectedValue = isSelected
? prevSelectedValue.filter((i) => option.value !== i)
: [...prevSelectedValue, option.value];
setLiveRegionSelection(
options
.filter((i) => newSelectedValue.includes(i.value))
.map((item) => item.label)
.filter(Boolean)
.join(', '),
);
}
},
[hide, multiple, onChangeRef, options, value],
);
let menuItems = _react.useMemo(
() =>
options.map((option, index) => {
let isSelected = isMultipleEnabled(value, multiple)
? value?.includes(option.value) ?? false
: value === option.value;
let menuItem = itemRenderer
? itemRenderer(option, {
close: () => setIsOpen(false),
isSelected,
})
: _react.createElement(_MenuItem.MenuItem, null, option.label);
let {
label,
icon,
startIcon: startIconProp,
value: _,
...restOption
} = option;
let startIcon = startIconProp ?? icon;
return _react.cloneElement(menuItem, {
key: `${label}-${index}`,
isSelected,
startIcon: startIcon,
endIcon: isSelected
? _react.createElement(_index.SvgCheckmark, {
'aria-hidden': true,
})
: null,
onClick: () => {
if (option.disabled) return;
handleOptionSelection(option, {
isSelected,
});
},
ref: (el) => {
if (isSelected && !multiple)
el?.scrollIntoView({
block: 'nearest',
});
},
role: 'option',
...restOption,
...menuItem.props,
});
}),
[handleOptionSelection, itemRenderer, multiple, options, value],
);
let selectedItems = _react.useMemo(() => {
if (null == value) return;
return isMultipleEnabled(value, multiple)
? options.filter((option) => value.some((val) => val === option.value))
: options.find((option) => option.value === value);
}, [multiple, options, value]);
let defaultFocusedIndex = _react.useMemo(() => {
let index = 0;
if (Array.isArray(value) && value.length > 0)
index = options.findIndex((option) => option.value === value[0]);
else if (value)
index = options.findIndex((option) => option.value === value);
return index >= 0 ? index : 0;
}, [options, value]);
let tagRenderer = _react.useCallback(
(option) =>
_react.createElement(_SelectTag.SelectTag, {
key: option.label,
label: option.label,
onRemove: disabled
? void 0
: () => {
handleOptionSelection(option, {
isSelected: true,
});
selectRef.current?.focus();
},
}),
[disabled, handleOptionSelection],
);
let popover = (0, _Popover.usePopover)({
visible: isOpen,
matchWidth: true,
closeOnOutsideClick: true,
middleware: {
size: {
maxHeight: 'var(--iui-menu-max-height)',
},
},
...popoverProps,
onVisibleChange: (open) => (open ? show() : hide()),
});
return _react.createElement(
_react.Fragment,
null,
_react.createElement(
_index.InputWithIcon,
{
...rest,
ref: (0, _index.useMergedRefs)(
popover.refs.setPositionReference,
forwardedRef,
),
},
_react.createElement(
SelectButton,
{
...popover.getReferenceProps(),
tabIndex: 0,
role: 'combobox',
size: size,
status: status,
'aria-disabled': disabled ? 'true' : void 0,
'data-iui-disabled': disabled ? 'true' : void 0,
'aria-autocomplete': 'none',
'aria-expanded': isOpen,
'aria-haspopup': 'listbox',
'aria-controls': `${uid}-menu`,
styleType: styleType,
...triggerProps,
ref: (0, _index.useMergedRefs)(
selectRef,
triggerProps?.ref,
popover.refs.setReference,
),
className: (0, _classnames.default)(
{
'iui-placeholder':
(!selectedItems || 0 === selectedItems.length) && !!placeholder,
},
triggerProps?.className,
),
'data-iui-multi': multiple,
},
(!selectedItems || 0 === selectedItems.length) &&
_react.createElement(
_index.Box,
{
as: 'span',
className: 'iui-content',
},
placeholder,
),
isMultipleEnabled(selectedItems, multiple)
? _react.createElement(_index.AutoclearingHiddenLiveRegion, {
text: liveRegionSelection,
})
: _react.createElement(SingleSelectButton, {
selectedItem: selectedItems,
selectedItemRenderer: selectedItemRenderer,
}),
),
_react.createElement(SelectEndIcon, {
disabled: disabled,
isOpen: isOpen,
}),
isMultipleEnabled(selectedItems, multiple)
? _react.createElement(MultipleSelectButton, {
selectedItems: selectedItems,
selectedItemsRenderer: selectedItemRenderer,
tagRenderer: tagRenderer,
size: 'small' === size ? 'small' : void 0,
})
: null,
),
popover.open &&
_react.createElement(
_index.Portal,
{
portal: portal,
},
_react.createElement(
SelectListbox,
{
defaultFocusedIndex: defaultFocusedIndex,
className: menuClassName,
id: `${uid}-menu`,
key: `${uid}-menu`,
...popover.getFloatingProps({
style: menuStyle,
onKeyDown: ({ key }) => {
if ('Tab' === key) hide();
},
}),
ref: popover.refs.setFloating,
},
menuItems,
),
),
);
});
const isMultipleEnabled = (variable, multiple) => multiple;
const isSingleOnChange = (onChange, multiple) => !multiple;
const SelectButton = _react.forwardRef((props, forwardedRef) => {
let { size, status, styleType = 'default', ...rest } = props;
return _react.createElement(_index.Box, {
'data-iui-size': size,
'data-iui-status': status,
'data-iui-variant': 'default' !== styleType ? styleType : void 0,
...rest,
ref: forwardedRef,
className: (0, _classnames.default)(
'iui-select-button',
'iui-field',
props.className,
),
});
});
const SelectEndIcon = _react.forwardRef((props, forwardedRef) => {
let { disabled, isOpen, ...rest } = props;
return _react.createElement(
_Icon.Icon,
{
'aria-hidden': true,
...rest,
ref: forwardedRef,
className: (0, _classnames.default)(
'iui-end-icon',
{
'iui-disabled': disabled,
'iui-open': isOpen,
},
props.className,
),
},
_react.createElement(_index.SvgCaretDownSmall, null),
);
});
const SingleSelectButton = ({ selectedItem, selectedItemRenderer }) => {
let startIcon = selectedItem?.startIcon ?? selectedItem?.icon;
return _react.createElement(
_react.Fragment,
null,
selectedItem && selectedItemRenderer && selectedItemRenderer(selectedItem),
selectedItem &&
!selectedItemRenderer &&
_react.createElement(
_react.Fragment,
null,
startIcon &&
_react.createElement(
_index.Box,
{
as: 'span',
className: 'iui-icon',
'aria-hidden': true,
},
startIcon,
),
_react.createElement(
_index.Box,
{
as: 'span',
className: 'iui-content',
},
selectedItem.label,
),
),
);
};
const MultipleSelectButton = ({
selectedItems,
selectedItemsRenderer,
tagRenderer,
size,
}) => {
let selectedItemsElements = _react.useMemo(() => {
if (!selectedItems) return [];
return selectedItems.map((item) => tagRenderer(item));
}, [selectedItems, tagRenderer]);
return _react.createElement(
_react.Fragment,
null,
selectedItems &&
_react.createElement(
_index.Box,
{
as: 'span',
className: 'iui-content',
},
selectedItemsRenderer
? selectedItemsRenderer(selectedItems)
: _react.createElement(_SelectTagContainer.SelectTagContainer, {
tags: selectedItemsElements,
'data-iui-size': size,
}),
),
);
};
const SelectListbox = _react.forwardRef((props, forwardedRef) => {
let {
defaultFocusedIndex = 0,
autoFocus = true,
children: childrenProp,
className,
...rest
} = props;
let [focusedIndex, setFocusedIndex] = _react.useState(defaultFocusedIndex);
let autoFocusRef = _react.useCallback((element) => {
queueMicrotask(() => {
let firstFocusable = element?.querySelector('[tabindex="0"]');
firstFocusable?.focus();
});
}, []);
let children = _react.useMemo(
() =>
_react.Children.map(childrenProp, (child, index) => {
if (_react.isValidElement(child)) {
let ref = _index.isReact17or18 ? child.ref : child.props.ref;
return _react.createElement(_react1.CompositeItem, {
key: index,
ref: ref,
render: child,
});
}
return child;
}),
[childrenProp],
);
return _react.createElement(
_react1.Composite,
{
render: _react.createElement(_List.List, {
as: 'div',
className: (0, _classnames.default)('iui-menu', className),
}),
orientation: 'vertical',
role: 'listbox',
activeIndex: focusedIndex,
onNavigate: setFocusedIndex,
ref: (0, _index.useMergedRefs)(
forwardedRef,
autoFocus ? autoFocusRef : void 0,
),
...rest,
},
children,
);
});