UNPKG

@procore/core-react

Version:
1,162 lines (1,130 loc) • 55.6 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import { autoUpdate, flip, offset, size, useClick, useDismiss, useFloating, useInteractions, useListNavigation } from '@floating-ui/react'; import { useId } from '@react-aria/utils'; import debounce from 'lodash.debounce'; import React from 'react'; import { ulid } from 'ulid'; import { useI18nContext } from '../_hooks/I18n'; import { useZIndexContext } from '../_hooks/ZIndex'; import { spacing } from '../_styles/spacing'; import * as defaultComponents from './SuperSelect.components'; import { draggableOptionIdSymbol, isOptSelectAllSymbol } from './SuperSelect.constants'; import { extendedSelectMenuWidth } from './SuperSelect.styles'; import { collectGroupsInOrderOfOccurrence, createOptgroup, defaultOnUnselectAll, getBatchOptionFormatter, getIsAllOptionsUngrouped, getOptionIsOptgroup, getOptionIsOptSelectAll, getOptionsSortingAlgorithm, isMultiple, removeEmptyOptGroups, reorder, sortOptgroups } from './SuperSelect.utils'; var listContainerVerticalPadding = spacing.sm * 2; /* Based on item height + padding (~35px), this minimum height is enough to show 4-6 items before * the overflow is hidden before scrolling. This will ensure the dropdown doesn't open with minimal * height, which would lead to a poor scrolling experience. */ var reasonableDropdownMinimumHeight = 248; function noop() {} function defaultGetOptionValue(option) { return option === null || option === void 0 ? void 0 : option.value; } function defaultGetOptionIsBatch(option) { return Array.isArray(option === null || option === void 0 ? void 0 : option.value); } function defaultGetOptionIsDisabled(option) { var _option$disabled; return (_option$disabled = option === null || option === void 0 ? void 0 : option.disabled) !== null && _option$disabled !== void 0 ? _option$disabled : false; } function defaultGetOptionGroup(option) { var _option$group; return (_option$group = option === null || option === void 0 ? void 0 : option.group) !== null && _option$group !== void 0 ? _option$group : ''; } function defaultSetOptionGroup(option, group) { return _objectSpread(_objectSpread({}, option), {}, { group: group }); } function defaultGetOptionLabel(option) { var _option$label; return (_option$label = option === null || option === void 0 ? void 0 : option.label) !== null && _option$label !== void 0 ? _option$label : ''; } function stringContains(str1, str2) { return str1.toLowerCase().includes(str2.toLowerCase()); } function useTokenNavigation(context) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref$enabled = _ref.enabled, enabled = _ref$enabled === void 0 ? true : _ref$enabled, _ref$value = _ref.value, value = _ref$value === void 0 ? [] : _ref$value, tokenRemoveRefs = _ref.tokenRemoveRefs; return { reference: { onKeyDown: function onKeyDown(e) { if (!enabled || !Array.isArray(value) || value.length === 0) { return; } if (e.key === 'ArrowLeft') { var _tokenRemoveRefs$curr; e.preventDefault(); tokenRemoveRefs === null || tokenRemoveRefs === void 0 ? void 0 : (_tokenRemoveRefs$curr = tokenRemoveRefs.current[value.length - 1]) === null || _tokenRemoveRefs$curr === void 0 ? void 0 : _tokenRemoveRefs$curr.focus(); } } } }; } function useKeyboardSelection(context) { var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref2$enabled = _ref2.enabled, enabled = _ref2$enabled === void 0 ? true : _ref2$enabled, _ref2$onSelect = _ref2.onSelect, onSelect = _ref2$onSelect === void 0 ? function () {} : _ref2$onSelect; function onKeyDown(e) { if (!enabled) { return; } if (e.key === 'Enter') { onSelect(); } } return { reference: { onKeyDown: onKeyDown }, floating: { onKeyDown: onKeyDown } }; } export function useSuperSelect(_ref3) { var _ref3$block = _ref3.block, block = _ref3$block === void 0 ? false : _ref3$block, customComponents = _ref3.components, defaultValue = _ref3.defaultValue, _ref3$disabled = _ref3.disabled, disabled = _ref3$disabled === void 0 ? false : _ref3$disabled, _ref3$draggable = _ref3.draggable, draggable = _ref3$draggable === void 0 ? false : _ref3$draggable, _ref3$emptyMessage = _ref3.emptyMessage, emptyMessage = _ref3$emptyMessage === void 0 ? 'No results' : _ref3$emptyMessage, _ref3$selectAllEnable = _ref3.selectAllEnabled, selectAllEnabled = _ref3$selectAllEnable === void 0 ? false : _ref3$selectAllEnable, error = _ref3.error, footer = _ref3.footer, _ref3$getOptionGroup = _ref3.getOptionGroup, getOptionGroup = _ref3$getOptionGroup === void 0 ? defaultGetOptionGroup : _ref3$getOptionGroup, _ref3$getOptionIsBatc = _ref3.getOptionIsBatch, getOptionIsBatch = _ref3$getOptionIsBatc === void 0 ? defaultGetOptionIsBatch : _ref3$getOptionIsBatc, _ref3$getOptionIsDisa = _ref3.getOptionIsDisabled, getOptionIsDisabled = _ref3$getOptionIsDisa === void 0 ? defaultGetOptionIsDisabled : _ref3$getOptionIsDisa, _ref3$getOptionLabel = _ref3.getOptionLabel, getOptionLabel = _ref3$getOptionLabel === void 0 ? defaultGetOptionLabel : _ref3$getOptionLabel, getOptionIsPartiallySelected = _ref3.getOptionIsPartiallySelected, getOptionIsSelected = _ref3.getOptionIsSelected, _ref3$getOptionValue = _ref3.getOptionValue, getOptionValue = _ref3$getOptionValue === void 0 ? defaultGetOptionValue : _ref3$getOptionValue, _ref3$onScrollBottom = _ref3.onScrollBottom, onScrollBottom = _ref3$onScrollBottom === void 0 ? noop : _ref3$onScrollBottom, _ref3$onOpenChange = _ref3.onOpenChange, _onOpenChange = _ref3$onOpenChange === void 0 ? noop : _ref3$onOpenChange, header = _ref3.header, _ref3$loading = _ref3.loading, loading = _ref3$loading === void 0 ? false : _ref3$loading, _ref3$multiple = _ref3.multiple, multiple = _ref3$multiple === void 0 ? false : _ref3$multiple, onChange = _ref3.onChange, onManualSort = _ref3.onManualSort, _ref3$onUnselectAll = _ref3.onUnselectAll, onUnselectAll = _ref3$onUnselectAll === void 0 ? defaultOnUnselectAll : _ref3$onUnselectAll, onSelectAll = _ref3.onSelectAll, _ref3$options = _ref3.options, sourceOptions = _ref3$options === void 0 ? [] : _ref3$options, placeholder = _ref3.placeholder, _ref3$preset = _ref3.preset, preset = _ref3$preset === void 0 ? '' : _ref3$preset, presetProps = _ref3.presetProps, _ref3$search = _ref3.search, search = _ref3$search === void 0 ? true : _ref3$search, _ref3$selectionStyle = _ref3.selectionStyle, selectionStyle = _ref3$selectionStyle === void 0 ? 'highlight' : _ref3$selectionStyle, _ref3$setOptionGroup = _ref3.setOptionGroup, setOptionGroup = _ref3$setOptionGroup === void 0 ? defaultSetOptionGroup : _ref3$setOptionGroup, _ref3$sort = _ref3.sort, sort = _ref3$sort === void 0 ? true : _ref3$sort, _ref3$tabIndex = _ref3.tabIndex, tabIndex = _ref3$tabIndex === void 0 ? 0 : _ref3$tabIndex, value_ = _ref3.value, _ref3$overlayMatchesT = _ref3.overlayMatchesTriggerWidth, overlayMatchesTriggerWidth = _ref3$overlayMatchesT === void 0 ? true : _ref3$overlayMatchesT, ariaLabel = _ref3['aria-label'], ariaLabelledBy = _ref3['aria-labelledby']; React.useEffect(function () { if (!draggable) { return; } var isUsingDefaultGetter = getOptionGroup === defaultGetOptionGroup; var isUsingDefaultSetter = setOptionGroup === defaultSetOptionGroup; if (isUsingDefaultGetter && !isUsingDefaultSetter || !isUsingDefaultGetter && isUsingDefaultSetter) { console.warn("SuperSelect: Using potentially conflicting \"getOptionGroup\" and \"setOptionGroup\" implementations.\n Group reassignment after drag-and-drop operation might be broken."); } }, [draggable, getOptionGroup, setOptionGroup]); var i18n = useI18nContext(); var initialValue = defaultValue !== null && defaultValue !== void 0 ? defaultValue : multiple ? [] : null; var _React$useState = React.useState(initialValue), _React$useState2 = _slicedToArray(_React$useState, 2), val = _React$useState2[0], setVal = _React$useState2[1]; var value = value_ !== undefined ? value_ : val; /** * Tracks whether the "Select All" feature is currently active. * When true, new options that appear will be automatically selected. * This state is activated when all available options are selected and * deactivated when any option is individually deselected. */ var _React$useState3 = React.useState(false), _React$useState4 = _slicedToArray(_React$useState3, 2), isSelectAllActive = _React$useState4[0], setIsSelectAllActive = _React$useState4[1]; /** * Flag to prevent recursive auto-selection calls during state updates. * Ensures that auto-selection logic doesn't trigger multiple times * concurrently, which could cause performance issues or incorrect state. */ var isProcessingAutoSelectionRef = React.useRef(false); /** * Stores a snapshot of previously selected values to compare against * current options for auto-selection. */ var previousSelectedValuesRef = React.useRef([]); function setValue(v) { if (!value_) { setVal(v); } if (onChange) { onChange(v); } } /** * Extracts all primitive values from an option, handling both single values and batch values. * For batch options (arrays), returns all values flattened into a single array. * For single options, wraps the value in an array for consistent processing. * * @param option - The option to extract values from * @returns Array of primitive values contained in the option */ var getOptionValuesFlat = React.useCallback(function (option) { var optionValue = getOptionValue(option); return Array.isArray(optionValue) ? optionValue : [optionValue]; }, [getOptionValue]); /** * Determines if all selectable options are currently selected. * Uses early termination to improve performance when checking if all selectable options are selected. * * Performance optimizations: * - Early exit if value count is less than options count * - Uses early termination in the validation loop * * @param currentValue - Current selected values array * @param currentSelectableOptions - Array of selectable options to check * @returns true if all selectable options are selected, false otherwise */ var checkIfAllSelectableOptionsSelected = React.useCallback(function (currentValue, currentSelectableOptions) { if (!Array.isArray(currentValue) || currentSelectableOptions.length === 0) { return false; } var valueSet = new Set(currentValue); // Use early termination for better performance var _iterator = _createForOfIteratorHelper(currentSelectableOptions), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var option = _step.value; var optionValues = getOptionValuesFlat(option); if (!optionValues.every(function (val) { return valueSet.has(val); })) { return false; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return true; }, [getOptionValuesFlat]); /** * Performs automatic selection of newly added options when Select All is active. * * Algorithm: * 1. Compares current options with previous snapshot to find new options * 2. Automatically selects any newly detected options * 3. Implements safeguards against recursive calls * * Performance considerations: * - Single pass to build current options map and detect new ones * - Set-based deduplication for efficient value management * * @param currentValue - Current selected values */ var executeAutoSelection = React.useCallback(function (currentValue) { // Early exit conditions for performance if (!isSelectAllActive || !multiple || !Array.isArray(currentValue) || isProcessingAutoSelectionRef.current) { return; } previousSelectedValuesRef.current = currentValue; var newlyAddedOptions = []; var currentValueSet = new Set(currentValue); var _iterator2 = _createForOfIteratorHelper(selectableOptions), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _option = _step2.value; var _optionValues = getOptionValuesFlat(_option); var hasNewValue = _optionValues.some(function (val) { return !currentValueSet.has(val); }); if (hasNewValue) { newlyAddedOptions.push(_option); } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (newlyAddedOptions.length === 0) { return; } isProcessingAutoSelectionRef.current = true; var newValuesToAdd = []; // Collect new values that aren't already selected for (var _i = 0, _newlyAddedOptions = newlyAddedOptions; _i < _newlyAddedOptions.length; _i++) { var option = _newlyAddedOptions[_i]; var optionValues = getOptionValuesFlat(option); var _iterator3 = _createForOfIteratorHelper(optionValues), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var _val = _step3.value; if (!currentValueSet.has(_val)) { newValuesToAdd.push(_val); } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } } // Only update if there are new values to add if (newValuesToAdd.length > 0) { // Use Set for efficient deduplication var combinedValueSet = new Set([].concat(_toConsumableArray(currentValue), newValuesToAdd)); setValue(Array.from(combinedValueSet)); } // Reset processing flag isProcessingAutoSelectionRef.current = false; }, [isSelectAllActive, multiple, setValue, getOptionValuesFlat]); // Debounced auto-selection for handling rapid option changes var debouncedAutoSelection = React.useMemo(function () { return debounce(executeAutoSelection, 50); }, // Optimized 50ms debounce [executeAutoSelection]); var navigationList = React.useRef([]); var virtuoso = React.useRef(null); var searchRef = React.useRef(null); var overlayId = useId(); // TODO use React 18 useId var listId = useId(); // TODO use React 18 useId var selectedValueId = useId(); var tokenListId = useId(); var _React$useState5 = React.useState(false), _React$useState6 = _slicedToArray(_React$useState5, 2), open = _React$useState6[0], setOpen = _React$useState6[1]; var _React$useState7 = React.useState(false), _React$useState8 = _slicedToArray(_React$useState7, 2), pointer = _React$useState8[0], setPointer = _React$useState8[1]; var _React$useState9 = React.useState(248), _React$useState0 = _slicedToArray(_React$useState9, 2), width = _React$useState0[0], setWidth = _React$useState0[1]; var _React$useState1 = React.useState(248), _React$useState10 = _slicedToArray(_React$useState1, 2), maxHeight = _React$useState10[0], setMaxHeight = _React$useState10[1]; var _React$useState11 = React.useState(0), _React$useState12 = _slicedToArray(_React$useState11, 2), listHeight = _React$useState12[0], setListHeight = _React$useState12[1]; var _React$useState13 = React.useState(0), _React$useState14 = _slicedToArray(_React$useState13, 2), searchHeight = _React$useState14[0], setSearchHeight = _React$useState14[1]; var _React$useState15 = React.useState(0), _React$useState16 = _slicedToArray(_React$useState15, 2), footerHeight = _React$useState16[0], setFooterHeight = _React$useState16[1]; var listContainerHeight = Math.min(maxHeight - searchHeight - footerHeight + listContainerVerticalPadding, listHeight + listContainerVerticalPadding); var _React$useState17 = React.useState(''), _React$useState18 = _slicedToArray(_React$useState17, 2), searchValue = _React$useState18[0], setSearchValue_ = _React$useState18[1]; var setSearchValue = debounce(setSearchValue_, 250); // TODO use React 18 useDeferredValue var _React$useState19 = React.useState(null), _React$useState20 = _slicedToArray(_React$useState19, 2), activeMenuIndex = _React$useState20[0], setActiveMenuIndex = _React$useState20[1]; var tokenRemoveRefs = React.useRef([]); var components = _objectSpread(_objectSpread({}, defaultComponents), customComponents || {}); // TODO #memogetters: consider having getOption... getter functions memoized by consumers // Until then, exclude these callbacks from effect and memo dependencies var sortOptions = React.useMemo(function () { return getOptionsSortingAlgorithm({ getOptionIsBatch: getOptionIsBatch, getOptionLabel: getOptionLabel }); }, // skip `getOptionIsBatch` and `getOptionLabel` []); var formatBatchOption = React.useMemo(function () { return getBatchOptionFormatter({ value: value, multiple: multiple, getOptionIsBatch: getOptionIsBatch, getOptionValue: getOptionValue }); }, // skip `getOptionIsBatch` and `getOptionValue`, refer to TODO #memogetters [value, multiple]); // collect groups, sort them and populate them with options, and sort the options inside the groups var enforceOptionsSortingOrder = React.useCallback(function groupAndSort(opts) { var _collectGroupsInOrder = collectGroupsInOrderOfOccurrence(opts, getOptionGroup), groups = _collectGroupsInOrder.groups, groupedOptions = _collectGroupsInOrder.groupedOptions; if (getIsAllOptionsUngrouped(groups)) { return opts.sort(sortOptions); } return Object.entries(groupedOptions).sort(function (_ref4, _ref5) { var _ref6 = _slicedToArray(_ref4, 1), groupA = _ref6[0]; var _ref7 = _slicedToArray(_ref5, 1), groupB = _ref7[0]; return sortOptgroups(groupA, groupB); }).flatMap(function (_ref8) { var _ref9 = _slicedToArray(_ref8, 2), groupName = _ref9[0], groupOptions = _ref9[1]; return createOptgroup(groupName, groupOptions.sort(sortOptions)); }); }, // skip `getOptionGroup`, refer to TODO #memogetters [sortOptions]); // collect groups and populate them with options var deriveOptionsSortingOrder = React.useCallback(function (opts) { var _collectGroupsInOrder2 = collectGroupsInOrderOfOccurrence(opts, getOptionGroup), groups = _collectGroupsInOrder2.groups, groupedOptions = _collectGroupsInOrder2.groupedOptions; if (getIsAllOptionsUngrouped(groups)) { return opts; } // display optgroups in the order of occurrence, as they are considered pre-sorted return groups.flatMap(function (group) { var groupOptions = groupedOptions[group]; return createOptgroup(group, groupOptions); }); }, // skip `getOptionGroup`, refer to TODO #memogetters []); var groupAndSortAlgorithm = React.useMemo(function () { return sort ? enforceOptionsSortingOrder : deriveOptionsSortingOrder; }, [sort, enforceOptionsSortingOrder, deriveOptionsSortingOrder]); var options = React.useMemo(function () { var queried = searchValue ? sourceOptions.filter(function (opt) { return stringContains(getOptionLabel(opt), searchValue); }) : _toConsumableArray(sourceOptions); // make a copy of source options to prevent the mutation when sorting return groupAndSortAlgorithm(queried).map(formatBatchOption); }, // skip `getOptionLabel`, refer to TODO #memogetters [groupAndSortAlgorithm, formatBatchOption, searchValue, sourceOptions]); /** * Filtered list of options that can be selected (excludes disabled options and optgroups). * Used for auto-selection logic to determine which options should be automatically * selected when Select All is active and new options appear. */ var selectableOptions = React.useMemo(function () { var result = []; var _iterator4 = _createForOfIteratorHelper(options), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var option = _step4.value; if (isSelectableOption(option)) { result.push(option); } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } return result; }, [options]); /** * Effect function that triggers auto-selection when conditions are met. * Calls the debounced auto-selection logic when: * - Select All is enabled * - Multiple selection mode is active * - Select All state is currently active * - Current value is an array (multiple selection) * * The debouncing helps handle rapid option changes efficiently. */ var autoSelectionEffect = React.useCallback(function () { if (selectAllEnabled && multiple && isSelectAllActive && Array.isArray(value) && value.length) { debouncedAutoSelection(value); } }, [selectAllEnabled, multiple, isSelectAllActive, value, debouncedAutoSelection]); /** * Effect that manages auto-selection behavior and cleanup. * - Triggers auto-selection when dependencies change * - Runs on every render when auto-selection conditions might have changed */ React.useEffect(function () { autoSelectionEffect(); return function () { var _debouncedAutoSelecti; // Cleanup debounced function (_debouncedAutoSelecti = debouncedAutoSelection.cancel) === null || _debouncedAutoSelecti === void 0 ? void 0 : _debouncedAutoSelecti.call(debouncedAutoSelection); // Reset processing flag isProcessingAutoSelectionRef.current = false; }; }, [autoSelectionEffect, debouncedAutoSelection]); var selectAllOption = _defineProperty({ label: i18n.t('core.select.selectAll'), id: 'selectAll' }, isOptSelectAllSymbol, true); var allOptionsWithSelectAll = React.useMemo(function () { if (!selectAllEnabled || !multiple) { return options; } return [selectAllOption].concat(_toConsumableArray(options)); }, [selectAllEnabled, multiple, selectAllOption, options]); var _React$useState21 = React.useState([]), _React$useState22 = _slicedToArray(_React$useState21, 2), draggableOptions = _React$useState22[0], setDraggableOptions = _React$useState22[1]; React.useEffect(function () { if (!draggable) { return; } // make a copy of source options to prevent the mutation when sorting var opts = groupAndSortAlgorithm(sourceOptions.map(function (opt) { return _objectSpread(_objectSpread({}, opt), {}, _defineProperty({}, draggableOptionIdSymbol, ulid())); })); setDraggableOptions(opts); }, // in draggable mode, sourceOptions must be memoized, othewise reorder will not work [draggable, sourceOptions, groupAndSortAlgorithm]); var queriedDraggableOptions = React.useMemo(function () { if (!draggable) { return []; } var queried = searchValue ? removeEmptyOptGroups(draggableOptions.filter(function (opt) { if (!getOptionIsOptgroup(opt)) { return stringContains(getOptionLabel(opt), searchValue); } return true; })) : draggableOptions; return queried.map(formatBatchOption); }, // skip `getOptionLabel`, refer to TODO #memogetters [draggable, draggableOptions, searchValue, formatBatchOption]); // Group indices calculation - needs allOptionsWithSelectAll because: // When SelectAll is enabled, it becomes the first item (index 0) and shifts all other indices by 1. // Group headers need their correct absolute positions in the combined list for navigation to work. var groupIndices = React.useMemo(function () { var baseOptions = selectAllEnabled && multiple ? allOptionsWithSelectAll : options; return baseOptions.reduce(function (acc, opt, i) { if (getOptionIsOptgroup(opt)) { acc.push(i); } return acc; }, []); }, [selectAllEnabled, multiple, allOptionsWithSelectAll, options]); // Selected index calculation - needs allOptionsWithSelectAll because: // The selectedIndex must reference the absolute position in the combined list for keyboard navigation. // When SelectAll is at index 0, the first regular option becomes index 1, affecting selection highlighting. var selectedIndex = React.useMemo(function () { var baseOptions = selectAllEnabled && multiple ? allOptionsWithSelectAll : options; return baseOptions.findIndex(function (option) { return isSelectableOption(option) && getOptionValue(option) === value; }); }, [selectAllEnabled, multiple, allOptionsWithSelectAll, options, value]); // Selected option lookup - needs allOptionsWithSelectAll because: // The selectedOption lookup must search the complete list including SelectAll to find matches. // This ensures proper option detection regardless of whether SelectAll or regular options are selected. var selectedOption = React.useMemo(function () { var val = Array.isArray(value) ? value[0] : value; var baseOptions = selectAllEnabled && multiple ? allOptionsWithSelectAll : options; return baseOptions.find(function (option) { return getOptionValue(option) === val; }); }, [selectAllEnabled, multiple, allOptionsWithSelectAll, options, value]); var selectedLabel = React.useMemo(function () { return selectedOption ? getOptionLabel(selectedOption) : ''; }, [selectedOption]); function isSelectableOption(o) { return !getOptionIsOptgroup(o) && !getOptionIsDisabled(o); } function getFirstSelectableOptionIndex() { // When Select All is enabled in multiple mode, it's always at index 0 and is selectable if (selectAllEnabled && multiple) { return 0; } var baseOptions = options; return baseOptions.findIndex(isSelectableOption) + (selectAllEnabled && multiple ? 1 : 0); } // Helper functions for onSelect optimization /** * Handles the toggle behavior for the Select All option. * * Behavior: * - If all options are selected: deselects all and deactivates Select All * - If not all options are selected: selects all and activates Select All * * Performance optimizations: * - Uses Set for efficient value deduplication * - Leverages existing option checking logic * - Calls optional onSelectAll callback for external handling */ var handleSelectAllToggle = React.useCallback(function () { var getIsSelected = getOptionIsSelected !== null && getOptionIsSelected !== void 0 ? getOptionIsSelected : defaultGetOptionIsSelected; var allSelected = selectableOptions.every(function (opt) { return getIsSelected(opt); }); if (allSelected) { var newValue = onUnselectAll(value, options, searchValue, getOptionValuesFlat); setValue(newValue); } else { if (onSelectAll) { onSelectAll(selectableOptions); } var currentValue = Array.isArray(value) ? value : []; var valueSet = new Set(currentValue); selectableOptions.forEach(function (opt) { getOptionValuesFlat(opt).forEach(function (val) { return valueSet.add(val); }); }); setValue(Array.from(valueSet)); } }, [getOptionIsSelected, selectableOptions, onSelectAll, setIsSelectAllActive, setValue, value, getOptionValuesFlat]); /** * Determines if Select All should be activated based on the new value. * Select All activates when all selectable options become selected, * but only if it wasn't already active (prevents unnecessary state changes). * * @param currentSelectableOptions - Current array of selectable options to check against * @returns true if Select All should be activated, false otherwise */ var shouldActivateSelectAll = React.useCallback(function (currentSelectableOptions) { return selectAllEnabled && !searchValue && checkIfAllSelectableOptionsSelected(value, currentSelectableOptions); }, [selectAllEnabled, searchValue, value, checkIfAllSelectableOptionsSelected]); // Centralize select all active state React.useEffect(function () { var shouldBeActive = shouldActivateSelectAll(selectableOptions) && selectableOptions.length && !!value.length; // When Select All is already active and new selectable options are added, // skip updating the Select All active state here. This allows the existing // auto-selection logic to handle newly added options without triggering // unnecessary state changes in this effect. var hasNewOptions = selectableOptions.length > previousSelectedValuesRef.current.length; var hasRemovedValue = (value || []).length < previousSelectedValuesRef.current.length; if (isSelectAllActive && hasNewOptions && !hasRemovedValue && value.length) { return; } if (shouldBeActive) { setIsSelectAllActive(true); } else { setIsSelectAllActive(false); } }, [isSelectAllActive, selectableOptions, shouldActivateSelectAll, value]); /** * Handles selection of batch options (options with multiple values). * Adds all values from the batch option to the current selection * and activates Select All if all options become selected. * * @param optionValue - Array of primitive values from the batch option */ var handleBatchOptionSelection = React.useCallback(function (optionValue) { // Ensure we're working with an array for multiple selection var currentValue = Array.isArray(value) ? value : []; var newValueSet = new Set([].concat(_toConsumableArray(currentValue), _toConsumableArray(optionValue))); var newValue = Array.from(newValueSet); setValue(newValue); }, [value, setValue]); /** * Handles selection/deselection of individual options in multiple mode. * * Behavior: * - If option is selected: removes it and deactivates Select All * - If option is not selected: adds it and activates Select All if all options become selected * * @param optionValue - The primitive value of the option to toggle */ var handleSingleOptionSelection = React.useCallback(function (optionValue) { // Ensure we're working with an array for multiple selection if (!Array.isArray(value)) { return; } var valueIndex = value.indexOf(optionValue); if (valueIndex >= 0) { // Remove value var newValue = value.filter(function (_, i) { return i !== valueIndex; }); setValue(newValue); } else { // Add value var _newValue = [].concat(_toConsumableArray(value), [optionValue]); setValue(_newValue); } }, [value, setValue]); var onSelect = React.useCallback(function (option) { if (getOptionIsDisabled(option)) { return; } var optionValue = getOptionValue(option); var isSelectAll = getOptionIsOptSelectAll(option); if (isMultiple(multiple, value)) { if (isSelectAll) { handleSelectAllToggle(); } else if (Array.isArray(optionValue)) { handleBatchOptionSelection(optionValue); } else { handleSingleOptionSelection(optionValue); } } else { setValue(optionValue); } }, [getOptionIsDisabled, getOptionValue, getOptionIsOptSelectAll, multiple, value, handleSelectAllToggle, handleBatchOptionSelection, handleSingleOptionSelection, setValue]); function onKeyboardSelect() { if (activeMenuIndex !== null) { // Select All must be included in the list for keyboard navigation to work properly. // The activeMenuIndex corresponds to positions in the complete navigation list including SelectAll. var baseOptions = selectAllEnabled && multiple ? allOptionsWithSelectAll : options; var option = baseOptions[activeMenuIndex]; if (option) { onSelect(option); } } } function defaultGetOptionIsSelected(option) { if (Array.isArray(value)) { var optionValues = getOptionValue(option); if (Array.isArray(optionValues)) { return optionValues.every(function (v) { return value.includes(v); }); } return value.includes(optionValues); } return value === getOptionValue(option); } function defaultGetOptionIsPartiallySelected(option) { if (selectionStyle !== 'checkbox') { return false; } if (Array.isArray(value)) { var optionValues = getOptionValue(option); if (Array.isArray(optionValues)) { return optionValues.some(function (v) { return value.includes(v); }) && !optionValues.every(function (v) { return value.includes(v); }); } } return false; } function defaultGetOptionIsSelectAllPartiallySelected(options, isSelected, isPartiallySelected) { if (selectionStyle !== 'checkbox' && !Array.isArray(value)) { return false; } return options.some(function (option) { return isSelected(option) || isPartiallySelected(option); }) && !options.every(function (option) { return isSelected(option); }); } function defaultGetOptionIsSelectAllSelected(options, isSelected) { if (!multiple) { return false; } return options.every(function (option) { return isSelected(option); }); } function isEmpty() { return Array.isArray(value) ? value.length === 0 : value === null; } var boundWidthRef = React.useRef(open); React.useEffect(function () { boundWidthRef.current = open; }, [open]); var floating = useFloating({ open: open, onOpenChange: function onOpenChange(nextOpen) { setOpen(nextOpen); _onOpenChange(nextOpen); if (!nextOpen && multiple) { requestAnimationFrame(function () { var _searchRef$current; (_searchRef$current = searchRef.current) === null || _searchRef$current === void 0 ? void 0 : _searchRef$current.focus(); }); } }, whileElementsMounted: function whileElementsMounted() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return (/* { animationFrame: true } is used to fix ResizeObserver issue with @floating-ui/react */ autoUpdate.apply(void 0, args.concat([{ animationFrame: true }])) ); }, strategy: 'fixed', middleware: [offset(function (_ref0) { var rects = _ref0.rects; return { mainAxis: 2, crossAxis: overlayMatchesTriggerWidth ? 0 : (rects.floating.width - rects.reference.width) / 2 }; }), flip(), size({ apply: function apply(_ref1) { var availableHeight = _ref1.availableHeight, elements = _ref1.elements; if (boundWidthRef.current) { boundWidthRef.current = false; setWidth(overlayMatchesTriggerWidth ? elements.reference.getBoundingClientRect().width : extendedSelectMenuWidth); } if (availableHeight >= reasonableDropdownMinimumHeight) { setMaxHeight(availableHeight); } }, padding: 10 })] }); var _useZIndexContext = useZIndexContext(), zIndex = _useZIndexContext.value; var activeDescendantId = React.useMemo(function () { if (activeMenuIndex === null) return undefined; var baseOptions = selectAllEnabled && multiple ? allOptionsWithSelectAll : options; var option = baseOptions[activeMenuIndex]; if (!option) return undefined; if (getOptionIsOptSelectAll(option)) return "item-".concat(selectAllOption.id); return "item-".concat(getOptionValue(option)); }, [activeMenuIndex, selectAllEnabled, multiple, allOptionsWithSelectAll, options, selectAllOption]); function getAriaLabelProps() { return _objectSpread(_objectSpread({}, ariaLabel && { 'aria-label': ariaLabel }), ariaLabelledBy && { 'aria-labelledby': ariaLabelledBy }); } var _useInteractions = useInteractions([useClick(floating.context, { enabled: !disabled, keyboardHandlers: false }), useDismiss(floating.context, { enabled: !disabled }), useListNavigation(floating.context, { activeIndex: activeMenuIndex, disabledIndices: groupIndices, enabled: !disabled, listRef: navigationList, loop: true, onNavigate: function onNavigate(i) { return setActiveMenuIndex(function () { return i; }); }, selectedIndex: selectedIndex, virtual: true }), useTokenNavigation(floating.context, { enabled: !disabled && multiple && searchValue === '', tokenRemoveRefs: tokenRemoveRefs, value: value }), useKeyboardSelection(floating.context, { enabled: !disabled, onSelect: onKeyboardSelect }), { reference: _objectSpread(_objectSpread({ $block: block, $disabled: disabled, $error: error, $hasClearIcon: !disabled && !loading, $loading: loading, $multiple: multiple, $open: open, $placeholder: !selectedLabel, 'aria-activedescendant': multiple ? undefined : activeDescendantId }, !multiple && _objectSpread(_objectSpread({ 'aria-controls': open ? overlayId : undefined, 'aria-describedby': selectedValueId, 'aria-expanded': open, 'aria-haspopup': 'listbox' }, getAriaLabelProps()), {}, { role: 'button', tabIndex: disabled ? -1 : tabIndex })), {}, { onKeyDown: function onKeyDown() { setPointer(false); }, search: search }), // TODO fix type floating: { role: search ? 'listbox' : 'none', id: overlayId, onKeyDown: function onKeyDown() { setPointer(false); }, onPointerMove: function onPointerMove() { setPointer(true); }, style: { position: floating.strategy, left: floating.x || 0, top: floating.y || 0, width: width, maxHeight: maxHeight, zIndex: zIndex } } }]), getReferenceProps = _useInteractions.getReferenceProps, getFloatingProps = _useInteractions.getFloatingProps, getItemProps = _useInteractions.getItemProps; function getLabelProps() { return { $hoverable: false, id: selectedValueId }; } function getMultiInputProps() { var showPlaceholder = isEmpty(); return _objectSpread(_objectSpread({ 'aria-activedescendant': activeDescendantId, 'aria-controls': open ? overlayId : undefined, 'aria-expanded': open, 'aria-haspopup': 'listbox' }, getAriaLabelProps()), {}, { role: 'combobox', ref: searchRef, tabIndex: disabled ? -1 : tabIndex, placeholder: showPlaceholder ? placeholder : '', disabled: disabled, onKeyDown: function onKeyDown(e) { if (e.key === 'Tab') { if (open) { var _floating$refs$floati; e.preventDefault(); e.stopPropagation(); (_floating$refs$floati = floating.refs.floating.current) === null || _floating$refs$floati === void 0 ? void 0 : _floating$refs$floati.focus(); } } } }); } function getMultiValueProps(index) { function removeToken() { if (!isMultiple(multiple, value)) return; var nextVal = value.filter(function (_, i) { return i !== index; }); setValue(nextVal); requestAnimationFrame(function () { if (nextVal.length === 0) { var _searchRef$current2; (_searchRef$current2 = searchRef.current) === null || _searchRef$current2 === void 0 ? void 0 : _searchRef$current2.focus(); } else if (index >= nextVal.length) { var _tokenRemoveRefs$curr2; (_tokenRemoveRefs$curr2 = tokenRemoveRefs.current[nextVal.length - 1]) === null || _tokenRemoveRefs$curr2 === void 0 ? void 0 : _tokenRemoveRefs$curr2.focus(); } else { var _tokenRemoveRefs$curr3; (_tokenRemoveRefs$curr3 = tokenRemoveRefs.current[index]) === null || _tokenRemoveRefs$curr3 === void 0 ? void 0 : _tokenRemoveRefs$curr3.focus(); } }); } return { ref: function ref(el) { tokenRemoveRefs.current[index] = el; }, onClick: function onClick(e) { e.stopPropagation(); removeToken(); }, onKeyDown: function onKeyDown(e) { if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') { e.preventDefault(); e.stopPropagation(); removeToken(); } else if (e.key === 'ArrowLeft') { e.preventDefault(); e.stopPropagation(); if (index > 0) { var _tokenRemoveRefs$curr4; (_tokenRemoveRefs$curr4 = tokenRemoveRefs.current[index - 1]) === null || _tokenRemoveRefs$curr4 === void 0 ? void 0 : _tokenRemoveRefs$curr4.focus(); } } else if (e.key === 'ArrowRight') { e.preventDefault(); e.stopPropagation(); if (isMultiple(multiple, value) && index < value.length - 1) { var _tokenRemoveRefs$curr5; (_tokenRemoveRefs$curr5 = tokenRemoveRefs.current[index + 1]) === null || _tokenRemoveRefs$curr5 === void 0 ? void 0 : _tokenRemoveRefs$curr5.focus(); } else { var _searchRef$current3; (_searchRef$current3 = searchRef.current) === null || _searchRef$current3 === void 0 ? void 0 : _searchRef$current3.focus(); } } } }; } function getSearchContainerProps() { return { ref: function ref(el) { var _el$clientHeight; setSearchHeight((_el$clientHeight = el === null || el === void 0 ? void 0 : el.clientHeight) !== null && _el$clientHeight !== void 0 ? _el$clientHeight : 0); } }; } function getSearchProps() { return { 'aria-activedescendant': activeDescendantId, 'aria-controls': listId, 'aria-expanded': open, 'aria-haspopup': 'listbox', onChange: function onChange(value) { setSearchValue(value); }, placeholder: i18n.t('core.select.search'), role: 'combobox' }; } function getHeaderProps() { return { ref: function ref(el) {} }; } function getFooterProps() { return { ref: function ref(el) { var _el$clientHeight2; setFooterHeight((_el$clientHeight2 = el === null || el === void 0 ? void 0 : el.clientHeight) !== null && _el$clientHeight2 !== void 0 ? _el$clientHeight2 : 0); }, onKeyDown: function onKeyDown(e) { if (e.key !== 'Escape') { e.stopPropagation(); } } }; } function getEmptyMessageProps() { return { emptyMessage: emptyMessage }; } function getClearProps() { return { 'aria-hidden': true, 'aria-label': i18n.t('core.select.clear'), tabIndex: -1, onClick: function onClick(e) { // prevent the menu from closing e.stopPropagation(); setValue(multiple ? [] : null); setOpen(true); } }; } function getMenuProps() { return { scrollable: false, style: { height: listContainerHeight } }; } function getVirtuosoProps() { var atBottomStateChange = function atBottomStateChange(atBottom) { if (atBottom) { onScrollBottom(); } }; return { role: 'listbox', id: listId, data: draggable ? queriedDraggableOptions : options, components: { Item: components === null || components === void 0 ? void 0 : components.Item // TODO fix type }, initialTopMostItemIndex: selectedIndex >= 0 ? selectedIndex : 0, style: { flex: '1 1 auto' }, tabIndex: -1, totalListHeightChanged: setListHeight, atBottomStateChange: atBottomStateChange }; } var onDragEnd = React.useCallback(function (result) { if (!result.destination) { return; } if (result.source.index === result.destination.index) { return; } var startIndex = result.source.index; var destinationIndex = result.destination.index; setDraggableOptions(function (options) { var _reorder = reorder(options, startIndex, destinationIndex), nextOptions = _reorder.nextOptions, prevGroup = _reorder.prevGroup, nextGroup = _reorder.nextGroup; var adjustedOptions = prevGroup !== nextGroup ? nextOptions.map(function (option, index) { if (index === destinationIndex) { return setOptionGroup(option, nextGroup); } return option; }) : nextOptions; if (typeof onManualSort === 'function') { setTimeout(function () { onManualSort(adjustedOptions.filter(function (option) { return !getOptionIsOptgroup(option); })); }, 0); } return adjustedOptions; }); }, []); // run effect when the `open` state changes // // if it was closed and is now open, reset the highlighted index to be // the first selectable option // // if it was open and is now closed, reset the search value and focused token index React.useEffect(function () { if (open) { setActiveMenuIndex(function () { return selectedIndex >= 0 ? selectedIndex : getFirstSelecta