UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

511 lines (510 loc) 15.1 kB
'use strict'; 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, ); });