@expofp/ui 3.45.1 → 3.45.2

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.
@@ -5,6 +5,7 @@ import { lazy, Suspense, useContext, useEffect, useId, useMemo, useRef, useState
5
5
  import { createPortal } from 'react-dom';
6
6
  import { ResizeObserver } from 'resize-observer';
7
7
  import useOnClickOutside from '../../shared/hooks/useOnClickOutside';
8
+ import { OverlayKeyboardInsetContext } from '../Overlay/OverlayKeyboardInsetContext';
8
9
  import { OverlayScrollContext } from '../Overlay/OverlayScrollContext';
9
10
  const DROPDOWN_GAP = 4;
10
11
  /** Below this the list is useless, so it is allowed to overflow the visible area. */
@@ -15,6 +16,7 @@ const Autocomplete = ({ placeholder, options, value, showClear = false, onChange
15
16
  const container = renderTarget;
16
17
  // The scrollable body of the sheet the inline list must stay inside of.
17
18
  const scrollContainer = useContext(OverlayScrollContext);
19
+ const keyboardInset = useContext(OverlayKeyboardInsetContext);
18
20
  const refAutocomplete = useRef(null);
19
21
  const refSearchInput = useRef(null);
20
22
  const refOptions = useRef(null);
@@ -87,10 +89,11 @@ const Autocomplete = ({ placeholder, options, value, showClear = false, onChange
87
89
  viewport?.removeEventListener('scroll', updateDropdownPosition);
88
90
  };
89
91
  }, [showOptionsDropdown, inline]);
90
- // The inline list ends where the sheet's scroll body does. Left to overflow,
91
- // it would extend that body and the tail could only be reached by scrolling
92
- // the sheet, then the list. Both rects come from the same layout, so the
93
- // keyboard panning the viewport does not skew the difference.
92
+ // The inline list ends where the sheet's scroll body does, or at the keyboard
93
+ // when that body runs on under one. Left to overflow, it would extend that
94
+ // body and the tail could only be reached by scrolling the sheet, then the
95
+ // list. Both rects come from the same layout, so the keyboard panning the
96
+ // viewport does not skew the difference.
94
97
  useEffect(() => {
95
98
  if (!inline || !showOptionsDropdown || !scrollContainer) {
96
99
  setInlineMaxHeight(null);
@@ -101,6 +104,7 @@ const Autocomplete = ({ placeholder, options, value, showClear = false, onChange
101
104
  if (!anchor)
102
105
  return;
103
106
  const room = scrollContainer.getBoundingClientRect().bottom -
107
+ keyboardInset -
104
108
  anchor.getBoundingClientRect().bottom -
105
109
  DROPDOWN_GAP;
106
110
  setInlineMaxHeight(Math.max(room, DROPDOWN_MIN_HEIGHT));
@@ -110,7 +114,7 @@ const Autocomplete = ({ placeholder, options, value, showClear = false, onChange
110
114
  const observer = new ResizeObserver(measure);
111
115
  observer.observe(scrollContainer);
112
116
  return () => observer.disconnect();
113
- }, [inline, showOptionsDropdown, scrollContainer]);
117
+ }, [inline, showOptionsDropdown, scrollContainer, keyboardInset]);
114
118
  useEffect(() => {
115
119
  if (focusOptionIndex === null)
116
120
  return;
@@ -7,6 +7,7 @@ import { useKeyboardInset } from '../../shared/hooks/useKeyboardInset';
7
7
  import { useSheetDrag } from '../../shared/hooks/useSheetDrag';
8
8
  import { useWindowSize } from '../../shared/hooks/useWindowSize';
9
9
  import { OverlayHeaderContext } from './OverlayHeaderContext';
10
+ import { OverlayKeyboardInsetContext } from './OverlayKeyboardInsetContext';
10
11
  import { OverlayScrollContext } from './OverlayScrollContext';
11
12
  const TRANSITION_DURATION = '0.3s';
12
13
  const DEFAULT_SCROLL_THRESHOLD = 10;
@@ -29,10 +30,10 @@ const Overlay = ({ children, open, size = 'medium', particles, className, onChan
29
30
  const mobile = windowWidth <= mobileBreakpoint;
30
31
  const sizes = useMemo(() => generateSizes(windowHeight, smallSizeMultiplier, mediumSizeMultiplier, fullSizeOffset), [windowHeight, smallSizeMultiplier, mediumSizeMultiplier, fullSizeOffset]);
31
32
  // The on-screen keyboard covers the sheet without resizing the window (#1567):
32
- // lift the sheet above it and give up the height the keyboard took.
33
+ // give up the height the keyboard took, and let the content scroll clear of it.
33
34
  const keyboardInset = useKeyboardInset(mobile);
34
- // What the sheet can actually occupy right now. Snap points and rendered
35
- // heights must come from the same numbers, or a drag lands where the sheet is not.
35
+ // What the sheet can show right now. Snap points and visible heights must
36
+ // come from the same numbers, or a drag lands where the sheet is not.
36
37
  const effectiveSizes = useMemo(() => {
37
38
  if (keyboardInset <= 0)
38
39
  return sizes;
@@ -87,24 +88,24 @@ const Overlay = ({ children, open, size = 'medium', particles, className, onChan
87
88
  }, []);
88
89
  const positioningStyles = useMemo(() => {
89
90
  if (mobile) {
90
- const lift = keyboardInset > 0 ? { bottom: keyboardInset } : {};
91
+ // The sheet stays on the bottom edge and runs on under the keyboard. A
92
+ // sheet lifted whole leaves the page showing below it, through the
93
+ // translucent bars iOS 26 floats there (#1712).
94
+ const sheet = (visible) => ({ height: visible + keyboardInset });
91
95
  if (forceFull && open) {
92
96
  // Covers the screen: `fullSizeOffset` keeps room for the search box above a
93
97
  // regular sheet, and a forced-full one is drawn over it (#1713). Only the
94
- // keyboard takes height away, never below the smallest snap point.
95
- return {
96
- ...lift,
97
- height: Math.max(getActualViewportHeight() - keyboardInset, sizes.small),
98
- };
98
+ // keyboard takes visible height away, never below the smallest snap point.
99
+ return sheet(Math.max(getActualViewportHeight() - keyboardInset, sizes.small));
99
100
  }
100
101
  // A gesture freezes the sheet at its tallest point and moves it with
101
- // `translate3d`, keeping the drag off the layout path. The lift stays on.
102
+ // `translate3d`, keeping the drag off the layout path.
102
103
  if (dragging)
103
- return { ...lift, height: effectiveSizes.full };
104
- return {
105
- ...lift,
106
- height: (open || isAnimating) && !collapsed ? effectiveSizes[currentSnap] : 0,
107
- };
104
+ return sheet(effectiveSizes.full);
105
+ // Closed, it takes no room at all, the part under the keyboard included.
106
+ if (collapsed || !(open || isAnimating))
107
+ return { height: 0 };
108
+ return sheet(effectiveSizes[currentSnap]);
108
109
  }
109
110
  return {
110
111
  height: open ? 'auto' : undefined,
@@ -121,6 +122,12 @@ const Overlay = ({ children, open, size = 'medium', particles, className, onChan
121
122
  effectiveSizes,
122
123
  keyboardInset,
123
124
  ]);
125
+ // The content runs on under the keyboard with the sheet (#1712). The padding
126
+ // is the covered part: with it the end of the content scrolls clear of the
127
+ // keyboard, and what the browser scrolls into view lands above it.
128
+ const scrollStyles = useMemo(() => mobile && keyboardInset > 0
129
+ ? { paddingBottom: keyboardInset, scrollPaddingBottom: keyboardInset }
130
+ : undefined, [mobile, keyboardInset]);
124
131
  const snapPoints = useMemo(() => [
125
132
  { name: 'small', height: effectiveSizes.small },
126
133
  { name: 'medium', height: effectiveSizes.medium },
@@ -235,9 +242,9 @@ const Overlay = ({ children, open, size = 'medium', particles, className, onChan
235
242
  'efp-overlay--scrolled': isScrolled,
236
243
  'efp-overlay--forced-full': forceFull && mobile,
237
244
  'efp-overlay--button-inset': mobile && withFloatingButton,
238
- }, className), style: positioningStyles, ref: overlayPanelRef, onPointerDown: onPointerDown, children: [!disableDrag && _jsx("div", { className: "efp-overlay__draghandle", "data-sheet-handle": true }), _jsx("div", { className: "efp-overlay__header", ref: setHeaderElement }), _jsxs("div", { className: "efp-overlay__scroll", ref: attachScroll, onScroll: () => {
245
+ }, className), style: positioningStyles, ref: overlayPanelRef, onPointerDown: onPointerDown, children: [!disableDrag && _jsx("div", { className: "efp-overlay__draghandle", "data-sheet-handle": true }), _jsx("div", { className: "efp-overlay__header", ref: setHeaderElement }), _jsxs("div", { className: "efp-overlay__scroll", style: scrollStyles, ref: attachScroll, onScroll: () => {
239
246
  handleScroll();
240
247
  debouncedScrollActivity();
241
- }, children: [particles, _jsx("div", { className: "efp-overlay__content", children: _jsx(OverlayHeaderContext, { value: headerElement, children: children ? children : noContentLabel }) })] })] }) }));
248
+ }, children: [particles, _jsx("div", { className: "efp-overlay__content", children: _jsx(OverlayHeaderContext, { value: headerElement, children: _jsx(OverlayKeyboardInsetContext, { value: mobile ? keyboardInset : 0, children: children ? children : noContentLabel }) }) })] })] }) }));
242
249
  };
243
250
  export default Overlay;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Height in px of the on-screen keyboard over the bottom of the overlay's scroll container, which
3
+ * runs on under it: whatever has to stay reachable must end that much above the container's
4
+ * bottom edge. Provided by {@link Overlay}; `0` with no keyboard, and outside an overlay.
5
+ */
6
+ export declare const OverlayKeyboardInsetContext: import("react").Context<number>;
7
+ //# sourceMappingURL=OverlayKeyboardInsetContext.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react';
2
+ /**
3
+ * Height in px of the on-screen keyboard over the bottom of the overlay's scroll container, which
4
+ * runs on under it: whatever has to stay reachable must end that much above the container's
5
+ * bottom edge. Provided by {@link Overlay}; `0` with no keyboard, and outside an overlay.
6
+ */
7
+ export const OverlayKeyboardInsetContext = createContext(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/ui",
3
- "version": "3.45.1",
3
+ "version": "3.45.2",
4
4
  "type": "module",
5
5
  "description": "ExpoFP SDK internal: shared pure React UI components",
6
6
  "homepage": "https://developer.expofp.com/",