@tamagui/select 1.0.1-beta.36 → 1.0.1-beta.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,54 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
  import {
4
+ FloatingFocusManager,
5
+ FloatingOverlay,
6
+ FloatingPortal,
7
+ autoUpdate,
8
+ detectOverflow,
9
+ flip,
4
10
  offset,
5
- useFloating
11
+ size,
12
+ useClick,
13
+ useDismiss,
14
+ useFloating,
15
+ useInteractions,
16
+ useListNavigation,
17
+ useRole,
18
+ useTypeahead
6
19
  } from "@floating-ui/react-dom-interactions";
7
20
  import { usePrevious } from "@radix-ui/react-use-previous";
21
+ import { Button } from "@tamagui/button";
8
22
  import { useComposedRefs } from "@tamagui/compose-refs";
9
- import { composeEventHandlers, useIsomorphicLayoutEffect } from "@tamagui/core";
23
+ import {
24
+ getVariableValue,
25
+ styled,
26
+ useIsomorphicLayoutEffect,
27
+ withStaticProperties
28
+ } from "@tamagui/core";
10
29
  import { useId } from "@tamagui/core";
11
30
  import { createContextScope } from "@tamagui/create-context";
12
31
  import { Separator } from "@tamagui/separator";
13
- import { XStack } from "@tamagui/stacks";
32
+ import { ThemeableStack, XStack, YStack } from "@tamagui/stacks";
33
+ import { Paragraph } from "@tamagui/text";
14
34
  import { useControllableState } from "@tamagui/use-controllable-state";
15
35
  import * as React from "react";
16
36
  import * as ReactDOM from "react-dom";
17
- const OPEN_KEYS = [" ", "Enter", "ArrowUp", "ArrowDown"];
18
- const SELECTION_KEYS = [" ", "Enter"];
37
+ const isFirefox = typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("firefox");
38
+ if (isFirefox) {
39
+ document.body.classList.add("firefox");
40
+ }
41
+ function getVisualOffsetTop() {
42
+ return !/^((?!chrome|android).)*safari/i.test(navigator.userAgent) ? visualViewport.offsetTop : 0;
43
+ }
44
+ __name(getVisualOffsetTop, "getVisualOffsetTop");
45
+ const getSelectItemSize = /* @__PURE__ */ __name((val, { tokens }) => {
46
+ const padding = getVariableValue(tokens.size[val]);
47
+ return {
48
+ paddingHorizontal: padding / 2,
49
+ paddingVertical: padding / 4
50
+ };
51
+ }, "getSelectItemSize");
19
52
  const SELECT_NAME = "Select";
20
53
  const WINDOW_PADDING = 8;
21
54
  const SCROLL_ARROW_VELOCITY = 8;
@@ -24,58 +57,8 @@ const MIN_HEIGHT = 80;
24
57
  const FALLBACK_THRESHOLD = 16;
25
58
  const [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME);
26
59
  const [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME);
27
- const Select = /* @__PURE__ */ __name((props) => {
28
- const {
29
- __scopeSelect,
30
- children,
31
- open: openProp,
32
- defaultOpen,
33
- onOpenChange,
34
- value: valueProp,
35
- defaultValue,
36
- onValueChange,
37
- dir: dir2,
38
- name,
39
- autoComplete
40
- } = props;
41
- const [open, setOpen] = useControllableState({
42
- prop: openProp,
43
- defaultProp: defaultOpen || false,
44
- onChange: onOpenChange
45
- });
46
- const [value, setValue] = useControllableState({
47
- prop: valueProp,
48
- defaultProp: defaultValue || "",
49
- onChange: onValueChange
50
- });
51
- const selectedIndexRef = React.useRef(null);
52
- const activeIndexRef = React.useRef(null);
53
- const prevActiveIndex = usePrevious(activeIndex);
54
- const [showArrows, setShowArrows] = React.useState(false);
55
- const [scrollTop, setScrollTop] = React.useState(0);
56
- const [activeIndex, setActiveIndex] = React.useState(null);
57
- const [selectedIndex, setSelectedIndex] = React.useState(Math.max(0, listContentRef.current.indexOf(value)));
58
- useIsomorphicLayoutEffect(() => {
59
- selectedIndexRef.current = selectedIndex;
60
- activeIndexRef.current = activeIndex;
61
- });
62
- useEffect(() => {
63
- const frame = requestAnimationFrame(() => {
64
- setShowArrows(open);
65
- if (!open) {
66
- setScrollTop(0);
67
- setMiddlewareType("align");
68
- setActiveIndex(null);
69
- setControlledScrolling(false);
70
- }
71
- });
72
- return () => cancelAnimationFrame(frame);
73
- }, [open]);
74
- const showUpArrow = showArrows && scrollTop > SCROLL_ARROW_THRESHOLD;
75
- const showDownArrow = showArrows && floatingRef.current && scrollTop < floatingRef.current.scrollHeight - floatingRef.current.clientHeight - SCROLL_ARROW_THRESHOLD;
76
- return <SelectProvider scope={__scopeSelect} open={open}>{children}</SelectProvider>;
77
- }, "Select");
78
- Select.displayName = SELECT_NAME;
60
+ const OPEN_KEYS = [" ", "Enter", "ArrowUp", "ArrowDown"];
61
+ const SELECTION_KEYS = [" ", "Enter"];
79
62
  const TRIGGER_NAME = "SelectTrigger";
