From 5b9cecc5f7430033641a4a076c297b8b882da8bb Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Thu, 16 Jan 2025 13:34:15 +0100 Subject: [PATCH 1/4] fix: Prevent dropdown misplacement in iOS --- src/internal/components/dropdown/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/internal/components/dropdown/index.tsx b/src/internal/components/dropdown/index.tsx index 7ba65b31a2..2007804f94 100644 --- a/src/internal/components/dropdown/index.tsx +++ b/src/internal/components/dropdown/index.tsx @@ -231,11 +231,13 @@ const Dropdown = ({ target.style.insetInlineStart = position.insetInlineStart; } + const targetRect = getLogicalBoundingClientRect(target); + // Position normal overflow dropdowns with fixed positioning relative to viewport if (expandToViewport && !interior) { target.style.position = 'fixed'; if (position.dropBlockStart) { - target.style.insetBlockEnd = `calc(100% - ${triggerBox.top}px)`; + target.style.insetBlockStart = `${triggerBox.top - targetRect.blockSize}px`; } else { target.style.insetBlockStart = `${triggerBox.bottom}px`; } @@ -393,9 +395,10 @@ const Dropdown = ({ if (triggerRef.current && dropdownRef.current && verticalContainerRef.current) { const triggerRect = getLogicalBoundingClientRect(triggerRef.current); const target = dropdownRef.current; + const targetRect = getLogicalBoundingClientRect(target); if (fixedPosition.current) { if (fixedPosition.current.dropBlockStart) { - dropdownRef.current.style.insetBlockEnd = `calc(100% - ${triggerRect.insetBlockStart}px)`; + target.style.insetBlockStart = `${triggerRect.insetBlockStart - targetRect.blockSize}px`; } else { target.style.insetBlockStart = `${triggerRect.insetBlockEnd}px`; } From 291cc9803f61584f609fd6ccad601e1d47ebb33f Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 17 Jan 2025 12:28:43 +0100 Subject: [PATCH 2/4] Revert "fix: Prevent dropdown misplacement in iOS" This reverts commit 5b9cecc5f7430033641a4a076c297b8b882da8bb. --- src/internal/components/dropdown/index.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/internal/components/dropdown/index.tsx b/src/internal/components/dropdown/index.tsx index 2007804f94..7ba65b31a2 100644 --- a/src/internal/components/dropdown/index.tsx +++ b/src/internal/components/dropdown/index.tsx @@ -231,13 +231,11 @@ const Dropdown = ({ target.style.insetInlineStart = position.insetInlineStart; } - const targetRect = getLogicalBoundingClientRect(target); - // Position normal overflow dropdowns with fixed positioning relative to viewport if (expandToViewport && !interior) { target.style.position = 'fixed'; if (position.dropBlockStart) { - target.style.insetBlockStart = `${triggerBox.top - targetRect.blockSize}px`; + target.style.insetBlockEnd = `calc(100% - ${triggerBox.top}px)`; } else { target.style.insetBlockStart = `${triggerBox.bottom}px`; } @@ -395,10 +393,9 @@ const Dropdown = ({ if (triggerRef.current && dropdownRef.current && verticalContainerRef.current) { const triggerRect = getLogicalBoundingClientRect(triggerRef.current); const target = dropdownRef.current; - const targetRect = getLogicalBoundingClientRect(target); if (fixedPosition.current) { if (fixedPosition.current.dropBlockStart) { - target.style.insetBlockStart = `${triggerRect.insetBlockStart - targetRect.blockSize}px`; + dropdownRef.current.style.insetBlockEnd = `calc(100% - ${triggerRect.insetBlockStart}px)`; } else { target.style.insetBlockStart = `${triggerRect.insetBlockEnd}px`; } From ef671e1c1c55bb69c160516dd19fa77d1a549245 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 17 Jan 2025 12:37:45 +0100 Subject: [PATCH 3/4] Use absolute position --- src/internal/components/dropdown/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/internal/components/dropdown/index.tsx b/src/internal/components/dropdown/index.tsx index 7ba65b31a2..de023be45b 100644 --- a/src/internal/components/dropdown/index.tsx +++ b/src/internal/components/dropdown/index.tsx @@ -233,11 +233,11 @@ const Dropdown = ({ // Position normal overflow dropdowns with fixed positioning relative to viewport if (expandToViewport && !interior) { - target.style.position = 'fixed'; + target.style.position = 'absolute'; if (position.dropBlockStart) { - target.style.insetBlockEnd = `calc(100% - ${triggerBox.top}px)`; + target.style.insetBlockEnd = `calc(100% - ${document.documentElement.scrollTop + triggerBox.top}px)`; } else { - target.style.insetBlockStart = `${triggerBox.bottom}px`; + target.style.insetBlockStart = `${document.documentElement.scrollTop + triggerBox.bottom}px`; } if (position.dropInlineStart) { target.style.insetInlineStart = `calc(${triggerBox.right}px - ${position.inlineSize})`; @@ -395,9 +395,9 @@ const Dropdown = ({ const target = dropdownRef.current; if (fixedPosition.current) { if (fixedPosition.current.dropBlockStart) { - dropdownRef.current.style.insetBlockEnd = `calc(100% - ${triggerRect.insetBlockStart}px)`; + dropdownRef.current.style.insetBlockEnd = `calc(100% - ${document.documentElement.scrollTop + triggerRect.insetBlockStart}px)`; } else { - target.style.insetBlockStart = `${triggerRect.insetBlockEnd}px`; + target.style.insetBlockStart = `${document.documentElement.scrollTop + triggerRect.insetBlockEnd}px`; } if (fixedPosition.current.dropInlineStart) { target.style.insetInlineStart = `calc(${triggerRect.insetInlineEnd}px - ${fixedPosition.current.inlineSize})`; From 9a0c7c15fd3c02e711220ea2c9286699dad22763 Mon Sep 17 00:00:00 2001 From: Joan Perals Tresserra Date: Fri, 17 Jan 2025 17:59:03 +0100 Subject: [PATCH 4/4] Use absolute positioning only when iOS virtual keyboard is present --- src/internal/components/dropdown/index.tsx | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/internal/components/dropdown/index.tsx b/src/internal/components/dropdown/index.tsx index de023be45b..57b15fffd7 100644 --- a/src/internal/components/dropdown/index.tsx +++ b/src/internal/components/dropdown/index.tsx @@ -233,16 +233,20 @@ const Dropdown = ({ // Position normal overflow dropdowns with fixed positioning relative to viewport if (expandToViewport && !interior) { - target.style.position = 'absolute'; + const isIOSVirtualKeyboardPresent = + window.visualViewport?.height && window.visualViewport.height < window.innerHeight; + target.style.position = isIOSVirtualKeyboardPresent ? 'absolute' : 'fixed'; + const verticalScrollOffset = isIOSVirtualKeyboardPresent ? document.documentElement.scrollTop : 0; + const horizontalScrollOffset = isIOSVirtualKeyboardPresent ? document.documentElement.scrollLeft : 0; if (position.dropBlockStart) { - target.style.insetBlockEnd = `calc(100% - ${document.documentElement.scrollTop + triggerBox.top}px)`; + target.style.insetBlockEnd = `calc(100% - ${verticalScrollOffset + triggerBox.top}px)`; } else { - target.style.insetBlockStart = `${document.documentElement.scrollTop + triggerBox.bottom}px`; + target.style.insetBlockStart = `${verticalScrollOffset + triggerBox.bottom}px`; } if (position.dropInlineStart) { - target.style.insetInlineStart = `calc(${triggerBox.right}px - ${position.inlineSize})`; + target.style.insetInlineStart = `calc(${horizontalScrollOffset + triggerBox.right}px - ${position.inlineSize})`; } else { - target.style.insetInlineStart = `${triggerBox.left}px`; + target.style.insetInlineStart = `${horizontalScrollOffset + triggerBox.left}px`; } // Keep track of the initial dropdown position and direction. // Dropdown direction doesn't need to change as the user scrolls, just needs to stay attached to the trigger. @@ -391,18 +395,26 @@ const Dropdown = ({ } const updateDropdownPosition = () => { if (triggerRef.current && dropdownRef.current && verticalContainerRef.current) { + const isIOSVirtualKeyboardPresent = + window.visualViewport?.height && window.visualViewport.height < window.innerHeight; + + dropdownRef.current.style.position = isIOSVirtualKeyboardPresent ? 'absolute' : 'fixed'; + + const verticalScrollOffset = isIOSVirtualKeyboardPresent ? document.documentElement.scrollTop : 0; + const horizontalScrollOffset = isIOSVirtualKeyboardPresent ? document.documentElement.scrollLeft : 0; + const triggerRect = getLogicalBoundingClientRect(triggerRef.current); const target = dropdownRef.current; if (fixedPosition.current) { if (fixedPosition.current.dropBlockStart) { - dropdownRef.current.style.insetBlockEnd = `calc(100% - ${document.documentElement.scrollTop + triggerRect.insetBlockStart}px)`; + dropdownRef.current.style.insetBlockEnd = `calc(100% - ${verticalScrollOffset + triggerRect.insetBlockStart}px)`; } else { - target.style.insetBlockStart = `${document.documentElement.scrollTop + triggerRect.insetBlockEnd}px`; + target.style.insetBlockStart = `${verticalScrollOffset + triggerRect.insetBlockEnd}px`; } if (fixedPosition.current.dropInlineStart) { - target.style.insetInlineStart = `calc(${triggerRect.insetInlineEnd}px - ${fixedPosition.current.inlineSize})`; + target.style.insetInlineStart = `calc(${horizontalScrollOffset + triggerRect.insetInlineEnd}px - ${fixedPosition.current.inlineSize})`; } else { - target.style.insetInlineStart = `${triggerRect.insetInlineStart}px`; + target.style.insetInlineStart = `${horizontalScrollOffset + triggerRect.insetInlineStart}px`; } } }