From cac29bac0df9112cec1d4c66af82dd343081e08a Mon Sep 17 00:00:00 2001 From: ben-basten <45583362+ben-basten@users.noreply.github.com> Date: Thu, 19 Sep 2024 23:22:05 -0400 Subject: [PATCH] feat: zooming and virtual keyboard working for iPadOS/Safari --- .../shared-components/combobox.svelte | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/web/src/lib/components/shared-components/combobox.svelte b/web/src/lib/components/shared-components/combobox.svelte index a565268dc7..6aa97bfdd2 100644 --- a/web/src/lib/components/shared-components/combobox.svelte +++ b/web/src/lib/components/shared-components/combobox.svelte @@ -21,7 +21,7 @@ import { fly } from 'svelte/transition'; import Icon from '$lib/components/elements/icon.svelte'; import { mdiMagnify, mdiUnfoldMoreHorizontal, mdiClose } from '@mdi/js'; - import { createEventDispatcher, onDestroy, onMount, tick } from 'svelte'; + import { createEventDispatcher, onMount, tick } from 'svelte'; import type { FormEventHandler } from 'svelte/elements'; import { shortcuts } from '$lib/actions/shortcut'; import { focusOutside } from '$lib/actions/focus-outside'; @@ -52,6 +52,7 @@ let selectedIndex: number | undefined; let optionRefs: HTMLElement[] = []; let input: HTMLInputElement; + let dropdown: HTMLUListElement; let bounds: DOMRect | undefined; let scrollableAncestor: Element | null; let dropdownDirection: 'bottom' | 'top' = 'bottom'; @@ -81,11 +82,15 @@ observer.observe(input); scrollableAncestor = input.closest('.overflow-y-auto, .overflow-y-scroll'); scrollableAncestor?.addEventListener('scroll', onPositionChange); - }); + window.visualViewport?.addEventListener('resize', onPositionChange); + window.visualViewport?.addEventListener('scroll', onPositionChange); - onDestroy(() => { - scrollableAncestor?.removeEventListener('scroll', onPositionChange); - observer.disconnect(); + return () => { + observer.disconnect(); + scrollableAncestor?.removeEventListener('scroll', onPositionChange); + window.visualViewport?.removeEventListener('resize', onPositionChange); + window.visualViewport?.removeEventListener('scroll', onPositionChange); + }; }); const dispatch = createEventDispatcher<{ @@ -155,21 +160,28 @@ return undefined; } - const viewportHeight = window.innerHeight; + const vv = window.visualViewport; + const viewportHeight = vv?.height || 0; + const left = boundary.left + (vv?.offsetLeft || 0); + const offsetTop = vv?.offsetTop || 0; if (dropdownDirection === 'top') { + const dropdownHeight = dropdown?.clientHeight || 0; + const availableHeight = boundary.top - dropdownOffset; + const adjustTop = Math.max(availableHeight - dropdownHeight, 0); return { - bottom: `${viewportHeight - boundary.top}px`, - left: `${boundary.left}px`, + top: `${dropdownOffset + offsetTop + adjustTop}px`, + left: `${left}px`, width: `${boundary.width}px`, - maxHeight: maxHeight(boundary.top - dropdownOffset), + maxHeight: maxHeight(availableHeight), }; } + const top = boundary.bottom + offsetTop; const availableHeight = viewportHeight - boundary.bottom; return { - top: `${boundary.bottom}px`, - left: `${boundary.left}px`, + top: `${top}px`, + left: `${left}px`, width: `${boundary.width}px`, maxHeight: maxHeight(availableHeight - dropdownOffset), }; @@ -191,7 +203,7 @@ return 'bottom'; } - const viewportHeight = window.innerHeight; + const viewportHeight = window.visualViewport?.height || 0; const heightBelow = viewportHeight - boundary.bottom; const heightAbove = boundary.top; @@ -201,7 +213,6 @@ const getInputPosition = () => input?.getBoundingClientRect(); -
{#if isOpen} {#if filteredOptions.length === 0}