@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
|
|
91
|
-
//
|
|
92
|
-
// the
|
|
93
|
-
//
|
|
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
|
-
//
|
|
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
|
|
35
|
-
//
|
|
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
|
-
|
|
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.
|
|
102
|
+
// `translate3d`, keeping the drag off the layout path.
|
|
102
103
|
if (dragging)
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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);
|