vaul
vaul copied to clipboard
Mobile Input - Causes whitespace + keyboard is blocking view
Hi All - this is a super cool component, thanks for working on it.
I am having some trouble with allowing for users to submit inputs in the mobile experience.
In particular, the two issues are:
- The keyboard obstructs the view of the input field
- The dialog seems to pop up a huge white rectangle over the drawer when in the mobile experience.
Has anyone found a way to fix this?
I'm using version 0.8.9
Thanks!
Due to the following part of the code there is a new height attribute set:
React.useEffect(() => {
function onVisualViewportChange() {
if (!drawerRef.current) return;
const focusedElement = document.activeElement as HTMLElement;
if (isInput(focusedElement) || keyboardIsOpen.current) {
const visualViewportHeight = window.visualViewport?.height || 0;
// This is the height of the keyboard
let diffFromInitial = window.innerHeight - visualViewportHeight;
const drawerHeight = drawerRef.current.getBoundingClientRect().height || 0;
if (!initialDrawerHeight.current) {
initialDrawerHeight.current = drawerHeight;
}
const offsetFromTop = drawerRef.current.getBoundingClientRect().top;
// visualViewport height may change due to some subtle changes to the keyboard. Checking if the height changed by 60 or more will make sure that they keyboard really changed its open state.
if (Math.abs(previousDiffFromInitial.current - diffFromInitial) > 60) {
keyboardIsOpen.current = !keyboardIsOpen.current;
}
if (snapPoints && snapPoints.length > 0 && snapPointsOffset && activeSnapPointIndex) {
const activeSnapPointHeight = snapPointsOffset[activeSnapPointIndex] || 0;
diffFromInitial += activeSnapPointHeight;
}
previousDiffFromInitial.current = diffFromInitial;
// We don't have to change the height if the input is in view, when we are here we are in the opened keyboard state so we can correctly check if the input is in view
if (drawerHeight > visualViewportHeight || keyboardIsOpen.current) {
const height = drawerRef.current.getBoundingClientRect().height;
let newDrawerHeight = height;
if (height > visualViewportHeight) {
newDrawerHeight = visualViewportHeight - WINDOW_TOP_OFFSET;
}
// When fixed, don't move the drawer upwards if there's space, but rather only change it's height so it's fully scrollable when the keyboard is open
if (fixed) {
drawerRef.current.style.height = `${height - Math.max(diffFromInitial, 0)}px`;
} else {
drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
}
} else {
drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
}
if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) {
drawerRef.current.style.bottom = `0px`;
} else {
// Negative bottom value would never make sense
drawerRef.current.style.bottom = `${Math.max(diffFromInitial, 0)}px`;
}
}
}
window.visualViewport?.addEventListener('resize', onVisualViewportChange);
return () => window.visualViewport?.removeEventListener('resize', onVisualViewportChange);
}, [activeSnapPointIndex, snapPoints, snapPointsOffset]);
I'm not familiar enough with the project to dare to make adjustments. So if someone else would like to look in to it would be lovely.
But for now I made this temporary solution that worked in my case.
So basically you're limited to the height of the remaining space while the keyboard is open. So what I did is I made an area a scrollable area if the keyboard is open.
The following part checks for the current view height
const [viewportHeight, setViewportHeight] = useState(window.visualViewport?.height);
useEffect(() => {
function updateViewportHeight() {
setViewportHeight(window.visualViewport?.height || 0);
}
window.visualViewport?.addEventListener('resize', updateViewportHeight);
return () => window.visualViewport?.removeEventListener('resize', updateViewportHeight);
}, []);
Here I adjust the styling according to when the inner height is bigger then the viewportHeight meaning the keyboard is open.
<ScrollArea style={{ height: `${ window.innerHeight > (viewportHeight || 0) ? 20 : 100 }%` }}>
Hope this helps. If not feel free to ask for more help.
Can you provide a demo with reproduction?
@emilkowalski I ran into this issue myself while using this component through Shadcn.
Made a reproduction here - https://github.com/AdiRishi/vaul-shadcn-drawer-issue Hope this helps. Really keen to see this fixed 🙏
Others have also made a reproduction in the linked Shadcn issue. See https://github.com/WhyAsh5114/shadcn-drawer-mobile by @WhyAsh5114
Yup also experiencing the same thing
I have encountered the same problem when using Chrome on android - but it seems to work fine when using Firefox on android!
Same problem on Safari
Same problem on safari!
same problem on safari
I was able to get it working on my end.
This example helped a lot https://codesandbox.io/p/devbox/drawer-with-scale-forked-73f8jw?file=%2Fapp%2Fmy-drawer.tsx
I had to make sure the the Drawer.Content was exactly like in the example
<Drawer.Content className="bg-white flex flex-col fixed bottom-0 left-0 right-0 max-h-[96%] rounded-t-[10px]">
<div className="max-w-md w-full mx-auto flex flex-col overflow-auto p-4 rounded-t-[10px]">
...
If you are experiencing this issue, please ensure that you have this html structure. Padding and overflow should be applied to the div right below Drawer.Content
. This is the optimal structure. If you ever notice that something else is not working as expected, please ensure that your structure matches the examples.
It's difficult to ensure that Vaul would work with any structure. The keyboard problem on mobile devices is hard to solve due to many differences between devices.
Same problem on safari!
Same problem on Chrome iOS
[TEMP Solution]:
I use TOP Sheet instead and works.
https://ui.shadcn.com/docs/components/sheet
[TEMP Solution]:
I use TOP Sheet instead and works.
https://ui.shadcn.com/docs/components/sheet
God bless you 🥰🥰😘😘
Closed my other issue as it was a duplicate of this, but to add to this, it's breaking in my experience specifically in chrome and with the default vaul example linked here using nextjs 14.1.3
https://github.com/emilkowalski/vaul/assets/37072462/a91f8103-6bec-4510-9875-8561c800e882
Made a video explaining it if it'll help anybody https://www.youtube.com/watch?v=QTtxeIJmN9o&ab_channel=HosnaQasmei
@hqasmei you have shown a different issue (which I also noticed and had to address in my app) impacting vaul on Safari on iOS. I forget the references I used there but I imagine you'll find it documented somewhere on GitHub and I'm sure your video would be appreciated there.
The issue here (with whitespace when the keyboard is displayed) I believe is isolated to Android.
please add ability to disable this logic with props
React.useEffect(() => {
function onVisualViewportChange() {
if (!drawerRef.current) return;
i need to use !important now to override it. The snippet works bad in IOS and in IOS chrome doesn't work at all. No idea how to fix it, so at least would be good to disable it because it makes visual bugs.
It happens to me on Android too but It would be great to be able to disable via prop or add some sort of prop to execute a callback after the resize.
@AlejandroGutierrezB not sure if it help you, but for me worked prop disablePreventScroll
for DrawerPrimitive.Root
and it resolved the issue with content jump and content overflow when keyboard is opened.
@flackeryyy that doesn't seem to do it for me - PreventScroll
is disabled if modal={false}
(see https://github.com/emilkowalski/vaul/blob/40d3a69825df25e04ffe1e52c99c3461d785f31a/src/index.tsx#L133
Mind you, I'd say the main readme is confused about this prop - default appears to be disablePreventScroll = false
https://github.com/emilkowalski/vaul/blob/40d3a69825df25e04ffe1e52c99c3461d785f31a/src/index.tsx#L84C3-L84C21
Try this if you are looking for a quick fix! In our NextJS project, I was able to fix this by adding below snippet to the global.css
.
[vaul-drawer][vaul-drawer-direction=bottom]:after { z-index: -1; }
I solved this problem of the keyboard by doing three things:
- Adding a fixed height for the drawer's content;
- Adding scrollArea as a chield of the content;
- Adding a div as a chield of the scrollArea with flex grow;
I have been at this for quite a while.
I've found that newer versions of iOS and Android are pretty good at handling the virtual keyboard; and, in some cases, you actually do more harm than good by trying to control it programmatically.
If you are using Tailwind CSS, you can use the !
syntax to mark a utility class as !important
. I found that making the bottom
and height
important solves most issues (if not all). By doing so, I just let iOS and Android decide where to put the input.
Below are my classes used for anybody that might find it useful:
fixed inset-x-0 !bottom-0 z-50 flex !h-[95dvh] flex-col rounded-t-xl bg-white
@emilkowalski I think an option to disable the VisualViewport EventListener completely would be beneficial in the long run.
https://github.com/emilkowalski/vaul/blob/8bf9c8f1d01d44bb3c0325a70dc374c1bf04e620/src/index.tsx#L357-L412
Should be fixed in #441, I'll release a new version later today. I've also adjusted the repositionInputs
prop to disable the visualViewport
logic as well when this prop is set to false
.
Should be fixed in #441, I'll release a new version later today. I've also adjusted the
repositionInputs
prop to disable thevisualViewport
logic as well when this prop is set tofalse
.
I currently have version v0.9.6 installed, but in fact, when focusing on an input inside the drawer in iOS Safari, the drawer's content is still scrolled to an unexpected height(dialog bottom is not 0, but about 268px). If I try to scroll, the drawer may automatically close due to the scroll event. When drawer is closed, found that window is scrolled an offset from position of original.
Should be fixed in #441, I'll release a new version later today. I've also adjusted the
repositionInputs
prop to disable thevisualViewport
logic as well when this prop is set tofalse
.I currently have version v0.9.6 installed, but in fact, when focusing on an input inside the drawer in iOS Safari, the drawer's content is still scrolled to an unexpected height(dialog bottom is not 0, but about 268px). If I try to scroll, the drawer may automatically close due to the scroll event. When drawer is closed, found that window is scrolled an offset from position of original.
Can confirm this still happens in 1.0.0 when the text input is just under the area where the keyboard is going to be
the same for me. I have 1.0.0 version and still facing the same problem, that text input is under the keyboard
Still happens in version 1.1.0 mobile chrome iOS
Should be fixed in #441, I'll release a new version later today. I've also adjusted the
repositionInputs
prop to disable thevisualViewport
logic as well when this prop is set tofalse
.I currently have version v0.9.6 installed, but in fact, when focusing on an input inside the drawer in iOS Safari, the drawer's content is still scrolled to an unexpected height(dialog bottom is not 0, but about 268px). If I try to scroll, the drawer may automatically close due to the scroll event. When drawer is closed, found that window is scrolled an offset from position of original.
Can confirm this still happens in 1.0.0 when the text input is just under the area where the keyboard is going to be
I find that works with adding a min-height for DrawerContent. Just like this:
<DrawerContent
className="h-[90vh] min-h-[90vh] bg-[#F2F2F2] p-3"
>