vaul icon indicating copy to clipboard operation
vaul copied to clipboard

Mobile Input - Causes whitespace + keyboard is blocking view

Open mrm2234 opened this issue 11 months ago • 22 comments

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:

  1. The keyboard obstructs the view of the input field
  2. 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!

Screen Shot 2024-03-10 at 4 00 07 PM

mrm2234 avatar Mar 10 '24 22:03 mrm2234

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.

Danillooost avatar Mar 15 '24 20:03 Danillooost

Can you provide a demo with reproduction?

emilkowalski avatar Mar 16 '24 21:03 emilkowalski

@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

AdiRishi avatar Mar 17 '24 00:03 AdiRishi

Yup also experiencing the same thing

hqasmei avatar Mar 18 '24 08:03 hqasmei

I have encountered the same problem when using Chrome on android - but it seems to work fine when using Firefox on android!

pjhi avatar Mar 18 '24 12:03 pjhi

Same problem on Safari

alvvaysxxx avatar Mar 19 '24 16:03 alvvaysxxx

Same problem on safari!

williamlmao avatar Mar 28 '24 21:03 williamlmao

same problem on safari

WatanabeThiago avatar Apr 02 '24 23:04 WatanabeThiago

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]">
...

hqasmei avatar Apr 02 '24 23:04 hqasmei

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.

emilkowalski avatar Apr 06 '24 16:04 emilkowalski

Same problem on safari!

IRediTOTO avatar Apr 10 '24 03:04 IRediTOTO

Same problem on Chrome iOS

AlaDouagi avatar Apr 10 '24 04:04 AlaDouagi

[TEMP Solution]: I use TOP Sheet instead and works. https://ui.shadcn.com/docs/components/sheet image

WatanabeThiago avatar Apr 10 '24 20:04 WatanabeThiago

[TEMP Solution]:

I use TOP Sheet instead and works.

https://ui.shadcn.com/docs/components/sheet

image

God bless you 🥰🥰😘😘

alvvaysxxx avatar Apr 17 '24 06:04 alvvaysxxx

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

AlexanderIsom avatar May 27 '24 21:05 AlexanderIsom

Made a video explaining it if it'll help anybody https://www.youtube.com/watch?v=QTtxeIJmN9o&ab_channel=HosnaQasmei

hqasmei avatar Jun 02 '24 07:06 hqasmei

@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.

rustyjux avatar Jun 02 '24 08:06 rustyjux

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.

flackeryyy avatar Jun 16 '24 00:06 flackeryyy

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 avatar Jun 20 '24 18:06 AlejandroGutierrezB

@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 avatar Jun 25 '24 08:06 flackeryyy

@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

rustyjux avatar Jun 28 '24 17:06 rustyjux

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; }

BeeBeeWeb avatar Jul 26 '24 12:07 BeeBeeWeb

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;

image

Relampago-Martins avatar Sep 16 '24 19:09 Relampago-Martins

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

mikkelpetersen avatar Sep 19 '24 00:09 mikkelpetersen

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.

emilkowalski avatar Sep 24 '24 05:09 emilkowalski

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.

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.

ranlix avatar Sep 27 '24 08:09 ranlix

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.

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

CarrettaRiccardo avatar Oct 01 '24 15:10 CarrettaRiccardo

the same for me. I have 1.0.0 version and still facing the same problem, that text input is under the keyboard

yanachrnsh avatar Oct 03 '24 16:10 yanachrnsh

Still happens in version 1.1.0 mobile chrome iOS

chenlevy avatar Oct 15 '24 09:10 chenlevy

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.

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"
>

ranlix avatar Oct 19 '24 16:10 ranlix