80
63
  const SelectTrigger = React.forwardRef((props, forwardedRef) => {
81
64
  const {
@@ -85,217 +68,679 @@ const SelectTrigger = React.forwardRef((props, forwardedRef) => {
85
68
  ...triggerProps
86
69
  } = props;
87
70
  const context = useSelectContext(TRIGGER_NAME, __scopeSelect);
88
- const composedRefs = useComposedRefs(forwardedRef, context.onTriggerChange);
89
- const getItems = useCollection(__scopeSelect);
90
- const handleOpen = /* @__PURE__ */ __name(() => {
91
- if (!disabled) {
92
- context.onOpenChange(true);
93
- resetTypeahead();
94
- }
95
- }, "handleOpen");
96
- return <Primitive.button type="button" role="combobox" aria-controls={context.contentId} aria-expanded={context.open} aria-autocomplete="none" dir={context.dir} disabled={disabled} data-disabled={disabled ? "" : void 0} {...triggerProps} ref={composedRefs} onPointerDown={composeEventHandlers(triggerProps.onPointerDown, (event) => {
97
- ;
98
- event.target.releasePointerCapture(event.pointerId);
99
- if (event.button === 0 && event.ctrlKey === false) {
100
- handleOpen();
101
- context.triggerPointerDownPosRef.current = {
102
- x: Math.round(event.pageX),
103
- y: Math.round(event.pageY)
104
- };
105
- event.preventDefault();
106
- }
107
- })} onKeyDown={composeEventHandlers(triggerProps.onKeyDown, (event) => {
108
- const isTypingAhead = searchRef.current !== "";
109
- const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
110
- if (!isModifierKey && event.key.length === 1)
111
- handleTypeaheadSearch(event.key);
112
- if (isTypingAhead && event.key === " ")
113
- return;
114
- if (OPEN_KEYS.includes(event.key)) {
115
- handleOpen();
116
- event.preventDefault();
117
- }
118
- })} />;
71
+ return <Button componentName={TRIGGER_NAME} aria-expanded={context.open} aria-autocomplete="none" disabled={disabled} data-disabled={disabled ? "" : void 0} {...triggerProps} ref={forwardedRef} {...context.interactions.getReferenceProps()} />;
119
72
  });
120
73
  SelectTrigger.displayName = TRIGGER_NAME;
121
74
  const VALUE_NAME = "SelectValue";
122
- const SelectValue = React.forwardRef((props, forwardedRef) => {
123
- const { __scopeSelect, className, style, ...valueProps } = props;
124
- return <XStack {...valueProps} pointerEvents="none" />;
75
+ const SelectValueFrame = styled(Paragraph, {
76
+ name: VALUE_NAME
125
77
  });
126
- const ICON_NAME = "SelectIcon";
127
- const SelectIcon = React.forwardRef((props, forwardedRef) => {
128
- const { __scopeSelect, children, ...iconProps } = props;
129
- return <XStack aria-hidden {...iconProps} ref={forwardedRef}>{children || "\u25BC"}</XStack>;
78
+ const SelectValue = SelectValueFrame.extractable(React.forwardRef(({ __scopeSelect, children, placeholder }, forwardedRef) => {
79
+ const context = useSelectContext(VALUE_NAME, __scopeSelect);
80
+ const { onValueNodeHasChildrenChange } = context;
81
+ const hasChildren = children !== void 0;
82
+ const composedRefs = useComposedRefs(forwardedRef, context.onValueNodeChange);
83
+ React.useLayoutEffect(() => {
84
+ onValueNodeHasChildrenChange(hasChildren);
85
+ }, [onValueNodeHasChildrenChange, hasChildren]);
86
+ return <SelectValueFrame ref={composedRefs} pointerEvents="none">{context.value === void 0 && placeholder !== void 0 ? placeholder : children}</SelectValueFrame>;
87
+ }));
88
+ SelectValue.displayName = VALUE_NAME;
89
+ const SelectIcon = styled(XStack, {
90
+ name: "SelectIcon",
91
+ "aria-hidden": true,
92
+ children: <Paragraph>{"\u25BC"}</Paragraph>
130
93
  });
131
- SelectIcon.displayName = ICON_NAME;
132
94
  const CONTENT_NAME = "SelectContent";
133
- const SelectContent = React.forwardRef((props, forwardedRef) => {
134
- const context = useSelectContext(CONTENT_NAME, props.__scopeSelect);
135
- return props.children;
95
+ const SelectContent = React.forwardRef(({ children, __scopeSelect }, forwardedRef) => {
96
+ const context = useSelectContext(CONTENT_NAME, __scopeSelect);
97
+ return <FloatingPortal>{context.open ? <FloatingOverlay lockScroll>{children}</FloatingOverlay> : <div style={{ display: "none" }}>{children}</div>}</FloatingPortal>;
136
98
  });
137
- const GROUP_NAME = "SelectGroup";
138
- const [SelectGroupContextProvider, useSelectGroupContext] = createSelectContext(GROUP_NAME);
139
- const SelectGroup = React.forwardRef((props, forwardedRef) => {
140
- const { __scopeSelect, ...groupProps } = props;
141
- const groupId = useId();
142
- return <SelectGroupContextProvider scope={__scopeSelect} id={groupId}><Primitive.div role="group" aria-labelledby={groupId} {...groupProps} ref={forwardedRef} /></SelectGroupContextProvider>;
99
+ const VIEWPORT_NAME = "SelectViewport";
100
+ const SelectViewportFrame = styled(ThemeableStack, {
101
+ name: VIEWPORT_NAME,
102
+ backgroundColor: "$background",
103
+ elevate: true,
104
+ variants: {
105
+ size: {
106
+ "...size": (val, { tokens }) => {
107
+ var _a;
108
+ return {
109
+ borderRadius: (_a = tokens.size[val]) != null ? _a : val
110
+ };
111
+ }
112
+ }
113
+ },
114
+ defaultVariants: {
115
+ size: "$2"
116
+ }
143
117
  });
144
- SelectGroup.displayName = GROUP_NAME;
145
- const LABEL_NAME = "SelectLabel";
146
- const SelectLabel = React.forwardRef((props, forwardedRef) => {
147
- const { __scopeSelect, ...labelProps } = props;
148
- const groupContext = useSelectGroupContext(LABEL_NAME, __scopeSelect);
149
- return <Primitive.div id={groupContext.id} {...labelProps} ref={forwardedRef} />;
118
+ const SelectViewport = React.forwardRef((props, forwardedRef) => {
119
+ const { __scopeSelect, children, ...viewportProps } = props;
120
+ const context = useSelectContext(VIEWPORT_NAME, __scopeSelect);
121
+ const composedRefs = useComposedRefs(forwardedRef);
122
+ const prevScrollTopRef = React.useRef(0);
123
+ return <>
124
+ <style dangerouslySetInnerHTML={{
125
+ __html: `[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}`
126
+ }} />
127
+ <FloatingFocusManager context={context.floatingContext} preventTabbing><div {...context.interactions.getFloatingProps()}><SelectViewportFrame data-radix-select-viewport="" role="presentation" {...viewportProps} ref={composedRefs}>{children}</SelectViewportFrame></div></FloatingFocusManager>
128
+ </>;
150
129
  });
151
- SelectLabel.displayName = LABEL_NAME;
130
+ SelectViewport.displayName = VIEWPORT_NAME;
152
131
  const ITEM_NAME = "SelectItem";
153
132
  const [SelectItemContextProvider, useSelectItemContext] = createSelectContext(ITEM_NAME);
133
+ const SelectItemFrame = styled(YStack, {
134
+ name: ITEM_NAME,
135
+ tag: "li",
136
+ role: "option",
137
+ width: "100%",
138
+ hoverStyle: {
139
+ backgroundColor: "$backgroundHover"
140
+ },
141
+ focusStyle: {
142
+ backgroundColor: "$backgroundFocus"
143
+ },
144
+ variants: {
145
+ size: {
146
+ "...size": getSelectItemSize
147
+ }
148
+ },
149
+ defaultVariants: {
150
+ size: "$4"
151
+ }
152
+ });
154
153
  const SelectItem = React.forwardRef((props, forwardedRef) => {
155
- const { __scopeSelect, value, disabled = false, textValue: textValueProp, ...itemProps } = props;
154
+ const {
155
+ __scopeSelect,
156
+ value,
157
+ disabled = false,
158
+ textValue: textValueProp,
159
+ index,
160
+ ...itemProps
161
+ } = props;
156
162
  const context = useSelectContext(ITEM_NAME, __scopeSelect);
157
- const contentContext = useSelectContentContext(ITEM_NAME, __scopeSelect);
158
163
  const isSelected = context.value === value;
159
164
  const [textValue, setTextValue] = React.useState(textValueProp != null ? textValueProp : "");
160
165
  const [isFocused, setIsFocused] = React.useState(false);
161
- const composedRefs = useComposedRefs(forwardedRef, isSelected ? contentContext.onSelectedItemChange : void 0);
162
166
  const textId = useId();
163
- const handleSelect = /* @__PURE__ */ __name(() => {
164
- if (!disabled) {
165
- context.onValueChange(value);
166
- context.onOpenChange(false);
167
+ const {
168
+ selectedIndex,
169
+ setSelectedIndex,
170
+ listRef,
171
+ open,
172
+ setOpen,
173
+ onChange,
174
+ activeIndex,
175
+ setActiveIndex,
176
+ dataRef
177
+ } = context;
178
+ const composedRefs = useComposedRefs(forwardedRef, (node) => {
179
+ console.log("node", node);
180
+ if (node instanceof HTMLElement) {
181
+ listRef.current[index] = node;
167
182
  }
168
- }, "handleSelect");
169
- return <SelectItemContextProvider scope={__scopeSelect} value={value} textId={textId} isSelected={isSelected} onItemTextChange={React.useCallback((node) => {
170
- setTextValue((prevTextValue) => {
171
- var _a;
172
- return prevTextValue || ((_a = node == null ? void 0 : node.textContent) != null ? _a : "").trim();
173
- });
174
- }, [])}><Collection.ItemSlot scope={__scopeSelect} value={value} disabled={disabled} textValue={textValue}><Primitive.div role="option" aria-labelledby={textId} aria-selected={isSelected && isFocused} data-state={isSelected ? "active" : "inactive"} 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))} onPointerUp={composeEventHandlers(itemProps.onPointerUp, handleSelect)} onPointerMove={composeEventHandlers(itemProps.onPointerMove, (event) => {
175
- if (disabled) {
176
- contentContext.onItemLeave();
183
+ });
184
+ const timeoutRef = React.useRef();
185
+ const [allowMouseUp, setAllowMouseUp] = React.useState(false);
186
+ function handleSelect() {
187
+ console.log("handling select!");
188
+ setSelectedIndex(index);
189
+ onChange(value);
190
+ setOpen(false);
191
+ }
192
+ __name(handleSelect, "handleSelect");
193
+ React.useEffect(() => {
194
+ clearTimeout(timeoutRef.current);
195
+ if (open) {
196
+ if (selectedIndex !== index) {
197
+ setAllowMouseUp(true);
198
+ } else {
199
+ timeoutRef.current = setTimeout(() => {
200
+ setAllowMouseUp(true);
201
+ }, 300);
202
+ }
177
203
  } else {
178
- event.currentTarget.focus({ preventScroll: true });
204
+ setAllowMouseUp(false);
179
205
  }
180
- })} onPointerLeave={composeEventHandlers(itemProps.onPointerLeave, (event) => {
181
- if (event.currentTarget === document.activeElement) {
182
- contentContext.onItemLeave();
183
- }
184
- })} onKeyDown={composeEventHandlers(itemProps.onKeyDown, (event) => {
185
- const isTypingAhead = contentContext.searchRef.current !== "";
186
- if (isTypingAhead && event.key === " ")
187
- return;
188
- if (SELECTION_KEYS.includes(event.key))
189
- handleSelect();
190
- if (event.key === " ")
206
+ }, [open, index, setActiveIndex, selectedIndex]);
207
+ function handleKeyDown(event) {
208
+ console.log("item key down");
209
+ if (event.key === "Enter" || event.key === " " && !dataRef.current.typing) {
191
210
  event.preventDefault();
192
- })} /></Collection.ItemSlot></SelectItemContextProvider>;
211
+ handleSelect();
212
+ }
213
+ }
214
+ __name(handleKeyDown, "handleKeyDown");
215
+ const selectItemProps = context.interactions.getItemProps({
216
+ onClick: allowMouseUp ? handleSelect : void 0,
217
+ onMouseUp: allowMouseUp ? handleSelect : void 0,
218
+ onKeyDown: handleKeyDown
219
+ });
220
+ return <SelectItemContextProvider scope={__scopeSelect} value={value} textId={textId || ""} isSelected={isSelected} onItemTextChange={React.useCallback((node) => {
221
+ }, [])}><SelectItemFrame ref={composedRefs} aria-labelledby={textId} aria-selected={isSelected && isFocused} data-state={isSelected ? "active" : "inactive"} aria-disabled={disabled || void 0} data-disabled={disabled ? "" : void 0} tabIndex={disabled ? void 0 : -1} {...itemProps} {...selectItemProps} /></SelectItemContextProvider>;
193
222
  });
194
223
  SelectItem.displayName = ITEM_NAME;
195
224
  const ITEM_TEXT_NAME = "SelectItemText";
225
+ const SelectItemTextFrame = styled(Paragraph, {
226
+ name: ITEM_TEXT_NAME,
227
+ cursor: "default"
228
+ });
196
229
  const SelectItemText = React.forwardRef((props, forwardedRef) => {
197
- var _a;
198
230
  const { __scopeSelect, className, style, ...itemTextProps } = props;
199
231
  const context = useSelectContext(ITEM_TEXT_NAME, __scopeSelect);
200
- const contentContext = useSelectContentContext(ITEM_TEXT_NAME, __scopeSelect);
201
232
  const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect);
202
233
  const ref = React.useRef(null);
203
- const composedRefs = useComposedRefs(forwardedRef, ref, itemContext.onItemTextChange, itemContext.isSelected ? contentContext.onSelectedItemTextChange : void 0);
234
+ const composedRefs = useComposedRefs(forwardedRef, ref, itemContext.onItemTextChange);
204
235
  return <>
205
- <Primitive.span id={itemContext.textId} {...itemTextProps} ref={composedRefs} />
236
+ <SelectItemTextFrame id={itemContext.textId} {...itemTextProps} ref={composedRefs} />
206
237
  {itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren ? ReactDOM.createPortal(itemTextProps.children, context.valueNode) : null}
207
- {context.bubbleSelect ? ReactDOM.createPortal(<option value={itemContext.value}>{(_a = ref.current) == null ? void 0 : _a.textContent}</option>, context.bubbleSelect) : null}
208
238
  </>;
209
239
  });
210
240
  SelectItemText.displayName = ITEM_TEXT_NAME;
211
241
  const ITEM_INDICATOR_NAME = "SelectItemIndicator";
242
+ const SelectItemIndicatorFrame = styled(XStack, {
243
+ name: ITEM_TEXT_NAME
244
+ });
212
245
  const SelectItemIndicator = React.forwardRef((props, forwardedRef) => {
213
246
  const { __scopeSelect, ...itemIndicatorProps } = props;
214
247
  const itemContext = useSelectItemContext(ITEM_INDICATOR_NAME, __scopeSelect);
215
- return itemContext.isSelected ? <Primitive.span aria-hidden {...itemIndicatorProps} ref={forwardedRef} /> : null;
248
+ return itemContext.isSelected ? <SelectItemIndicatorFrame aria-hidden {...itemIndicatorProps} ref={forwardedRef} /> : null;
216
249
  });
217
250
  SelectItemIndicator.displayName = ITEM_INDICATOR_NAME;
251
+ const GROUP_NAME = "SelectGroup";
252
+ const [SelectGroupContextProvider, useSelectGroupContext] = createSelectContext(GROUP_NAME);
253
+ const SelectGroupFrame = styled(YStack, {
254
+ name: GROUP_NAME,
255
+ width: "100%"
256
+ });
257
+ const SelectGroup = React.forwardRef((props, forwardedRef) => {
258
+ const { __scopeSelect, ...groupProps } = props;
259
+ const groupId = useId();
260
+ return <SelectGroupContextProvider scope={__scopeSelect} id={groupId || ""}><SelectGroupFrame role="group" aria-labelledby={groupId} {...groupProps} ref={forwardedRef} /></SelectGroupContextProvider>;
261
+ });
262
+ SelectGroup.displayName = GROUP_NAME;
263
+ const LABEL_NAME = "SelectLabel";
264
+ const SelectLabelFrame = styled(SelectItemTextFrame, {
265
+ name: LABEL_NAME,
266
+ variants: {
267
+ size: {
268
+ "...size": (val, extras) => {
269
+ const size2 = getSelectItemSize(val, extras);
270
+ return {
271
+ ...size2,
272
+ paddingTop: size2.paddingVertical * 4
273
+ };
274
+ }
275
+ }
276
+ },
277
+ defaultVariants: {
278
+ size: "$4"
279
+ }
280
+ });
281
+ const SelectLabel = React.forwardRef((props, forwardedRef) => {
282
+ const { __scopeSelect, ...labelProps } = props;
283
+ const groupContext = useSelectGroupContext(LABEL_NAME, __scopeSelect);
284
+ return <SelectLabelFrame id={groupContext.id} {...labelProps} ref={forwardedRef} />;
285
+ });
286
+ SelectLabel.displayName = LABEL_NAME;
218
287
  const SCROLL_UP_BUTTON_NAME = "SelectScrollUpButton";
219
288
  const SelectScrollUpButton = React.forwardRef((props, forwardedRef) => {
220
- const contentContext = useSelectContentContext(SCROLL_UP_BUTTON_NAME, props.__scopeSelect);
221
- const [canScrollUp, setCanScrollUp] = React.useState(false);
222
- const composedRefs = useComposedRefs(forwardedRef, contentContext.onScrollButtonChange);
223
- return canScrollUp ? <SelectScrollButtonImpl {...props} ref={composedRefs} onAutoScroll={() => {
224
- const { viewport, selectedItem } = contentContext;
225
- if (viewport && selectedItem) {
226
- viewport.scrollTop = viewport.scrollTop - selectedItem.offsetHeight;
227
- }
228
- }} /> : null;
289
+ return <SelectScrollButtonImpl componentName={SCROLL_UP_BUTTON_NAME} {...props} dir="up" ref={forwardedRef} />;
229
290
  });
230
291
  SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME;
231
292
  const SCROLL_DOWN_BUTTON_NAME = "SelectScrollDownButton";
232
293
  const SelectScrollDownButton = React.forwardRef((props, forwardedRef) => {
233
- const contentContext = useSelectContentContext(SCROLL_DOWN_BUTTON_NAME, props.__scopeSelect);
234
- const [canScrollDown, setCanScrollDown] = React.useState(false);
235
- const composedRefs = useComposedRefs(forwardedRef, contentContext.onScrollButtonChange);
236
- return canScrollDown ? <SelectScrollButtonImpl {...props} ref={composedRefs} onAutoScroll={() => {
237
- const { viewport, selectedItem } = contentContext;
238
- if (viewport && selectedItem) {
239
- viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
240
- }
241
- }} /> : null;
294
+ return <SelectScrollButtonImpl componentName={SCROLL_DOWN_BUTTON_NAME} {...props} dir="down" ref={forwardedRef} />;
242
295
  });
243
296
  SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME;
244
297
  const SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => {
245
- const { __scopeSelect, onAutoScroll, ...scrollIndicatorProps } = props;
246
- const contentContext = useSelectContentContext("SelectScrollButton", __scopeSelect);
247
- const autoScrollTimerRef = React.useRef(null);
298
+ var _a, _b;
299
+ const { __scopeSelect, dir, componentName, ...scrollIndicatorProps } = props;
300
+ const { floatingRef, increaseHeight, forceUpdate, ...context } = useSelectContext(componentName, __scopeSelect);
248
301
  const intervalRef = React.useRef();
249
302
  const loopingRef = React.useRef(false);
303
+ const isVisible = context[dir === "down" ? "canScrollDown" : "canScrollUp"];
250
304
  const { x, y, reference, floating, strategy, update, refs } = useFloating({
251
305
  strategy: "fixed",
252
306
  placement: dir === "up" ? "top" : "bottom",
253
- middleware: [offset(({ floating: floating2 }) => -floating2.height)]
307
+ middleware: [offset(({ rects }) => -rects.floating.height)]
254
308
  });
255
- return <Primitive.div aria-hidden {...scrollIndicatorProps} ref={forwardedRef} style={{ flexShrink: 0, ...scrollIndicatorProps.style }} onPointerMove={composeEventHandlers(scrollIndicatorProps.onPointerMove, () => {
256
- contentContext.onItemLeave();
257
- if (autoScrollTimerRef.current === null) {
258
- autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50);
309
+ const composedRefs = useComposedRefs(forwardedRef, floating);
310
+ React.useLayoutEffect(() => {
311
+ reference(floatingRef.current);
312
+ }, [reference, floatingRef.current]);
313
+ React.useEffect(() => {
314
+ if (!refs.reference.current || !refs.floating.current || !isVisible) {
315
+ return;
316
+ }
317
+ const cleanup = autoUpdate(refs.reference.current, refs.floating.current, update, {
318
+ animationFrame: true
319
+ });
320
+ return () => {
321
+ clearInterval(intervalRef.current);
322
+ loopingRef.current = false;
323
+ cleanup();
324
+ };
325
+ }, [isVisible, update, refs.floating, refs.reference]);
326
+ if (!isVisible) {
327
+ return null;
328
+ }
329
+ const handleScrollArrowChange = /* @__PURE__ */ __name(() => {
330
+ const floating2 = floatingRef.current;
331
+ const isUp = dir === "up";
332
+ if (floating2) {
333
+ const value = isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY;
334
+ const multi = isUp && floating2.scrollTop <= SCROLL_ARROW_THRESHOLD * 2 || !isUp && floating2.scrollTop >= floating2.scrollHeight - floating2.clientHeight - SCROLL_ARROW_THRESHOLD * 2 ? 2 : 1;
335
+ floating2.scrollTop += multi * (isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY);
336
+ increaseHeight(floating2, multi === 2 ? value * 2 : value);
337
+ forceUpdate();
338
+ }
339
+ }, "handleScrollArrowChange");
340
+ return <YStack ref={composedRefs} componentName={componentName} aria-hidden {...scrollIndicatorProps} position={strategy} left={x || 0} top={y || 0} width={`calc(${((_b = (_a = floatingRef.current) == null ? void 0 : _a.offsetWidth) != null ? _b : 0) - 2}px)`} background={`linear-gradient(to ${dir === "up" ? "bottom" : "top"}, #29282b 50%, rgba(53, 54, 55, 0.01))`} onPointerMove={() => {
341
+ if (!loopingRef.current) {
342
+ intervalRef.current = setInterval(handleScrollArrowChange, 1e3 / 60);
343
+ loopingRef.current = true;
259
344
  }
260
- })} onPointerLeave={composeEventHandlers(scrollIndicatorProps.onPointerLeave, () => {
261
- clearAutoScrollTimer();
262
- })} />;
345
+ }} onPointerLeave={() => {
346
+ loopingRef.current = false;
347
+ clearInterval(intervalRef.current);
348
+ }} />;
263
349
  });
264
- const SEPARATOR_NAME = "SelectSeparator";
265
- const SelectSeparator = Separator;
266
- SelectSeparator.displayName = SEPARATOR_NAME;
267
- const BubbleSelect = React.forwardRef((props, forwardedRef) => {
268
- const { value, ...selectProps } = props;
269
- const ref = React.useRef(null);
270
- const composedRefs = useComposedRefs(forwardedRef, ref);
271
- const prevValue = usePrevious(value);
350
+ const SelectSeparator = styled(Separator, {
351
+ name: "SelectSeparator"
352
+ });
353
+ const Select = withStaticProperties((props) => {
354
+ var _a;
355
+ const {
356
+ __scopeSelect,
357
+ children,
358
+ open: openProp,
359
+ defaultOpen,
360
+ onOpenChange,
361
+ value: valueProp,
362
+ defaultValue,
363
+ onValueChange,
364
+ dir,
365
+ name,
366
+ autoComplete
367
+ } = props;
368
+ const [open, setOpen] = useControllableState({
369
+ prop: openProp,
370
+ defaultProp: defaultOpen || false,
371
+ onChange: onOpenChange
372
+ });
373
+ const [value, setValue] = useControllableState({
374
+ prop: valueProp,
375
+ defaultProp: defaultValue || "",
376
+ onChange: onValueChange
377
+ });
378
+ const [activeIndex, setActiveIndex] = React.useState(null);
379
+ const selectedIndexRef = React.useRef(null);
380
+ const activeIndexRef = React.useRef(null);
381
+ const prevActiveIndex = usePrevious(activeIndex);
382
+ const [showArrows, setShowArrows] = React.useState(false);
383
+ const [scrollTop, setScrollTop] = React.useState(0);
384
+ const listItemsRef = React.useRef([]);
385
+ const listContentRef = React.useRef([
386
+ "Select...",
387
+ ...(_a = React.Children.map(children, (child) => React.Children.map(React.isValidElement(child) && child.props.children, (child2) => child2.props.value))) != null ? _a : []
388
+ ]);
389
+ const [selectedIndex, setSelectedIndex] = React.useState(Math.max(0, listContentRef.current.indexOf(value)));
390
+ const [controlledScrolling, setControlledScrolling] = React.useState(false);
391
+ const [middlewareType, setMiddlewareType] = React.useState("align");
392
+ useIsomorphicLayoutEffect(() => {
393
+ selectedIndexRef.current = selectedIndex;
394
+ activeIndexRef.current = activeIndex;
395
+ });
272
396
  React.useEffect(() => {
273
- const select = ref.current;
274
- const selectProto = window.HTMLSelectElement.prototype;
275
- const descriptor = Object.getOwnPropertyDescriptor(selectProto, "value");
276
- const setValue = descriptor.set;
277
- if (prevValue !== value && setValue) {
278
- const event = new Event("change", { bubbles: true });
279
- setValue.call(select, value);
280
- select.dispatchEvent(event);
397
+ const frame = requestAnimationFrame(() => {
398
+ setShowArrows(open);
399
+ if (!open) {
400
+ setScrollTop(0);
401
+ setMiddlewareType("align");
402
+ setActiveIndex(null);
403
+ setControlledScrolling(false);
404
+ }
405
+ });
406
+ return () => {
407
+ cancelAnimationFrame(frame);
408
+ };
409
+ }, [open]);
410
+ function getFloatingPadding(floating2) {
411
+ var _a2;
412
+ if (!floating2) {
413
+ return 0;
414
+ }
415
+ return Number((_a2 = getComputedStyle(floating2).paddingLeft) == null ? void 0 : _a2.replace("px", ""));
416
+ }
417
+ __name(getFloatingPadding, "getFloatingPadding");
418
+ const { x, y, reference, floating, strategy, context, refs, middlewareData, update } = useFloating({
419
+ open,
420
+ onOpenChange: setOpen,
421
+ placement: "bottom",
422
+ middleware: middlewareType === "align" ? [
423
+ offset(({ rects }) => {
424
+ var _a2;
425
+ const index = (_a2 = activeIndexRef.current) != null ? _a2 : selectedIndexRef.current;
426
+ if (index == null) {
427
+ return 0;
428
+ }
429
+ const item = listItemsRef.current[index];
430
+ if (item == null) {
431
+ return 0;
432
+ }
433
+ const offsetTop = item.offsetTop;
434
+ const itemHeight = item.offsetHeight;
435
+ const height = rects.reference.height;
436
+ return -offsetTop - height - (itemHeight - height) / 2;
437
+ }),
438
+ {
439
+ name: "size",
440
+ async fn(args) {
441
+ var _a2;
442
+ const {
443
+ elements: { floating: floating2 },
444
+ rects: { reference: reference2 },
445
+ middlewareData: middlewareData2
446
+ } = args;
447
+ const overflow = await detectOverflow(args, {
448
+ padding: WINDOW_PADDING
449
+ });
450
+ const top = Math.max(0, overflow.top);
451
+ const bottom = Math.max(0, overflow.bottom);
452
+ const nextY = args.y + top;
453
+ if ((_a2 = middlewareData2.size) == null ? void 0 : _a2.skip) {
454
+ return {
455
+ y: nextY,
456
+ data: {
457
+ y: middlewareData2.size.y
458
+ }
459
+ };
460
+ }
461
+ Object.assign(floating2.style, {
462
+ maxHeight: `${floating2.scrollHeight - Math.abs(top + bottom)}px`,
463
+ minWidth: `${reference2.width + getFloatingPadding(floating2) * 2}px`
464
+ });
465
+ return {
466
+ y: nextY,
467
+ data: {
468
+ y: top,
469
+ skip: true
470
+ },
471
+ reset: {
472
+ rects: true
473
+ }
474
+ };
475
+ }
476
+ }
477
+ ] : [
478
+ offset(5),
479
+ flip(),
480
+ size({
481
+ apply({ rects, availableHeight, elements }) {
482
+ Object.assign(elements.floating.style, {
483
+ width: `${rects.reference.width}px`,
484
+ maxHeight: `${availableHeight}px`
485
+ });
486
+ },
487
+ padding: WINDOW_PADDING
488
+ })
489
+ ]
490
+ });
491
+ const floatingRef = refs.floating;
492
+ const forceUpdate = React.useReducer(() => ({}), {})[1];
493
+ const showUpArrow = showArrows && scrollTop > SCROLL_ARROW_THRESHOLD;
494
+ const showDownArrow = showArrows && floatingRef.current && scrollTop < floatingRef.current.scrollHeight - floatingRef.current.clientHeight - SCROLL_ARROW_THRESHOLD;
495
+ console.log("Select", {
496
+ activeIndex,
497
+ selectedIndex
498
+ });
499
+ const interactions = useInteractions([
500
+ useClick(context, { pointerDown: true }),
501
+ useRole(context, { role: "listbox" }),
502
+ useDismiss(context),
503
+ useListNavigation(context, {
504
+ listRef: listItemsRef,
505
+ activeIndex,
506
+ selectedIndex,
507
+ onNavigate: setActiveIndex
508
+ }),
509
+ useTypeahead(context, {
510
+ listRef: listContentRef,
511
+ onMatch: open ? setActiveIndex : setSelectedIndex,
512
+ selectedIndex,
513
+ activeIndex
514
+ })
515
+ ]);
516
+ const increaseHeight = React.useCallback((floating2, amount = 0) => {
517
+ if (middlewareType === "fallback") {
518
+ return;
519
+ }
520
+ const currentMaxHeight = Number(floating2.style.maxHeight.replace("px", ""));
521
+ const currentTop = Number(floating2.style.top.replace("px", ""));
522
+ const rect = floating2.getBoundingClientRect();
523
+ const rectTop = rect.top;
524
+ const rectBottom = rect.bottom;
525
+ const visualMaxHeight = visualViewport.height - WINDOW_PADDING * 2;
526
+ if (amount < 0 && selectedIndexRef.current != null && Math.round(rectBottom) < Math.round(visualViewport.height + getVisualOffsetTop() - WINDOW_PADDING)) {
527
+ floating2.style.maxHeight = `${Math.min(visualMaxHeight, currentMaxHeight - amount)}px`;
528
+ }
529
+ if (amount > 0 && Math.round(rectTop) > Math.round(WINDOW_PADDING - getVisualOffsetTop()) && floating2.scrollHeight > floating2.offsetHeight) {
530
+ const nextTop = Math.max(WINDOW_PADDING + getVisualOffsetTop(), currentTop - amount);
531
+ const nextMaxHeight = Math.min(visualMaxHeight, currentMaxHeight + amount);
532
+ Object.assign(floating2.style, {
533
+ maxHeight: `${nextMaxHeight}px`,
534
+ top: `${nextTop}px`
535
+ });
536
+ if (nextTop - WINDOW_PADDING > getVisualOffsetTop()) {
537
+ floating2.scrollTop -= nextMaxHeight - currentMaxHeight + getFloatingPadding(floating2);
538
+ }
539
+ return currentTop - nextTop;
540
+ }
541
+ }, [middlewareType]);
542
+ const touchPageYRef = React.useRef(null);
543
+ const handleWheel = React.useCallback((event) => {
544
+ var _a2, _b;
545
+ const pinching = event.ctrlKey;
546
+ const currentTarget = event.currentTarget;
547
+ function isWheelEvent(event2) {
548
+ return typeof event2.deltaY === "number";
549
+ }
550
+ __name(isWheelEvent, "isWheelEvent");
551
+ function isTouchEvent(event2) {
552
+ return event2.touches != null;
553
+ }
554
+ __name(isTouchEvent, "isTouchEvent");
555
+ if (Math.abs(((_a2 = currentTarget == null ? void 0 : currentTarget.offsetHeight) != null ? _a2 : 0) - (visualViewport.height - WINDOW_PADDING * 2)) > 1 && !pinching) {
556
+ event.preventDefault();
557
+ } else if (isWheelEvent(event) && isFirefox) {
558
+ currentTarget.scrollTop += event.deltaY;
559
+ }
560
+ if (!pinching) {
561
+ let delta = 5;
562
+ if (isTouchEvent(event)) {
563
+ const currentPageY = touchPageYRef.current;
564
+ const pageY = (_b = event.touches[0]) == null ? void 0 : _b.pageY;
565
+ if (pageY != null) {
566
+ touchPageYRef.current = pageY;
567
+ if (currentPageY != null) {
568
+ delta = currentPageY - pageY;
569
+ }
570
+ }
571
+ }
572
+ increaseHeight(currentTarget, isWheelEvent(event) ? event.deltaY : delta);
573
+ setScrollTop(currentTarget.scrollTop);
574
+ forceUpdate();
281
575
  }
282
- }, [prevValue, value]);
283
- return null;
284
- return <VisuallyHidden asChild><select {...selectProps} ref={composedRefs} defaultValue={value} /></VisuallyHidden>;
576
+ }, [increaseHeight, forceUpdate]);
577
+ React.useEffect(() => {
578
+ function onTouchEnd() {
579
+ touchPageYRef.current = null;
580
+ }
581
+ __name(onTouchEnd, "onTouchEnd");
582
+ const floating2 = floatingRef.current;
583
+ if (open && floating2 && middlewareType === "align") {
584
+ floating2.addEventListener("wheel", handleWheel);
585
+ floating2.addEventListener("touchmove", handleWheel);
586
+ floating2.addEventListener("touchend", onTouchEnd, { passive: true });
587
+ return () => {
588
+ floating2.removeEventListener("wheel", handleWheel);
589
+ floating2.removeEventListener("touchmove", handleWheel);
590
+ floating2.removeEventListener("touchend", onTouchEnd);
591
+ };
592
+ }
593
+ }, [open, floatingRef, handleWheel, middlewareType]);
594
+ React.useEffect(() => {
595
+ window.addEventListener("resize", update);
596
+ return () => {
597
+ window.removeEventListener("resize", update);
598
+ };
599
+ }, [update]);
600
+ React.useLayoutEffect(() => {
601
+ var _a2, _b, _c;
602
+ const floating2 = floatingRef.current;
603
+ if (open && controlledScrolling && floating2) {
604
+ const item = activeIndex != null ? listItemsRef.current[activeIndex] : selectedIndex != null ? listItemsRef.current[selectedIndex] : null;
605
+ if (item && prevActiveIndex != null) {
606
+ const itemHeight = (_b = (_a2 = listItemsRef.current[prevActiveIndex]) == null ? void 0 : _a2.offsetHeight) != null ? _b : 0;
607
+ const floatingHeight = floating2.offsetHeight;
608
+ const top = item.offsetTop;
609
+ const bottom = top + itemHeight;
610
+ if (top < floating2.scrollTop + 20) {
611
+ const diff = floating2.scrollTop - top + 20;
612
+ floating2.scrollTop -= diff;
613
+ if (activeIndex != selectedIndex && activeIndex != null) {
614
+ increaseHeight(floating2, -diff);
615
+ }
616
+ } else if (bottom > floatingHeight + floating2.scrollTop - 20) {
617
+ const diff = bottom - floatingHeight - floating2.scrollTop + 20;
618
+ floating2.scrollTop += diff;
619
+ if (activeIndex != selectedIndex && activeIndex != null) {
620
+ floating2.scrollTop -= (_c = increaseHeight(floating2, diff)) != null ? _c : 0;
621
+ }
622
+ }
623
+ }
624
+ }
625
+ }, [
626
+ open,
627
+ controlledScrolling,
628
+ prevActiveIndex,
629
+ activeIndex,
630
+ selectedIndex,
631
+ floatingRef,
632
+ increaseHeight
633
+ ]);
634
+ React.useLayoutEffect(() => {
635
+ var _a2;
636
+ const floating2 = refs.floating.current;
637
+ const reference2 = refs.reference.current;
638
+ if (open && floating2 && reference2 && floating2.offsetHeight < floating2.scrollHeight) {
639
+ const referenceRect = reference2.getBoundingClientRect();
640
+ if (middlewareType === "fallback") {
641
+ const item = listItemsRef.current[selectedIndex];
642
+ if (item) {
643
+ floating2.scrollTop = item.offsetTop - floating2.clientHeight + referenceRect.height;
644
+ }
645
+ return;
646
+ }
647
+ floating2.scrollTop = (_a2 = middlewareData.size) == null ? void 0 : _a2.y;
648
+ const closeToBottom = visualViewport.height + getVisualOffsetTop() - referenceRect.bottom < FALLBACK_THRESHOLD;
649
+ const closeToTop = referenceRect.top < FALLBACK_THRESHOLD;
650
+ if (floating2.offsetHeight < MIN_HEIGHT || closeToTop || closeToBottom) {
651
+ setMiddlewareType("fallback");
652
+ }
653
+ }
654
+ }, [
655
+ open,
656
+ increaseHeight,
657
+ selectedIndex,
658
+ middlewareType,
659
+ refs.floating,
660
+ refs.reference,
661
+ middlewareData
662
+ ]);
663
+ React.useLayoutEffect(() => {
664
+ console.log("focusin", selectedIndex, listItemsRef.current[selectedIndex], listItemsRef.current);
665
+ if (open && selectedIndex != null) {
666
+ requestAnimationFrame(() => {
667
+ var _a2;
668
+ (_a2 = listItemsRef.current[selectedIndex]) == null ? void 0 : _a2.focus({ preventScroll: true });
669
+ });
670
+ }
671
+ }, [listItemsRef, selectedIndex, open]);
672
+ React.useEffect(() => {
673
+ const frame = requestAnimationFrame(() => {
674
+ setShowArrows(open);
675
+ if (!open) {
676
+ setScrollTop(0);
677
+ setMiddlewareType("align");
678
+ setActiveIndex(null);
679
+ setControlledScrolling(false);
680
+ }
681
+ });
682
+ return () => cancelAnimationFrame(frame);
683
+ }, [open]);
684
+ const [valueNode, setValueNode] = React.useState(null);
685
+ const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false);
686
+ return <SelectProvider scope={__scopeSelect} increaseHeight={increaseHeight} forceUpdate={forceUpdate} floatingRef={floatingRef} valueNode={valueNode} onValueNodeChange={setValueNode} valueNodeHasChildren={valueNodeHasChildren} onValueNodeHasChildrenChange={setValueNodeHasChildren} interactions={{
687
+ ...interactions,
688
+ getReferenceProps() {
689
+ return interactions.getReferenceProps({
690
+ ref: reference,
691
+ className: "SelectTrigger",
692
+ onKeyDown(event) {
693
+ if (event.key === "Enter" || event.key === " " && !context.dataRef.current.typing) {
694
+ event.preventDefault();
695
+ setOpen(true);
696
+ }
697
+ }
698
+ });
699
+ },
700
+ getFloatingProps() {
701
+ return interactions.getFloatingProps({
702
+ ref: floating,
703
+ className: "Select",
704
+ style: {
705
+ position: strategy,
706
+ top: y != null ? y : "",
707
+ left: x != null ? x : ""
708
+ },
709
+ onPointerEnter() {
710
+ setControlledScrolling(false);
711
+ },
712
+ onPointerMove() {
713
+ setControlledScrolling(false);
714
+ },
715
+ onKeyDown() {
716
+ setControlledScrolling(true);
717
+ },
718
+ onScroll(event) {
719
+ setScrollTop(event.currentTarget.scrollTop);
720
+ }
721
+ });
722
+ }
723
+ }} floatingContext={context} activeIndex={activeIndex} canScrollDown={!!showDownArrow} canScrollUp={!!showUpArrow} controlledScrolling dataRef={context.dataRef} listRef={listItemsRef} onChange={setValue} selectedIndex={0} setActiveIndex={setActiveIndex} setOpen={setOpen} setSelectedIndex={setSelectedIndex} value={value} open={open}>{children}</SelectProvider>;
724
+ }, {
725
+ Content: SelectContent,
726
+ Group: SelectGroup,
727
+ Icon: SelectIcon,
728
+ Item: SelectItem,
729
+ ItemIndicator: SelectItemIndicator,
730
+ ItemText: SelectItemText,
731
+ Label: SelectLabel,
732
+ ScrollDownButton: SelectScrollDownButton,
733
+ ScrollUpButton: SelectScrollUpButton,
734
+ Trigger: SelectTrigger,
735
+ Value: SelectValue,
736
+ Viewport: SelectViewport
285
737
  });
738
+ Select.displayName = SELECT_NAME;
286
739
  export {
287
740
  Select,
288
- SelectContent,
289
- SelectGroup,
290
741
  SelectIcon,
291
- SelectItem,
292
- SelectItemIndicator,
293
- SelectItemText,
294
- SelectLabel,
295
- SelectScrollDownButton,
296
- SelectScrollUpButton,
297
742
  SelectSeparator,
298
743
  SelectTrigger,
299
- SelectValue,
744
+ SelectViewportFrame,
300
745
  createSelectScope
301
746
  };