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();
-