@tamagui/select 1.0.1-beta.103 → 1.0.1-beta.106

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.
@@ -17,9 +17,11 @@ import {
17
17
  } from "@floating-ui/react-dom-interactions";
18
18
  import { usePrevious } from "@radix-ui/react-use-previous";
19
19
  import { useComposedRefs } from "@tamagui/compose-refs";
20
+ import { isWeb, useMedia } from "@tamagui/core";
20
21
  import {
21
22
  Theme,
22
23
  styled,
24
+ useGet,
23
25
  useIsomorphicLayoutEffect,
24
26
  useThemeName,
25
27
  withStaticProperties
@@ -27,12 +29,20 @@ import {
27
29
  import { useId } from "@tamagui/core";
28
30
  import { createContextScope } from "@tamagui/create-context";
29
31
  import { ListItem } from "@tamagui/list-item";
32
+ import { PortalHost, PortalItem } from "@tamagui/portal";
30
33
  import { Separator } from "@tamagui/separator";
34
+ import { Sheet, SheetController } from "@tamagui/sheet";
31
35
  import { ThemeableStack, XStack, YStack } from "@tamagui/stacks";
32
36
  import { Paragraph } from "@tamagui/text";
33
37
  import { useControllableState } from "@tamagui/use-controllable-state";
34
38
  import * as React from "react";
35
39
  import * as ReactDOM from "react-dom";
40
+ const SELECT_NAME = "Select";
41
+ const WINDOW_PADDING = 8;
42
+ const SCROLL_ARROW_VELOCITY = 8;
43
+ const SCROLL_ARROW_THRESHOLD = 8;
44
+ const MIN_HEIGHT = 80;
45
+ const FALLBACK_THRESHOLD = 16;
36
46
  const userAgent = typeof navigator !== "undefined" && navigator.userAgent || "";
37
47
  const isFirefox = userAgent.toLowerCase().includes("firefox");
38
48
  if (isFirefox) {
@@ -42,12 +52,6 @@ function getVisualOffsetTop() {
42
52
  var _a;
43
53
  return !/^((?!chrome|android).)*safari/i.test(userAgent) ? (_a = visualViewport == null ? void 0 : visualViewport.offsetTop) != null ? _a : 0 : 0;
44
54
  }
45
- const SELECT_NAME = "Select";
46
- const WINDOW_PADDING = 8;
47
- const SCROLL_ARROW_VELOCITY = 8;
48
- const SCROLL_ARROW_THRESHOLD = 8;
49
- const MIN_HEIGHT = 80;
50
- const FALLBACK_THRESHOLD = 16;
51
55
  const [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME);
52
56
  const [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME);
53
57
  const TRIGGER_NAME = "SelectTrigger";
@@ -59,7 +63,12 @@ const SelectTrigger = React.forwardRef((props, forwardedRef) => {
59
63
  ...triggerProps
60
64
  } = props;
61
65
  const context = useSelectContext(TRIGGER_NAME, __scopeSelect);
62
- return <ListItem backgrounded radiused hoverTheme pressTheme focusTheme focusable={false} borderWidth={1} componentName={TRIGGER_NAME} size={context.size} aria-expanded={context.open} aria-autocomplete="none" disabled={disabled} data-disabled={disabled ? "" : void 0} {...triggerProps} ref={forwardedRef} {...context.interactions.getReferenceProps()} />;
66
+ const labelledBy = ariaLabelledby;
67
+ return <ListItem backgrounded radiused hoverTheme pressTheme focusTheme focusable={false} borderWidth={1} componentName={TRIGGER_NAME} size={context.size} aria-expanded={context.open} aria-autocomplete="none" aria-labelledby={labelledBy} dir={context.dir} disabled={disabled} data-disabled={disabled ? "" : void 0} {...triggerProps} ref={forwardedRef} {...context.interactions ? context.interactions.getReferenceProps() : {
68
+ onPress() {
69
+ context.setOpen(!context.open);
70
+ }
71
+ }} />;
63
72
  });
64
73
  SelectTrigger.displayName = TRIGGER_NAME;
65
74
  const VALUE_NAME = "SelectValue";
@@ -87,7 +96,11 @@ const CONTENT_NAME = "SelectContent";
87
96
  const SelectContent = ({ children, __scopeSelect }) => {
88
97
  const context = useSelectContext(CONTENT_NAME, __scopeSelect);
89
98
  const themeName = useThemeName();
99
+ const showSheet = useShowSelectSheet(context);
90
100
  const contents = <Theme name={themeName}>{children}</Theme>;
101
+ if (showSheet && context.open) {
102
+ return contents;
103
+ }
91
104
  return <FloatingPortal>{context.open ? <FloatingOverlay lockScroll>{contents}</FloatingOverlay> : <div style={{ display: "none" }}>{contents}</div>}</FloatingPortal>;
92
105
  };
93
106
  const VIEWPORT_NAME = "SelectViewport";
@@ -113,9 +126,17 @@ const SelectViewportFrame = styled(ThemeableStack, {
113
126
  }
114
127
  });
115
128
  const SelectViewport = React.forwardRef((props, forwardedRef) => {
129
+ var _a, _b;
116
130
  const { __scopeSelect, children, ...viewportProps } = props;
117
131
  const context = useSelectContext(VIEWPORT_NAME, __scopeSelect);
118
- const { style, ...floatingProps } = context.interactions.getFloatingProps();
132
+ const { style, ...floatingProps } = (_b = (_a = context.interactions) == null ? void 0 : _a.getFloatingProps()) != null ? _b : {};
133
+ const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint);
134
+ if (breakpointActive || !isWeb) {
135
+ return <PortalItem hostName={`${context.scopeKey}SheetContents`}>{children}</PortalItem>;
136
+ }
137
+ if (!context.floatingContext) {
138
+ return null;
139
+ }
119
140
  delete style["scrollbarWidth"];
120
141
  delete style["listStyleType"];
121
142
  return <>
@@ -156,12 +177,14 @@ const SelectItem = React.forwardRef((props, forwardedRef) => {
156
177
  } = context;
157
178
  const composedRefs = useComposedRefs(forwardedRef, (node) => {
158
179
  if (node instanceof HTMLElement) {
159
- listRef.current[index] = node;
180
+ if (listRef) {
181
+ listRef.current[index] = node;
182
+ }
160
183
  }
161
184
  });
162
185
  React.useEffect(() => {
163
186
  setValueAtIndex(index, value);
164
- }, [index]);
187
+ }, [index, setValueAtIndex, value]);
165
188
  const timeoutRef = React.useRef();
166
189
  const [allowMouseUp, setAllowMouseUp] = React.useState(false);
167
190
  function handleSelect() {
@@ -184,16 +207,18 @@ const SelectItem = React.forwardRef((props, forwardedRef) => {
184
207
  }
185
208
  }, [open, index, setActiveIndex, selectedIndex]);
186
209
  function handleKeyDown(event) {
187
- if (event.key === "Enter" || event.key === " " && !dataRef.current.typing) {
210
+ if (event.key === "Enter" || event.key === " " && !(dataRef == null ? void 0 : dataRef.current.typing)) {
188
211
  event.preventDefault();
189
212
  handleSelect();
190
213
  }
191
214
  }
192
- const selectItemProps = context.interactions.getItemProps({
215
+ const selectItemProps = context.interactions ? context.interactions.getItemProps({
193
216
  onClick: allowMouseUp ? handleSelect : void 0,
194
217
  onMouseUp: allowMouseUp ? handleSelect : void 0,
195
218
  onKeyDown: handleKeyDown
196
- });
219
+ }) : {
220
+ onPress: handleSelect
221
+ };
197
222
  return <SelectItemContextProvider scope={__scopeSelect} value={value} textId={textId || ""} isSelected={isSelected} onItemTextChange={React.useCallback((node) => {
198
223
  setTextValue((prevTextValue) => {
199
224
  var _a;
@@ -208,13 +233,13 @@ const SelectItemTextFrame = styled(Paragraph, {
208
233
  selectable: false
209
234
  });
210
235
  const SelectItemText = React.forwardRef((props, forwardedRef) => {
211
- const { __scopeSelect, className, style, ...itemTextProps } = props;
236
+ const { __scopeSelect, className, ...itemTextProps } = props;
212
237
  const context = useSelectContext(ITEM_TEXT_NAME, __scopeSelect);
213
238
  const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect);
214
239
  const ref = React.useRef(null);
215
240
  const composedRefs = useComposedRefs(forwardedRef, ref, itemContext.onItemTextChange);
216
241
  return <>
217
- <SelectItemTextFrame size={context.size} id={itemContext.textId} {...itemTextProps} ref={composedRefs} />
242
+ <SelectItemTextFrame className={className} size={context.size} id={itemContext.textId} {...itemTextProps} ref={composedRefs} />
218
243
  {itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren ? ReactDOM.createPortal(itemTextProps.children, context.valueNode) : null}
219
244
  </>;
220
245
  });
@@ -259,7 +284,7 @@ const SelectScrollDownButton = React.forwardRef((props, forwardedRef) => {
259
284
  return <SelectScrollButtonImpl componentName={SCROLL_DOWN_BUTTON_NAME} {...props} dir="down" ref={forwardedRef} />;
260
285
  });
261
286
  SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME;
262
- const SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => {
287
+ const SelectScrollButtonImpl = React.memo(React.forwardRef((props, forwardedRef) => {
263
288
  var _a, _b;
264
289
  const { __scopeSelect, dir, componentName, ...scrollIndicatorProps } = props;
265
290
  const { floatingRef, increaseHeight, forceUpdate, ...context } = useSelectContext(componentName, __scopeSelect);
@@ -273,8 +298,10 @@ const SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => {
273
298
  });
274
299
  const composedRefs = useComposedRefs(forwardedRef, floating);
275
300
  React.useLayoutEffect(() => {
301
+ if (!floatingRef)
302
+ return;
276
303
  reference(floatingRef.current);
277
- }, [reference, floatingRef.current]);
304
+ }, [reference, floatingRef == null ? void 0 : floatingRef.current]);
278
305
  React.useEffect(() => {
279
306
  if (!refs.reference.current || !refs.floating.current || !isVisible) {
280
307
  return;
@@ -292,17 +319,19 @@ const SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => {
292
319
  return null;
293
320
  }
294
321
  const handleScrollArrowChange = () => {
322
+ if (!floatingRef)
323
+ return;
295
324
  const floating2 = floatingRef.current;
296
325
  const isUp = dir === "up";
297
326
  if (floating2) {
298
327
  const value = isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY;
299
328
  const multi = isUp && floating2.scrollTop <= SCROLL_ARROW_THRESHOLD * 2 || !isUp && floating2.scrollTop >= floating2.scrollHeight - floating2.clientHeight - SCROLL_ARROW_THRESHOLD * 2 ? 2 : 1;
300
329
  floating2.scrollTop += multi * (isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY);
301
- increaseHeight(floating2, multi === 2 ? value * 2 : value);
330
+ increaseHeight == null ? void 0 : increaseHeight(floating2, multi === 2 ? value * 2 : value);
302
331
  forceUpdate();
303
332
  }
304
333
  };
305
- return <YStack ref={composedRefs} componentName={componentName} aria-hidden {...scrollIndicatorProps} zIndex={1e3} position={strategy} left={x || 0} top={y || 0} width={`calc(${((_b = (_a = floatingRef.current) == null ? void 0 : _a.offsetWidth) != null ? _b : 0) - 2}px)`} onPointerMove={() => {
334
+ return <YStack ref={composedRefs} componentName={componentName} aria-hidden {...scrollIndicatorProps} zIndex={1e3} position={strategy} left={x || 0} top={y || 0} width={`calc(${((_b = (_a = floatingRef == null ? void 0 : floatingRef.current) == null ? void 0 : _a.offsetWidth) != null ? _b : 0) - 2}px)`} onPointerMove={() => {
306
335
  if (!loopingRef.current) {
307
336
  intervalRef.current = setInterval(handleScrollArrowChange, 1e3 / 60);
308
337
  loopingRef.current = true;
@@ -311,51 +340,52 @@ const SelectScrollButtonImpl = React.forwardRef((props, forwardedRef) => {
311
340
  loopingRef.current = false;
312
341
  clearInterval(intervalRef.current);
313
342
  }} />;
314
- });
343
+ }));
315
344
  const SelectSeparator = styled(Separator, {
316
345
  name: "SelectSeparator"
317
346
  });
318
- const Select = withStaticProperties((props) => {
347
+ const useSelectBreakpointActive = (sheetBreakpoint) => {
348
+ const media = useMedia();
349
+ return sheetBreakpoint ? media[sheetBreakpoint] : false;
350
+ };
351
+ const useShowSelectSheet = (context) => {
352
+ const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint);
353
+ return context.open === false ? false : breakpointActive;
354
+ };
355
+ const SelectSheetController = (props) => {
356
+ const context = useSelectContext("SelectSheetController", props.__scopeSelect);
357
+ const showSheet = useShowSelectSheet(context);
358
+ const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint);
359
+ const getShowSheet = useGet(showSheet);
360
+ return <SheetController onChangeOpen={(val) => {
361
+ if (getShowSheet()) {
362
+ props.onChangeOpen(val);
363
+ }
364
+ }} open={context.open} hidden={breakpointActive === false}>{props.children}</SheetController>;
365
+ };
366
+ const SHEET_CONTENTS_NAME = "SelectSheetContents";
367
+ const SelectSheetContents = ({ __scopeSelect }) => {
368
+ const context = useSelectContext(SHEET_CONTENTS_NAME, __scopeSelect);
369
+ return <PortalHost name={`${context.scopeKey}SheetContents`} />;
370
+ };
371
+ SelectSheetContents.displayName = SHEET_CONTENTS_NAME;
372
+ const SelectInlineImpl = (props) => {
319
373
  const {
320
- id,
321
374
  __scopeSelect,
322
375
  children,
323
- open: openProp,
324
- defaultOpen,
325
- onOpenChange,
326
- value: valueProp,
327
- defaultValue,
328
- onValueChange,
329
- size: sizeProp,
330
- dir,
331
- name,
332
- autoComplete
376
+ open = false,
377
+ activeIndexRef,
378
+ selectedIndexRef,
379
+ listContentRef
333
380
  } = props;
334
- const [open, setOpen] = useControllableState({
335
- prop: openProp,
336
- defaultProp: defaultOpen || false,
337
- onChange: onOpenChange
338
- });
339
- const [value, setValue] = useControllableState({
340
- prop: valueProp,
341
- defaultProp: defaultValue || "",
342
- onChange: onValueChange
343
- });
344
- const [activeIndex, setActiveIndex] = React.useState(null);
345
- const selectedIndexRef = React.useRef(null);
346
- const activeIndexRef = React.useRef(null);
347
- const prevActiveIndex = usePrevious(activeIndex);
381
+ const selectContext = useSelectContext("SelectSheetImpl", __scopeSelect);
382
+ const { setActiveIndex, setOpen, setSelectedIndex, selectedIndex, activeIndex, forceUpdate } = selectContext;
348
383
  const [showArrows, setShowArrows] = React.useState(false);
349
384
  const [scrollTop, setScrollTop] = React.useState(0);
385
+ const prevActiveIndex = usePrevious(activeIndex);
350
386
  const listItemsRef = React.useRef([]);
351
- const listContentRef = React.useRef([]);
352
- const [selectedIndex, setSelectedIndex] = React.useState(Math.max(0, listContentRef.current.indexOf(value)));
353
387
  const [controlledScrolling, setControlledScrolling] = React.useState(false);
354
388
  const [middlewareType, setMiddlewareType] = React.useState("align");
355
- useIsomorphicLayoutEffect(() => {
356
- selectedIndexRef.current = selectedIndex;
357
- activeIndexRef.current = activeIndex;
358
- });
359
389
  React.useEffect(() => {
360
390
  const frame = requestAnimationFrame(() => {
361
391
  setShowArrows(open);
@@ -369,7 +399,7 @@ const Select = withStaticProperties((props) => {
369
399
  return () => {
370
400
  cancelAnimationFrame(frame);
371
401
  };
372
- }, [open]);
402
+ }, [open, setActiveIndex]);
373
403
  function getFloatingPadding(floating2) {
374
404
  var _a;
375
405
  if (!floating2) {
@@ -451,7 +481,6 @@ const Select = withStaticProperties((props) => {
451
481
  ]
452
482
  });
453
483
  const floatingRef = refs.floating;
454
- const forceUpdate = React.useReducer(() => ({}), {})[1];
455
484
  const showUpArrow = showArrows && scrollTop > SCROLL_ARROW_THRESHOLD;
456
485
  const showDownArrow = showArrows && floatingRef.current && scrollTop < floatingRef.current.scrollHeight - floatingRef.current.clientHeight - SCROLL_ARROW_THRESHOLD;
457
486
  const interactions = useInteractions([
@@ -498,7 +527,7 @@ const Select = withStaticProperties((props) => {
498
527
  }
499
528
  return currentTop - nextTop;
500
529
  }
501
- }, [middlewareType]);
530
+ }, [middlewareType, selectedIndexRef]);
502
531
  const touchPageYRef = React.useRef(null);
503
532
  const handleWheel = React.useCallback((event) => {
504
533
  var _a, _b, _c;
@@ -531,7 +560,7 @@ const Select = withStaticProperties((props) => {
531
560
  setScrollTop(currentTarget.scrollTop);
532
561
  forceUpdate();
533
562
  }
534
- }, [increaseHeight, forceUpdate]);
563
+ }, [forceUpdate, increaseHeight]);
535
564
  React.useEffect(() => {
536
565
  function onTouchEnd() {
537
566
  touchPageYRef.current = null;
@@ -637,10 +666,8 @@ const Select = withStaticProperties((props) => {
637
666
  });
638
667
  return () => cancelAnimationFrame(frame);
639
668
  }, [open]);
640
- const [valueNode, setValueNode] = React.useState(null);
641
- const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false);
642
- return <SelectProvider size={sizeProp} scope={__scopeSelect} increaseHeight={increaseHeight} forceUpdate={forceUpdate} floatingRef={floatingRef} valueNode={valueNode} onValueNodeChange={setValueNode} valueNodeHasChildren={valueNodeHasChildren} onValueNodeHasChildrenChange={setValueNodeHasChildren} setValueAtIndex={(index, value2) => {
643
- listContentRef.current[index] = value2;
669
+ return <SelectProvider scope={__scopeSelect} {...selectContext} increaseHeight={increaseHeight} floatingRef={floatingRef} setValueAtIndex={(index, value) => {
670
+ listContentRef.current[index] = value;
644
671
  }} interactions={{
645
672
  ...interactions,
646
673
  getReferenceProps() {
@@ -684,7 +711,52 @@ const Select = withStaticProperties((props) => {
684
711
  }
685
712
  });
686
713
  }
687
- }} floatingContext={context} activeIndex={activeIndex} canScrollDown={!!showDownArrow} canScrollUp={!!showUpArrow} controlledScrolling dataRef={context.dataRef} listRef={listItemsRef} onChange={setValue} selectedIndex={selectedIndex} setActiveIndex={setActiveIndex} setOpen={setOpen} setSelectedIndex={setSelectedIndex} value={value} open={open}>{children}</SelectProvider>;
714
+ }} floatingContext={context} activeIndex={activeIndex} canScrollDown={!!showDownArrow} canScrollUp={!!showUpArrow} controlledScrolling dataRef={context.dataRef} listRef={listItemsRef}>{children}</SelectProvider>;
715
+ };
716
+ const SelectSheetImpl = (props) => {
717
+ return <>{props.children}</>;
718
+ };
719
+ const Select = withStaticProperties((props) => {
720
+ const {
721
+ __scopeSelect,
722
+ children,
723
+ open: openProp,
724
+ defaultOpen,
725
+ onOpenChange,
726
+ value: valueProp,
727
+ defaultValue,
728
+ onValueChange,
729
+ size: sizeProp,
730
+ sheetBreakpoint = false,
731
+ dir
732
+ } = props;
733
+ const isSheet = useSelectBreakpointActive(sheetBreakpoint);
734
+ const SelectImpl = isSheet ? SelectSheetImpl : SelectInlineImpl;
735
+ const forceUpdate = React.useReducer(() => ({}), {})[1];
736
+ const [open, setOpen] = useControllableState({
737
+ prop: openProp,
738
+ defaultProp: defaultOpen || false,
739
+ onChange: onOpenChange
740
+ });
741
+ const [value, setValue] = useControllableState({
742
+ prop: valueProp,
743
+ defaultProp: defaultValue || "",
744
+ onChange: onValueChange
745
+ });
746
+ const [activeIndex, setActiveIndex] = React.useState(null);
747
+ const selectedIndexRef = React.useRef(null);
748
+ const activeIndexRef = React.useRef(null);
749
+ const listContentRef = React.useRef([]);
750
+ const [selectedIndex, setSelectedIndex] = React.useState(Math.max(0, listContentRef.current.indexOf(value)));
751
+ const [valueNode, setValueNode] = React.useState(null);
752
+ const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false);
753
+ useIsomorphicLayoutEffect(() => {
754
+ selectedIndexRef.current = selectedIndex;
755
+ activeIndexRef.current = activeIndex;
756
+ });
757
+ return <SelectProvider dir={dir} size={sizeProp} forceUpdate={forceUpdate} valueNode={valueNode} onValueNodeChange={setValueNode} onValueNodeHasChildrenChange={setValueNodeHasChildren} valueNodeHasChildren={valueNodeHasChildren} scopeKey={__scopeSelect ? Object.keys(__scopeSelect)[0] : ""} sheetBreakpoint={sheetBreakpoint} scope={__scopeSelect} setValueAtIndex={(index, value2) => {
758
+ listContentRef.current[index] = value2;
759
+ }} activeIndex={activeIndex} onChange={setValue} selectedIndex={selectedIndex} setActiveIndex={setActiveIndex} setOpen={setOpen} setSelectedIndex={setSelectedIndex} value={value} open={open}><SelectSheetController onChangeOpen={setOpen} __scopeSelect={__scopeSelect}><SelectImpl activeIndexRef={activeIndexRef} listContentRef={listContentRef} selectedIndexRef={selectedIndexRef} {...props} open={open} value={value}>{children}</SelectImpl></SelectSheetController></SelectProvider>;
688
760
  }, {
689
761
  Content: SelectContent,
690
762
  Group: SelectGroup,
@@ -697,7 +769,9 @@ const Select = withStaticProperties((props) => {
697
769
  ScrollUpButton: SelectScrollUpButton,
698
770
  Trigger: SelectTrigger,
699
771
  Value: SelectValue,
700
- Viewport: SelectViewport
772
+ Viewport: SelectViewport,
773
+ SheetContents: SelectSheetContents,
774
+ Sheet
701
775
  });
702
776
  Select.displayName = SELECT_NAME;
703
777
  export {
@@ -707,6 +781,7 @@ export {
707
781
  SelectItem,
708
782
  SelectItemTextFrame,
709
783
  SelectSeparator,
784
+ SelectSheetContents,
710
785
  SelectTrigger,
711
786
  SelectViewport,
712
787
  SelectViewportFrame,