Spaces:
Runtime error
Runtime error
| "use client"; | |
| // src/select.tsx | |
| import * as React from "react"; | |
| import * as ReactDOM from "react-dom"; | |
| import { clamp } from "@radix-ui/number"; | |
| import { composeEventHandlers } from "@radix-ui/primitive"; | |
| import { createCollection } from "@radix-ui/react-collection"; | |
| import { useComposedRefs } from "@radix-ui/react-compose-refs"; | |
| import { createContextScope } from "@radix-ui/react-context"; | |
| import { useDirection } from "@radix-ui/react-direction"; | |
| import { DismissableLayer } from "@radix-ui/react-dismissable-layer"; | |
| import { useFocusGuards } from "@radix-ui/react-focus-guards"; | |
| import { FocusScope } from "@radix-ui/react-focus-scope"; | |
| import { useId } from "@radix-ui/react-id"; | |
| import * as PopperPrimitive from "@radix-ui/react-popper"; | |
| import { createPopperScope } from "@radix-ui/react-popper"; | |
| import { Portal as PortalPrimitive } from "@radix-ui/react-portal"; | |
| import { Primitive } from "@radix-ui/react-primitive"; | |
| import { createSlot } from "@radix-ui/react-slot"; | |
| import { useCallbackRef } from "@radix-ui/react-use-callback-ref"; | |
| import { useControllableState } from "@radix-ui/react-use-controllable-state"; | |
| import { useLayoutEffect } from "@radix-ui/react-use-layout-effect"; | |
| import { usePrevious } from "@radix-ui/react-use-previous"; | |
| import { VISUALLY_HIDDEN_STYLES } from "@radix-ui/react-visually-hidden"; | |
| import { hideOthers } from "aria-hidden"; | |
| import { RemoveScroll } from "react-remove-scroll"; | |
| import { Fragment, jsx, jsxs } from "react/jsx-runtime"; | |
| var OPEN_KEYS = [" ", "Enter", "ArrowUp", "ArrowDown"]; | |
| var SELECTION_KEYS = [" ", "Enter"]; | |
| var SELECT_NAME = "Select"; | |
| var [Collection, useCollection, createCollectionScope] = createCollection(SELECT_NAME); | |
| var [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME, [ | |
| createCollectionScope, | |
| createPopperScope | |
| ]); | |
| var usePopperScope = createPopperScope(); | |
| var [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME); | |
| var [SelectNativeOptionsProvider, useSelectNativeOptionsContext] = createSelectContext(SELECT_NAME); | |
| var Select = (props) => { | |
| const { | |
| __scopeSelect, | |
| children, | |
| open: openProp, | |
| defaultOpen, | |
| onOpenChange, | |
| value: valueProp, | |
| defaultValue, | |
| onValueChange, | |
| dir, | |
| name, | |
| autoComplete, | |
| disabled, | |
| required, | |
| form | |
| } = props; | |
| const popperScope = usePopperScope(__scopeSelect); | |
| const [trigger, setTrigger] = React.useState(null); | |
| const [valueNode, setValueNode] = React.useState(null); | |
| const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false); | |
| const direction = useDirection(dir); | |
| const [open, setOpen] = useControllableState({ | |
| prop: openProp, | |
| defaultProp: defaultOpen ?? false, | |
| onChange: onOpenChange, | |
| caller: SELECT_NAME | |
| }); | |
| const [value, setValue] = useControllableState({ | |
| prop: valueProp, | |
| defaultProp: defaultValue, | |
| onChange: onValueChange, | |
| caller: SELECT_NAME | |
| }); | |
| const triggerPointerDownPosRef = React.useRef(null); | |
| const isFormControl = trigger ? form || !!trigger.closest("form") : true; | |
| const [nativeOptionsSet, setNativeOptionsSet] = React.useState(/* @__PURE__ */ new Set()); | |
| const nativeSelectKey = Array.from(nativeOptionsSet).map((option) => option.props.value).join(";"); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ jsxs( | |
| SelectProvider, | |
| { | |
| required, | |
| scope: __scopeSelect, | |
| trigger, | |
| onTriggerChange: setTrigger, | |
| valueNode, | |
| onValueNodeChange: setValueNode, | |
| valueNodeHasChildren, | |
| onValueNodeHasChildrenChange: setValueNodeHasChildren, | |
| contentId: useId(), | |
| value, | |
| onValueChange: setValue, | |
| open, | |
| onOpenChange: setOpen, | |
| dir: direction, | |
| triggerPointerDownPosRef, | |
| disabled, | |
| children: [ | |
| /* @__PURE__ */ jsx(Collection.Provider, { scope: __scopeSelect, children: /* @__PURE__ */ jsx( | |
| SelectNativeOptionsProvider, | |
| { | |
| scope: props.__scopeSelect, | |
| onNativeOptionAdd: React.useCallback((option) => { | |
| setNativeOptionsSet((prev) => new Set(prev).add(option)); | |
| }, []), | |
| onNativeOptionRemove: React.useCallback((option) => { | |
| setNativeOptionsSet((prev) => { | |
| const optionsSet = new Set(prev); | |
| optionsSet.delete(option); | |
| return optionsSet; | |
| }); | |
| }, []), | |
| children | |
| } | |
| ) }), | |
| isFormControl ? /* @__PURE__ */ jsxs( | |
| SelectBubbleInput, | |
| { | |
| "aria-hidden": true, | |
| required, | |
| tabIndex: -1, | |
| name, | |
| autoComplete, | |
| value, | |
| onChange: (event) => setValue(event.target.value), | |
| disabled, | |
| form, | |
| children: [ | |
| value === void 0 ? /* @__PURE__ */ jsx("option", { value: "" }) : null, | |
| Array.from(nativeOptionsSet) | |
| ] | |
| }, | |
| nativeSelectKey | |
| ) : null | |
| ] | |
| } | |
| ) }); | |
| }; | |
| Select.displayName = SELECT_NAME; | |
| var TRIGGER_NAME = "SelectTrigger"; | |
| var SelectTrigger = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, disabled = false, ...triggerProps } = props; | |
| const popperScope = usePopperScope(__scopeSelect); | |
| const context = useSelectContext(TRIGGER_NAME, __scopeSelect); | |
| const isDisabled = context.disabled || disabled; | |
| const composedRefs = useComposedRefs(forwardedRef, context.onTriggerChange); | |
| const getItems = useCollection(__scopeSelect); | |
| const pointerTypeRef = React.useRef("touch"); | |
| const [searchRef, handleTypeaheadSearch, resetTypeahead] = useTypeaheadSearch((search) => { | |
| const enabledItems = getItems().filter((item) => !item.disabled); | |
| const currentItem = enabledItems.find((item) => item.value === context.value); | |
| const nextItem = findNextItem(enabledItems, search, currentItem); | |
| if (nextItem !== void 0) { | |
| context.onValueChange(nextItem.value); | |
| } | |
| }); | |
| const handleOpen = (pointerEvent) => { | |
| if (!isDisabled) { | |
| context.onOpenChange(true); | |
| resetTypeahead(); | |
| } | |
| if (pointerEvent) { | |
| context.triggerPointerDownPosRef.current = { | |
| x: Math.round(pointerEvent.pageX), | |
| y: Math.round(pointerEvent.pageY) | |
| }; | |
| } | |
| }; | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Anchor, { asChild: true, ...popperScope, children: /* @__PURE__ */ jsx( | |
| Primitive.button, | |
| { | |
| type: "button", | |
| role: "combobox", | |
| "aria-controls": context.contentId, | |
| "aria-expanded": context.open, | |
| "aria-required": context.required, | |
| "aria-autocomplete": "none", | |
| dir: context.dir, | |
| "data-state": context.open ? "open" : "closed", | |
| disabled: isDisabled, | |
| "data-disabled": isDisabled ? "" : void 0, | |
| "data-placeholder": shouldShowPlaceholder(context.value) ? "" : void 0, | |
| ...triggerProps, | |
| ref: composedRefs, | |
| onClick: composeEventHandlers(triggerProps.onClick, (event) => { | |
| event.currentTarget.focus(); | |
| if (pointerTypeRef.current !== "mouse") { | |
| handleOpen(event); | |
| } | |
| }), | |
| onPointerDown: composeEventHandlers(triggerProps.onPointerDown, (event) => { | |
| pointerTypeRef.current = event.pointerType; | |
| const target = event.target; | |
| if (target.hasPointerCapture(event.pointerId)) { | |
| target.releasePointerCapture(event.pointerId); | |
| } | |
| if (event.button === 0 && event.ctrlKey === false && event.pointerType === "mouse") { | |
| handleOpen(event); | |
| event.preventDefault(); | |
| } | |
| }), | |
| onKeyDown: composeEventHandlers(triggerProps.onKeyDown, (event) => { | |
| const isTypingAhead = searchRef.current !== ""; | |
| const isModifierKey = event.ctrlKey || event.altKey || event.metaKey; | |
| if (!isModifierKey && event.key.length === 1) handleTypeaheadSearch(event.key); | |
| if (isTypingAhead && event.key === " ") return; | |
| if (OPEN_KEYS.includes(event.key)) { | |
| handleOpen(); | |
| event.preventDefault(); | |
| } | |
| }) | |
| } | |
| ) }); | |
| } | |
| ); | |
| SelectTrigger.displayName = TRIGGER_NAME; | |
| var VALUE_NAME = "SelectValue"; | |
| var SelectValue = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, className, style, children, placeholder = "", ...valueProps } = props; | |
| const context = useSelectContext(VALUE_NAME, __scopeSelect); | |
| const { onValueNodeHasChildrenChange } = context; | |
| const hasChildren = children !== void 0; | |
| const composedRefs = useComposedRefs(forwardedRef, context.onValueNodeChange); | |
| useLayoutEffect(() => { | |
| onValueNodeHasChildrenChange(hasChildren); | |
| }, [onValueNodeHasChildrenChange, hasChildren]); | |
| return /* @__PURE__ */ jsx( | |
| Primitive.span, | |
| { | |
| ...valueProps, | |
| ref: composedRefs, | |
| style: { pointerEvents: "none" }, | |
| children: shouldShowPlaceholder(context.value) ? /* @__PURE__ */ jsx(Fragment, { children: placeholder }) : children | |
| } | |
| ); | |
| } | |
| ); | |
| SelectValue.displayName = VALUE_NAME; | |
| var ICON_NAME = "SelectIcon"; | |
| var SelectIcon = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, children, ...iconProps } = props; | |
| return /* @__PURE__ */ jsx(Primitive.span, { "aria-hidden": true, ...iconProps, ref: forwardedRef, children: children || "\u25BC" }); | |
| } | |
| ); | |
| SelectIcon.displayName = ICON_NAME; | |
| var PORTAL_NAME = "SelectPortal"; | |
| var SelectPortal = (props) => { | |
| return /* @__PURE__ */ jsx(PortalPrimitive, { asChild: true, ...props }); | |
| }; | |
| SelectPortal.displayName = PORTAL_NAME; | |
| var CONTENT_NAME = "SelectContent"; | |
| var SelectContent = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const context = useSelectContext(CONTENT_NAME, props.__scopeSelect); | |
| const [fragment, setFragment] = React.useState(); | |
| useLayoutEffect(() => { | |
| setFragment(new DocumentFragment()); | |
| }, []); | |
| if (!context.open) { | |
| const frag = fragment; | |
| return frag ? ReactDOM.createPortal( | |
| /* @__PURE__ */ jsx(SelectContentProvider, { scope: props.__scopeSelect, children: /* @__PURE__ */ jsx(Collection.Slot, { scope: props.__scopeSelect, children: /* @__PURE__ */ jsx("div", { children: props.children }) }) }), | |
| frag | |
| ) : null; | |
| } | |
| return /* @__PURE__ */ jsx(SelectContentImpl, { ...props, ref: forwardedRef }); | |
| } | |
| ); | |
| SelectContent.displayName = CONTENT_NAME; | |
| var CONTENT_MARGIN = 10; | |
| var [SelectContentProvider, useSelectContentContext] = createSelectContext(CONTENT_NAME); | |
| var CONTENT_IMPL_NAME = "SelectContentImpl"; | |
| var Slot = createSlot("SelectContent.RemoveScroll"); | |
| var SelectContentImpl = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { | |
| __scopeSelect, | |
| position = "item-aligned", | |
| onCloseAutoFocus, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| // | |
| // PopperContent props | |
| side, | |
| sideOffset, | |
| align, | |
| alignOffset, | |
| arrowPadding, | |
| collisionBoundary, | |
| collisionPadding, | |
| sticky, | |
| hideWhenDetached, | |
| avoidCollisions, | |
| // | |
| ...contentProps | |
| } = props; | |
| const context = useSelectContext(CONTENT_NAME, __scopeSelect); | |
| const [content, setContent] = React.useState(null); | |
| const [viewport, setViewport] = React.useState(null); | |
| const composedRefs = useComposedRefs(forwardedRef, (node) => setContent(node)); | |
| const [selectedItem, setSelectedItem] = React.useState(null); | |
| const [selectedItemText, setSelectedItemText] = React.useState( | |
| null | |
| ); | |
| const getItems = useCollection(__scopeSelect); | |
| const [isPositioned, setIsPositioned] = React.useState(false); | |
| const firstValidItemFoundRef = React.useRef(false); | |
| React.useEffect(() => { | |
| if (content) return hideOthers(content); | |
| }, [content]); | |
| useFocusGuards(); | |
| const focusFirst = React.useCallback( | |
| (candidates) => { | |
| const [firstItem, ...restItems] = getItems().map((item) => item.ref.current); | |
| const [lastItem] = restItems.slice(-1); | |
| const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement; | |
| for (const candidate of candidates) { | |
| if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return; | |
| candidate?.scrollIntoView({ block: "nearest" }); | |
| if (candidate === firstItem && viewport) viewport.scrollTop = 0; | |
| if (candidate === lastItem && viewport) viewport.scrollTop = viewport.scrollHeight; | |
| candidate?.focus(); | |
| if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return; | |
| } | |
| }, | |
| [getItems, viewport] | |
| ); | |
| const focusSelectedItem = React.useCallback( | |
| () => focusFirst([selectedItem, content]), | |
| [focusFirst, selectedItem, content] | |
| ); | |
| React.useEffect(() => { | |
| if (isPositioned) { | |
| focusSelectedItem(); | |
| } | |
| }, [isPositioned, focusSelectedItem]); | |
| const { onOpenChange, triggerPointerDownPosRef } = context; | |
| React.useEffect(() => { | |
| if (content) { | |
| let pointerMoveDelta = { x: 0, y: 0 }; | |
| const handlePointerMove = (event) => { | |
| pointerMoveDelta = { | |
| x: Math.abs(Math.round(event.pageX) - (triggerPointerDownPosRef.current?.x ?? 0)), | |
| y: Math.abs(Math.round(event.pageY) - (triggerPointerDownPosRef.current?.y ?? 0)) | |
| }; | |
| }; | |
| const handlePointerUp = (event) => { | |
| if (pointerMoveDelta.x <= 10 && pointerMoveDelta.y <= 10) { | |
| event.preventDefault(); | |
| } else { | |
| if (!content.contains(event.target)) { | |
| onOpenChange(false); | |
| } | |
| } | |
| document.removeEventListener("pointermove", handlePointerMove); | |
| triggerPointerDownPosRef.current = null; | |
| }; | |
| if (triggerPointerDownPosRef.current !== null) { | |
| document.addEventListener("pointermove", handlePointerMove); | |
| document.addEventListener("pointerup", handlePointerUp, { capture: true, once: true }); | |
| } | |
| return () => { | |
| document.removeEventListener("pointermove", handlePointerMove); | |
| document.removeEventListener("pointerup", handlePointerUp, { capture: true }); | |
| }; | |
| } | |
| }, [content, onOpenChange, triggerPointerDownPosRef]); | |
| React.useEffect(() => { | |
| const close = () => onOpenChange(false); | |
| window.addEventListener("blur", close); | |
| window.addEventListener("resize", close); | |
| return () => { | |
| window.removeEventListener("blur", close); | |
| window.removeEventListener("resize", close); | |
| }; | |
| }, [onOpenChange]); | |
| const [searchRef, handleTypeaheadSearch] = useTypeaheadSearch((search) => { | |
| const enabledItems = getItems().filter((item) => !item.disabled); | |
| const currentItem = enabledItems.find((item) => item.ref.current === document.activeElement); | |
| const nextItem = findNextItem(enabledItems, search, currentItem); | |
| if (nextItem) { | |
| setTimeout(() => nextItem.ref.current.focus()); | |
| } | |
| }); | |
| const itemRefCallback = React.useCallback( | |
| (node, value, disabled) => { | |
| const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; | |
| const isSelectedItem = context.value !== void 0 && context.value === value; | |
| if (isSelectedItem || isFirstValidItem) { | |
| setSelectedItem(node); | |
| if (isFirstValidItem) firstValidItemFoundRef.current = true; | |
| } | |
| }, | |
| [context.value] | |
| ); | |
| const handleItemLeave = React.useCallback(() => content?.focus(), [content]); | |
| const itemTextRefCallback = React.useCallback( | |
| (node, value, disabled) => { | |
| const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; | |
| const isSelectedItem = context.value !== void 0 && context.value === value; | |
| if (isSelectedItem || isFirstValidItem) { | |
| setSelectedItemText(node); | |
| } | |
| }, | |
| [context.value] | |
| ); | |
| const SelectPosition = position === "popper" ? SelectPopperPosition : SelectItemAlignedPosition; | |
| const popperContentProps = SelectPosition === SelectPopperPosition ? { | |
| side, | |
| sideOffset, | |
| align, | |
| alignOffset, | |
| arrowPadding, | |
| collisionBoundary, | |
| collisionPadding, | |
| sticky, | |
| hideWhenDetached, | |
| avoidCollisions | |
| } : {}; | |
| return /* @__PURE__ */ jsx( | |
| SelectContentProvider, | |
| { | |
| scope: __scopeSelect, | |
| content, | |
| viewport, | |
| onViewportChange: setViewport, | |
| itemRefCallback, | |
| selectedItem, | |
| onItemLeave: handleItemLeave, | |
| itemTextRefCallback, | |
| focusSelectedItem, | |
| selectedItemText, | |
| position, | |
| isPositioned, | |
| searchRef, | |
| children: /* @__PURE__ */ jsx(RemoveScroll, { as: Slot, allowPinchZoom: true, children: /* @__PURE__ */ jsx( | |
| FocusScope, | |
| { | |
| asChild: true, | |
| trapped: context.open, | |
| onMountAutoFocus: (event) => { | |
| event.preventDefault(); | |
| }, | |
| onUnmountAutoFocus: composeEventHandlers(onCloseAutoFocus, (event) => { | |
| context.trigger?.focus({ preventScroll: true }); | |
| event.preventDefault(); | |
| }), | |
| children: /* @__PURE__ */ jsx( | |
| DismissableLayer, | |
| { | |
| asChild: true, | |
| disableOutsidePointerEvents: true, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| onFocusOutside: (event) => event.preventDefault(), | |
| onDismiss: () => context.onOpenChange(false), | |
| children: /* @__PURE__ */ jsx( | |
| SelectPosition, | |
| { | |
| role: "listbox", | |
| id: context.contentId, | |
| "data-state": context.open ? "open" : "closed", | |
| dir: context.dir, | |
| onContextMenu: (event) => event.preventDefault(), | |
| ...contentProps, | |
| ...popperContentProps, | |
| onPlaced: () => setIsPositioned(true), | |
| ref: composedRefs, | |
| style: { | |
| // flex layout so we can place the scroll buttons properly | |
| display: "flex", | |
| flexDirection: "column", | |
| // reset the outline by default as the content MAY get focused | |
| outline: "none", | |
| ...contentProps.style | |
| }, | |
| onKeyDown: composeEventHandlers(contentProps.onKeyDown, (event) => { | |
| const isModifierKey = event.ctrlKey || event.altKey || event.metaKey; | |
| if (event.key === "Tab") event.preventDefault(); | |
| if (!isModifierKey && event.key.length === 1) handleTypeaheadSearch(event.key); | |
| if (["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) { | |
| const items = getItems().filter((item) => !item.disabled); | |
| let candidateNodes = items.map((item) => item.ref.current); | |
| if (["ArrowUp", "End"].includes(event.key)) { | |
| candidateNodes = candidateNodes.slice().reverse(); | |
| } | |
| if (["ArrowUp", "ArrowDown"].includes(event.key)) { | |
| const currentElement = event.target; | |
| const currentIndex = candidateNodes.indexOf(currentElement); | |
| candidateNodes = candidateNodes.slice(currentIndex + 1); | |
| } | |
| setTimeout(() => focusFirst(candidateNodes)); | |
| event.preventDefault(); | |
| } | |
| }) | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| ) }) | |
| } | |
| ); | |
| } | |
| ); | |
| SelectContentImpl.displayName = CONTENT_IMPL_NAME; | |
| var ITEM_ALIGNED_POSITION_NAME = "SelectItemAlignedPosition"; | |
| var SelectItemAlignedPosition = React.forwardRef((props, forwardedRef) => { | |
| const { __scopeSelect, onPlaced, ...popperProps } = props; | |
| const context = useSelectContext(CONTENT_NAME, __scopeSelect); | |
| const contentContext = useSelectContentContext(CONTENT_NAME, __scopeSelect); | |
| const [contentWrapper, setContentWrapper] = React.useState(null); | |
| const [content, setContent] = React.useState(null); | |
| const composedRefs = useComposedRefs(forwardedRef, (node) => setContent(node)); | |
| const getItems = useCollection(__scopeSelect); | |
| const shouldExpandOnScrollRef = React.useRef(false); | |
| const shouldRepositionRef = React.useRef(true); | |
| const { viewport, selectedItem, selectedItemText, focusSelectedItem } = contentContext; | |
| const position = React.useCallback(() => { | |
| if (context.trigger && context.valueNode && contentWrapper && content && viewport && selectedItem && selectedItemText) { | |
| const triggerRect = context.trigger.getBoundingClientRect(); | |
| const contentRect = content.getBoundingClientRect(); | |
| const valueNodeRect = context.valueNode.getBoundingClientRect(); | |
| const itemTextRect = selectedItemText.getBoundingClientRect(); | |
| if (context.dir !== "rtl") { | |
| const itemTextOffset = itemTextRect.left - contentRect.left; | |
| const left = valueNodeRect.left - itemTextOffset; | |
| const leftDelta = triggerRect.left - left; | |
| const minContentWidth = triggerRect.width + leftDelta; | |
| const contentWidth = Math.max(minContentWidth, contentRect.width); | |
| const rightEdge = window.innerWidth - CONTENT_MARGIN; | |
| const clampedLeft = clamp(left, [ | |
| CONTENT_MARGIN, | |
| // Prevents the content from going off the starting edge of the | |
| // viewport. It may still go off the ending edge, but this can be | |
| // controlled by the user since they may want to manage overflow in a | |
| // specific way. | |
| // https://github.com/radix-ui/primitives/issues/2049 | |
| Math.max(CONTENT_MARGIN, rightEdge - contentWidth) | |
| ]); | |
| contentWrapper.style.minWidth = minContentWidth + "px"; | |
| contentWrapper.style.left = clampedLeft + "px"; | |
| } else { | |
| const itemTextOffset = contentRect.right - itemTextRect.right; | |
| const right = window.innerWidth - valueNodeRect.right - itemTextOffset; | |
| const rightDelta = window.innerWidth - triggerRect.right - right; | |
| const minContentWidth = triggerRect.width + rightDelta; | |
| const contentWidth = Math.max(minContentWidth, contentRect.width); | |
| const leftEdge = window.innerWidth - CONTENT_MARGIN; | |
| const clampedRight = clamp(right, [ | |
| CONTENT_MARGIN, | |
| Math.max(CONTENT_MARGIN, leftEdge - contentWidth) | |
| ]); | |
| contentWrapper.style.minWidth = minContentWidth + "px"; | |
| contentWrapper.style.right = clampedRight + "px"; | |
| } | |
| const items = getItems(); | |
| const availableHeight = window.innerHeight - CONTENT_MARGIN * 2; | |
| const itemsHeight = viewport.scrollHeight; | |
| const contentStyles = window.getComputedStyle(content); | |
| const contentBorderTopWidth = parseInt(contentStyles.borderTopWidth, 10); | |
| const contentPaddingTop = parseInt(contentStyles.paddingTop, 10); | |
| const contentBorderBottomWidth = parseInt(contentStyles.borderBottomWidth, 10); | |
| const contentPaddingBottom = parseInt(contentStyles.paddingBottom, 10); | |
| const fullContentHeight = contentBorderTopWidth + contentPaddingTop + itemsHeight + contentPaddingBottom + contentBorderBottomWidth; | |
| const minContentHeight = Math.min(selectedItem.offsetHeight * 5, fullContentHeight); | |
| const viewportStyles = window.getComputedStyle(viewport); | |
| const viewportPaddingTop = parseInt(viewportStyles.paddingTop, 10); | |
| const viewportPaddingBottom = parseInt(viewportStyles.paddingBottom, 10); | |
| const topEdgeToTriggerMiddle = triggerRect.top + triggerRect.height / 2 - CONTENT_MARGIN; | |
| const triggerMiddleToBottomEdge = availableHeight - topEdgeToTriggerMiddle; | |
| const selectedItemHalfHeight = selectedItem.offsetHeight / 2; | |
| const itemOffsetMiddle = selectedItem.offsetTop + selectedItemHalfHeight; | |
| const contentTopToItemMiddle = contentBorderTopWidth + contentPaddingTop + itemOffsetMiddle; | |
| const itemMiddleToContentBottom = fullContentHeight - contentTopToItemMiddle; | |
| const willAlignWithoutTopOverflow = contentTopToItemMiddle <= topEdgeToTriggerMiddle; | |
| if (willAlignWithoutTopOverflow) { | |
| const isLastItem = items.length > 0 && selectedItem === items[items.length - 1].ref.current; | |
| contentWrapper.style.bottom = "0px"; | |
| const viewportOffsetBottom = content.clientHeight - viewport.offsetTop - viewport.offsetHeight; | |
| const clampedTriggerMiddleToBottomEdge = Math.max( | |
| triggerMiddleToBottomEdge, | |
| selectedItemHalfHeight + // viewport might have padding bottom, include it to avoid a scrollable viewport | |
| (isLastItem ? viewportPaddingBottom : 0) + viewportOffsetBottom + contentBorderBottomWidth | |
| ); | |
| const height = contentTopToItemMiddle + clampedTriggerMiddleToBottomEdge; | |
| contentWrapper.style.height = height + "px"; | |
| } else { | |
| const isFirstItem = items.length > 0 && selectedItem === items[0].ref.current; | |
| contentWrapper.style.top = "0px"; | |
| const clampedTopEdgeToTriggerMiddle = Math.max( | |
| topEdgeToTriggerMiddle, | |
| contentBorderTopWidth + viewport.offsetTop + // viewport might have padding top, include it to avoid a scrollable viewport | |
| (isFirstItem ? viewportPaddingTop : 0) + selectedItemHalfHeight | |
| ); | |
| const height = clampedTopEdgeToTriggerMiddle + itemMiddleToContentBottom; | |
| contentWrapper.style.height = height + "px"; | |
| viewport.scrollTop = contentTopToItemMiddle - topEdgeToTriggerMiddle + viewport.offsetTop; | |
| } | |
| contentWrapper.style.margin = `${CONTENT_MARGIN}px 0`; | |
| contentWrapper.style.minHeight = minContentHeight + "px"; | |
| contentWrapper.style.maxHeight = availableHeight + "px"; | |
| onPlaced?.(); | |
| requestAnimationFrame(() => shouldExpandOnScrollRef.current = true); | |
| } | |
| }, [ | |
| getItems, | |
| context.trigger, | |
| context.valueNode, | |
| contentWrapper, | |
| content, | |
| viewport, | |
| selectedItem, | |
| selectedItemText, | |
| context.dir, | |
| onPlaced | |
| ]); | |
| useLayoutEffect(() => position(), [position]); | |
| const [contentZIndex, setContentZIndex] = React.useState(); | |
| useLayoutEffect(() => { | |
| if (content) setContentZIndex(window.getComputedStyle(content).zIndex); | |
| }, [content]); | |
| const handleScrollButtonChange = React.useCallback( | |
| (node) => { | |
| if (node && shouldRepositionRef.current === true) { | |
| position(); | |
| focusSelectedItem?.(); | |
| shouldRepositionRef.current = false; | |
| } | |
| }, | |
| [position, focusSelectedItem] | |
| ); | |
| return /* @__PURE__ */ jsx( | |
| SelectViewportProvider, | |
| { | |
| scope: __scopeSelect, | |
| contentWrapper, | |
| shouldExpandOnScrollRef, | |
| onScrollButtonChange: handleScrollButtonChange, | |
| children: /* @__PURE__ */ jsx( | |
| "div", | |
| { | |
| ref: setContentWrapper, | |
| style: { | |
| display: "flex", | |
| flexDirection: "column", | |
| position: "fixed", | |
| zIndex: contentZIndex | |
| }, | |
| children: /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| ...popperProps, | |
| ref: composedRefs, | |
| style: { | |
| // When we get the height of the content, it includes borders. If we were to set | |
| // the height without having `boxSizing: 'border-box'` it would be too big. | |
| boxSizing: "border-box", | |
| // We need to ensure the content doesn't get taller than the wrapper | |
| maxHeight: "100%", | |
| ...popperProps.style | |
| } | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| ); | |
| }); | |
| SelectItemAlignedPosition.displayName = ITEM_ALIGNED_POSITION_NAME; | |
| var POPPER_POSITION_NAME = "SelectPopperPosition"; | |
| var SelectPopperPosition = React.forwardRef((props, forwardedRef) => { | |
| const { | |
| __scopeSelect, | |
| align = "start", | |
| collisionPadding = CONTENT_MARGIN, | |
| ...popperProps | |
| } = props; | |
| const popperScope = usePopperScope(__scopeSelect); | |
| return /* @__PURE__ */ jsx( | |
| PopperPrimitive.Content, | |
| { | |
| ...popperScope, | |
| ...popperProps, | |
| ref: forwardedRef, | |
| align, | |
| collisionPadding, | |
| style: { | |
| // Ensure border-box for floating-ui calculations | |
| boxSizing: "border-box", | |
| ...popperProps.style, | |
| // re-namespace exposed content custom properties | |
| ...{ | |
| "--radix-select-content-transform-origin": "var(--radix-popper-transform-origin)", | |
| "--radix-select-content-available-width": "var(--radix-popper-available-width)", | |
| "--radix-select-content-available-height": "var(--radix-popper-available-height)", | |
| "--radix-select-trigger-width": "var(--radix-popper-anchor-width)", | |
| "--radix-select-trigger-height": "var(--radix-popper-anchor-height)" | |
| } | |
| } | |
| } | |
| ); | |
| }); | |
| SelectPopperPosition.displayName = POPPER_POSITION_NAME; | |
| var [SelectViewportProvider, useSelectViewportContext] = createSelectContext(CONTENT_NAME, {}); | |
| var VIEWPORT_NAME = "SelectViewport"; | |
| var SelectViewport = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, nonce, ...viewportProps } = props; | |
| const contentContext = useSelectContentContext(VIEWPORT_NAME, __scopeSelect); | |
| const viewportContext = useSelectViewportContext(VIEWPORT_NAME, __scopeSelect); | |
| const composedRefs = useComposedRefs(forwardedRef, contentContext.onViewportChange); | |
| const prevScrollTopRef = React.useRef(0); | |
| return /* @__PURE__ */ jsxs(Fragment, { children: [ | |
| /* @__PURE__ */ jsx( | |
| "style", | |
| { | |
| dangerouslySetInnerHTML: { | |
| __html: `[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}` | |
| }, | |
| nonce | |
| } | |
| ), | |
| /* @__PURE__ */ jsx(Collection.Slot, { scope: __scopeSelect, children: /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| "data-radix-select-viewport": "", | |
| role: "presentation", | |
| ...viewportProps, | |
| ref: composedRefs, | |
| style: { | |
| // we use position: 'relative' here on the `viewport` so that when we call | |
| // `selectedItem.offsetTop` in calculations, the offset is relative to the viewport | |
| // (independent of the scrollUpButton). | |
| position: "relative", | |
| flex: 1, | |
| // Viewport should only be scrollable in the vertical direction. | |
| // This won't work in vertical writing modes, so we'll need to | |
| // revisit this if/when that is supported | |
| // https://developer.chrome.com/blog/vertical-form-controls | |
| overflow: "hidden auto", | |
| ...viewportProps.style | |
| }, | |
| onScroll: composeEventHandlers(viewportProps.onScroll, (event) => { | |
| const viewport = event.currentTarget; | |
| const { contentWrapper, shouldExpandOnScrollRef } = viewportContext; | |
| if (shouldExpandOnScrollRef?.current && contentWrapper) { | |
| const scrolledBy = Math.abs(prevScrollTopRef.current - viewport.scrollTop); | |
| if (scrolledBy > 0) { | |
| const availableHeight = window.innerHeight - CONTENT_MARGIN * 2; | |
| const cssMinHeight = parseFloat(contentWrapper.style.minHeight); | |
| const cssHeight = parseFloat(contentWrapper.style.height); | |
| const prevHeight = Math.max(cssMinHeight, cssHeight); | |
| if (prevHeight < availableHeight) { | |
| const nextHeight = prevHeight + scrolledBy; | |
| const clampedNextHeight = Math.min(availableHeight, nextHeight); | |
| const heightDiff = nextHeight - clampedNextHeight; | |
| contentWrapper.style.height = clampedNextHeight + "px"; | |
| if (contentWrapper.style.bottom === "0px") { | |
| viewport.scrollTop = heightDiff > 0 ? heightDiff : 0; | |
| contentWrapper.style.justifyContent = "flex-end"; | |
| } | |
| } | |
| } | |
| } | |
| prevScrollTopRef.current = viewport.scrollTop; | |
| }) | |
| } | |
| ) }) | |
| ] }); | |
| } | |
| ); | |
| SelectViewport.displayName = VIEWPORT_NAME; | |
| var GROUP_NAME = "SelectGroup"; | |
| var [SelectGroupContextProvider, useSelectGroupContext] = createSelectContext(GROUP_NAME); | |
| var SelectGroup = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, ...groupProps } = props; | |
| const groupId = useId(); | |
| return /* @__PURE__ */ jsx(SelectGroupContextProvider, { scope: __scopeSelect, id: groupId, children: /* @__PURE__ */ jsx(Primitive.div, { role: "group", "aria-labelledby": groupId, ...groupProps, ref: forwardedRef }) }); | |
| } | |
| ); | |
| SelectGroup.displayName = GROUP_NAME; | |
| var LABEL_NAME = "SelectLabel"; | |
| var SelectLabel = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, ...labelProps } = props; | |
| const groupContext = useSelectGroupContext(LABEL_NAME, __scopeSelect); | |
| return /* @__PURE__ */ jsx(Primitive.div, { id: groupContext.id, ...labelProps, ref: forwardedRef }); | |
| } | |
| ); | |
| SelectLabel.displayName = LABEL_NAME; | |
| var ITEM_NAME = "SelectItem"; | |
| var [SelectItemContextProvider, useSelectItemContext] = createSelectContext(ITEM_NAME); | |
| var SelectItem = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { | |
| __scopeSelect, | |
| value, | |
| disabled = false, | |
| textValue: textValueProp, | |
| ...itemProps | |
| } = props; | |
| const context = useSelectContext(ITEM_NAME, __scopeSelect); | |
| const contentContext = useSelectContentContext(ITEM_NAME, __scopeSelect); | |
| const isSelected = context.value === value; | |
| const [textValue, setTextValue] = React.useState(textValueProp ?? ""); | |
| const [isFocused, setIsFocused] = React.useState(false); | |
| const composedRefs = useComposedRefs( | |
| forwardedRef, | |
| (node) => contentContext.itemRefCallback?.(node, value, disabled) | |
| ); | |
| const textId = useId(); | |
| const pointerTypeRef = React.useRef("touch"); | |
| const handleSelect = () => { | |
| if (!disabled) { | |
| context.onValueChange(value); | |
| context.onOpenChange(false); | |
| } | |
| }; | |
| if (value === "") { | |
| throw new Error( | |
| "A <Select.Item /> must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder." | |
| ); | |
| } | |
| return /* @__PURE__ */ jsx( | |
| SelectItemContextProvider, | |
| { | |
| scope: __scopeSelect, | |
| value, | |
| disabled, | |
| textId, | |
| isSelected, | |
| onItemTextChange: React.useCallback((node) => { | |
| setTextValue((prevTextValue) => prevTextValue || (node?.textContent ?? "").trim()); | |
| }, []), | |
| children: /* @__PURE__ */ jsx( | |
| Collection.ItemSlot, | |
| { | |
| scope: __scopeSelect, | |
| value, | |
| disabled, | |
| textValue, | |
| children: /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| role: "option", | |
| "aria-labelledby": textId, | |
| "data-highlighted": isFocused ? "" : void 0, | |
| "aria-selected": isSelected && isFocused, | |
| "data-state": isSelected ? "checked" : "unchecked", | |
| "aria-disabled": disabled || void 0, | |
| "data-disabled": disabled ? "" : void 0, | |
| tabIndex: disabled ? void 0 : -1, | |
| ...itemProps, | |
| ref: composedRefs, | |
| onFocus: composeEventHandlers(itemProps.onFocus, () => setIsFocused(true)), | |
| onBlur: composeEventHandlers(itemProps.onBlur, () => setIsFocused(false)), | |
| onClick: composeEventHandlers(itemProps.onClick, () => { | |
| if (pointerTypeRef.current !== "mouse") handleSelect(); | |
| }), | |
| onPointerUp: composeEventHandlers(itemProps.onPointerUp, () => { | |
| if (pointerTypeRef.current === "mouse") handleSelect(); | |
| }), | |
| onPointerDown: composeEventHandlers(itemProps.onPointerDown, (event) => { | |
| pointerTypeRef.current = event.pointerType; | |
| }), | |
| onPointerMove: composeEventHandlers(itemProps.onPointerMove, (event) => { | |
| pointerTypeRef.current = event.pointerType; | |
| if (disabled) { | |
| contentContext.onItemLeave?.(); | |
| } else if (pointerTypeRef.current === "mouse") { | |
| event.currentTarget.focus({ preventScroll: true }); | |
| } | |
| }), | |
| onPointerLeave: composeEventHandlers(itemProps.onPointerLeave, (event) => { | |
| if (event.currentTarget === document.activeElement) { | |
| contentContext.onItemLeave?.(); | |
| } | |
| }), | |
| onKeyDown: composeEventHandlers(itemProps.onKeyDown, (event) => { | |
| const isTypingAhead = contentContext.searchRef?.current !== ""; | |
| if (isTypingAhead && event.key === " ") return; | |
| if (SELECTION_KEYS.includes(event.key)) handleSelect(); | |
| if (event.key === " ") event.preventDefault(); | |
| }) | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| ); | |
| } | |
| ); | |
| SelectItem.displayName = ITEM_NAME; | |
| var ITEM_TEXT_NAME = "SelectItemText"; | |
| var SelectItemText = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, className, style, ...itemTextProps } = props; | |
| const context = useSelectContext(ITEM_TEXT_NAME, __scopeSelect); | |
| const contentContext = useSelectContentContext(ITEM_TEXT_NAME, __scopeSelect); | |
| const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect); | |
| const nativeOptionsContext = useSelectNativeOptionsContext(ITEM_TEXT_NAME, __scopeSelect); | |
| const [itemTextNode, setItemTextNode] = React.useState(null); | |
| const composedRefs = useComposedRefs( | |
| forwardedRef, | |
| (node) => setItemTextNode(node), | |
| itemContext.onItemTextChange, | |
| (node) => contentContext.itemTextRefCallback?.(node, itemContext.value, itemContext.disabled) | |
| ); | |
| const textContent = itemTextNode?.textContent; | |
| const nativeOption = React.useMemo( | |
| () => /* @__PURE__ */ jsx("option", { value: itemContext.value, disabled: itemContext.disabled, children: textContent }, itemContext.value), | |
| [itemContext.disabled, itemContext.value, textContent] | |
| ); | |
| const { onNativeOptionAdd, onNativeOptionRemove } = nativeOptionsContext; | |
| useLayoutEffect(() => { | |
| onNativeOptionAdd(nativeOption); | |
| return () => onNativeOptionRemove(nativeOption); | |
| }, [onNativeOptionAdd, onNativeOptionRemove, nativeOption]); | |
| return /* @__PURE__ */ jsxs(Fragment, { children: [ | |
| /* @__PURE__ */ jsx(Primitive.span, { id: itemContext.textId, ...itemTextProps, ref: composedRefs }), | |
| itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren ? ReactDOM.createPortal(itemTextProps.children, context.valueNode) : null | |
| ] }); | |
| } | |
| ); | |
| SelectItemText.displayName = ITEM_TEXT_NAME; | |
| var ITEM_INDICATOR_NAME = "SelectItemIndicator"; | |
| var SelectItemIndicator = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, ...itemIndicatorProps } = props; | |
| const itemContext = useSelectItemContext(ITEM_INDICATOR_NAME, __scopeSelect); | |
| return itemContext.isSelected ? /* @__PURE__ */ jsx(Primitive.span, { "aria-hidden": true, ...itemIndicatorProps, ref: forwardedRef }) : null; | |
| } | |
| ); | |
| SelectItemIndicator.displayName = ITEM_INDICATOR_NAME; | |
| var SCROLL_UP_BUTTON_NAME = "SelectScrollUpButton"; | |
| var SelectScrollUpButton = React.forwardRef((props, forwardedRef) => { | |
| const contentContext = useSelectContentContext(SCROLL_UP_BUTTON_NAME, props.__scopeSelect); | |
| const viewportContext = useSelectViewportContext(SCROLL_UP_BUTTON_NAME, props.__scopeSelect); | |
| const [canScrollUp, setCanScrollUp] = React.useState(false); | |
| const composedRefs = useComposedRefs(forwardedRef, viewportContext.onScrollButtonChange); | |
| useLayoutEffect(() => { | |
| if (contentContext.viewport && contentContext.isPositioned) { | |
| let handleScroll2 = function() { | |
| const canScrollUp2 = viewport.scrollTop > 0; | |
| setCanScrollUp(canScrollUp2); | |
| }; | |
| var handleScroll = handleScroll2; | |
| const viewport = contentContext.viewport; | |
| handleScroll2(); | |
| viewport.addEventListener("scroll", handleScroll2); | |
| return () => viewport.removeEventListener("scroll", handleScroll2); | |
| } | |
| }, [contentContext.viewport, contentContext.isPositioned]); | |
| return canScrollUp ? /* @__PURE__ */ jsx( | |
| SelectScrollButtonImpl, | |
| { | |
| ...props, | |
| ref: composedRefs, | |
| onAutoScroll: () => { | |
| const { viewport, selectedItem } = contentContext; | |
| if (viewport && selectedItem) { | |
| viewport.scrollTop = viewport.scrollTop - selectedItem.offsetHeight; | |
| } | |
| } | |
| } | |
| ) : null; | |
| }); | |
| SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME; | |
| var SCROLL_DOWN_BUTTON_NAME = "SelectScrollDownButton"; | |
| var SelectScrollDownButton = React.forwardRef((props, forwardedRef) => { | |
| const contentContext = useSelectContentContext(SCROLL_DOWN_BUTTON_NAME, props.__scopeSelect); | |
| const viewportContext = useSelectViewportContext(SCROLL_DOWN_BUTTON_NAME, props.__scopeSelect); | |
| const [canScrollDown, setCanScrollDown] = React.useState(false); | |
| const composedRefs = useComposedRefs(forwardedRef, viewportContext.onScrollButtonChange); | |
| useLayoutEffect(() => { | |
| if (contentContext.viewport && contentContext.isPositioned) { | |
| let handleScroll2 = function() { | |
| const maxScroll = viewport.scrollHeight - viewport.clientHeight; | |
| const canScrollDown2 = Math.ceil(viewport.scrollTop) < maxScroll; | |
| setCanScrollDown(canScrollDown2); | |
| }; | |
| var handleScroll = handleScroll2; | |
| const viewport = contentContext.viewport; | |
| handleScroll2(); | |
| viewport.addEventListener("scroll", handleScroll2); | |
| return () => viewport.removeEventListener("scroll", handleScroll2); | |
| } | |
| }, [contentContext.viewport, contentContext.isPositioned]); | |
| return canScrollDown ? /* @__PURE__ */ jsx( | |
| SelectScrollButtonImpl, | |
| { | |
| ...props, | |
| ref: composedRefs, | |
| onAutoScroll: () => { | |
| const { viewport, selectedItem } = contentContext; | |
| if (viewport && selectedItem) { | |
| viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight; | |
| } | |
| } | |
| } | |
| ) : null; | |
| }); | |
| SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME; | |
| var SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => { | |
| const { __scopeSelect, onAutoScroll, ...scrollIndicatorProps } = props; | |
| const contentContext = useSelectContentContext("SelectScrollButton", __scopeSelect); | |
| const autoScrollTimerRef = React.useRef(null); | |
| const getItems = useCollection(__scopeSelect); | |
| const clearAutoScrollTimer = React.useCallback(() => { | |
| if (autoScrollTimerRef.current !== null) { | |
| window.clearInterval(autoScrollTimerRef.current); | |
| autoScrollTimerRef.current = null; | |
| } | |
| }, []); | |
| React.useEffect(() => { | |
| return () => clearAutoScrollTimer(); | |
| }, [clearAutoScrollTimer]); | |
| useLayoutEffect(() => { | |
| const activeItem = getItems().find((item) => item.ref.current === document.activeElement); | |
| activeItem?.ref.current?.scrollIntoView({ block: "nearest" }); | |
| }, [getItems]); | |
| return /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| "aria-hidden": true, | |
| ...scrollIndicatorProps, | |
| ref: forwardedRef, | |
| style: { flexShrink: 0, ...scrollIndicatorProps.style }, | |
| onPointerDown: composeEventHandlers(scrollIndicatorProps.onPointerDown, () => { | |
| if (autoScrollTimerRef.current === null) { | |
| autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50); | |
| } | |
| }), | |
| onPointerMove: composeEventHandlers(scrollIndicatorProps.onPointerMove, () => { | |
| contentContext.onItemLeave?.(); | |
| if (autoScrollTimerRef.current === null) { | |
| autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50); | |
| } | |
| }), | |
| onPointerLeave: composeEventHandlers(scrollIndicatorProps.onPointerLeave, () => { | |
| clearAutoScrollTimer(); | |
| }) | |
| } | |
| ); | |
| }); | |
| var SEPARATOR_NAME = "SelectSeparator"; | |
| var SelectSeparator = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, ...separatorProps } = props; | |
| return /* @__PURE__ */ jsx(Primitive.div, { "aria-hidden": true, ...separatorProps, ref: forwardedRef }); | |
| } | |
| ); | |
| SelectSeparator.displayName = SEPARATOR_NAME; | |
| var ARROW_NAME = "SelectArrow"; | |
| var SelectArrow = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeSelect, ...arrowProps } = props; | |
| const popperScope = usePopperScope(__scopeSelect); | |
| const context = useSelectContext(ARROW_NAME, __scopeSelect); | |
| const contentContext = useSelectContentContext(ARROW_NAME, __scopeSelect); | |
| return context.open && contentContext.position === "popper" ? /* @__PURE__ */ jsx(PopperPrimitive.Arrow, { ...popperScope, ...arrowProps, ref: forwardedRef }) : null; | |
| } | |
| ); | |
| SelectArrow.displayName = ARROW_NAME; | |
| var BUBBLE_INPUT_NAME = "SelectBubbleInput"; | |
| var SelectBubbleInput = React.forwardRef( | |
| ({ __scopeSelect, value, ...props }, forwardedRef) => { | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| const prevValue = usePrevious(value); | |
| React.useEffect(() => { | |
| const select = ref.current; | |
| if (!select) return; | |
| const selectProto = window.HTMLSelectElement.prototype; | |
| const descriptor = Object.getOwnPropertyDescriptor( | |
| selectProto, | |
| "value" | |
| ); | |
| const setValue = descriptor.set; | |
| if (prevValue !== value && setValue) { | |
| const event = new Event("change", { bubbles: true }); | |
| setValue.call(select, value); | |
| select.dispatchEvent(event); | |
| } | |
| }, [prevValue, value]); | |
| return /* @__PURE__ */ jsx( | |
| Primitive.select, | |
| { | |
| ...props, | |
| style: { ...VISUALLY_HIDDEN_STYLES, ...props.style }, | |
| ref: composedRefs, | |
| defaultValue: value | |
| } | |
| ); | |
| } | |
| ); | |
| SelectBubbleInput.displayName = BUBBLE_INPUT_NAME; | |
| function shouldShowPlaceholder(value) { | |
| return value === "" || value === void 0; | |
| } | |
| function useTypeaheadSearch(onSearchChange) { | |
| const handleSearchChange = useCallbackRef(onSearchChange); | |
| const searchRef = React.useRef(""); | |
| const timerRef = React.useRef(0); | |
| const handleTypeaheadSearch = React.useCallback( | |
| (key) => { | |
| const search = searchRef.current + key; | |
| handleSearchChange(search); | |
| (function updateSearch(value) { | |
| searchRef.current = value; | |
| window.clearTimeout(timerRef.current); | |
| if (value !== "") timerRef.current = window.setTimeout(() => updateSearch(""), 1e3); | |
| })(search); | |
| }, | |
| [handleSearchChange] | |
| ); | |
| const resetTypeahead = React.useCallback(() => { | |
| searchRef.current = ""; | |
| window.clearTimeout(timerRef.current); | |
| }, []); | |
| React.useEffect(() => { | |
| return () => window.clearTimeout(timerRef.current); | |
| }, []); | |
| return [searchRef, handleTypeaheadSearch, resetTypeahead]; | |
| } | |
| function findNextItem(items, search, currentItem) { | |
| const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); | |
| const normalizedSearch = isRepeated ? search[0] : search; | |
| const currentItemIndex = currentItem ? items.indexOf(currentItem) : -1; | |
| let wrappedItems = wrapArray(items, Math.max(currentItemIndex, 0)); | |
| const excludeCurrentItem = normalizedSearch.length === 1; | |
| if (excludeCurrentItem) wrappedItems = wrappedItems.filter((v) => v !== currentItem); | |
| const nextItem = wrappedItems.find( | |
| (item) => item.textValue.toLowerCase().startsWith(normalizedSearch.toLowerCase()) | |
| ); | |
| return nextItem !== currentItem ? nextItem : void 0; | |
| } | |
| function wrapArray(array, startIndex) { | |
| return array.map((_, index) => array[(startIndex + index) % array.length]); | |
| } | |
| var Root2 = Select; | |
| var Trigger = SelectTrigger; | |
| var Value = SelectValue; | |
| var Icon = SelectIcon; | |
| var Portal = SelectPortal; | |
| var Content2 = SelectContent; | |
| var Viewport = SelectViewport; | |
| var Group = SelectGroup; | |
| var Label = SelectLabel; | |
| var Item = SelectItem; | |
| var ItemText = SelectItemText; | |
| var ItemIndicator = SelectItemIndicator; | |
| var ScrollUpButton = SelectScrollUpButton; | |
| var ScrollDownButton = SelectScrollDownButton; | |
| var Separator = SelectSeparator; | |
| var Arrow2 = SelectArrow; | |
| export { | |
| Arrow2 as Arrow, | |
| Content2 as Content, | |
| Group, | |
| Icon, | |
| Item, | |
| ItemIndicator, | |
| ItemText, | |
| Label, | |
| Portal, | |
| Root2 as Root, | |
| ScrollDownButton, | |
| ScrollUpButton, | |
| Select, | |
| SelectArrow, | |
| SelectContent, | |
| SelectGroup, | |
| SelectIcon, | |
| SelectItem, | |
| SelectItemIndicator, | |
| SelectItemText, | |
| SelectLabel, | |
| SelectPortal, | |
| SelectScrollDownButton, | |
| SelectScrollUpButton, | |
| SelectSeparator, | |
| SelectTrigger, | |
| SelectValue, | |
| SelectViewport, | |
| Separator, | |
| Trigger, | |
| Value, | |
| Viewport, | |
| createSelectScope | |
| }; | |
| //# sourceMappingURL=index.mjs.map | |