diff --git a/src/features/comment/reply/CommentReply.tsx b/src/features/comment/reply/CommentReply.tsx index 5e54f9ff0b..b499920fdd 100644 --- a/src/features/comment/reply/CommentReply.tsx +++ b/src/features/comment/reply/CommentReply.tsx @@ -23,24 +23,25 @@ import { Centered, Spinner } from "../../auth/Login"; import { handleSelector, jwtSelector } from "../../auth/authSlice"; import { css } from "@emotion/react"; import { preventPhotoswipeGalleryFocusTrap } from "../../gallery/GalleryImg"; +import TextareaAutosizedForOnScreenKeyboard from "../../shared/TextareaAutosizedForOnScreenKeyboard"; export const Container = styled.div` - position: absolute; - inset: 0; + min-height: 100%; display: flex; flex-direction: column; `; -export const Textarea = styled.textarea` +export const Textarea = styled(TextareaAutosizedForOnScreenKeyboard)` border: 0; background: none; resize: none; outline: 0; padding: 1rem; - flex: 1 0 0; - min-height: 7rem; + min-height: 200px; + + flex: 1 0 auto; ${({ theme }) => !theme.dark && diff --git a/src/features/post/new/NewPostText.tsx b/src/features/post/new/NewPostText.tsx index 92a831f8bc..d0c770ab1b 100644 --- a/src/features/post/new/NewPostText.tsx +++ b/src/features/post/new/NewPostText.tsx @@ -12,23 +12,23 @@ import { import { useState } from "react"; import { Centered, Spinner } from "../../auth/Login"; import { css } from "@emotion/react"; +import TextareaAutosizedForOnScreenKeyboard from "../../shared/TextareaAutosizedForOnScreenKeyboard"; const Container = styled.div` - position: absolute; - inset: 0; + min-height: 100%; display: flex; flex-direction: column; `; -const Textarea = styled.textarea` +const Textarea = styled(TextareaAutosizedForOnScreenKeyboard)` border: 0; background: none; resize: none; outline: 0; padding: 1rem; - flex: 1 0 0; + flex: 1 0 auto; min-height: 7rem; ${({ theme }) => diff --git a/src/features/shared/DynamicDismissableModal.tsx b/src/features/shared/DynamicDismissableModal.tsx index 2545d73e00..a904322886 100644 --- a/src/features/shared/DynamicDismissableModal.tsx +++ b/src/features/shared/DynamicDismissableModal.tsx @@ -5,9 +5,10 @@ import React, { useRef, useState, } from "react"; -import { IonModal, useIonActionSheet } from "@ionic/react"; +import { useIonActionSheet } from "@ionic/react"; import { PageContext } from "../auth/PageContext"; import { Prompt, useLocation } from "react-router"; +import IonModalAutosizedForOnScreenKeyboard from "./IonModalAutosizedForOnScreenKeyboard"; export interface DismissableProps { dismiss: () => void; @@ -82,7 +83,7 @@ export function DynamicDismissableModal({ when={!canDismiss} message="Are you sure you want to discard your work?" /> - setIsOpen(false)} @@ -99,7 +100,7 @@ export function DynamicDismissableModal({ onDismissAttemptCb(); }, })} - + ); } diff --git a/src/features/shared/IonModalAutosizedForOnScreenKeyboard.tsx b/src/features/shared/IonModalAutosizedForOnScreenKeyboard.tsx new file mode 100644 index 0000000000..89795a8f39 --- /dev/null +++ b/src/features/shared/IonModalAutosizedForOnScreenKeyboard.tsx @@ -0,0 +1,108 @@ +import { IonModal } from "@ionic/react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import usePageVisibility from "../../helpers/usePageVisibility"; +import styled from "@emotion/styled"; + +// TODO it's a bit buggy trying to compute this +// in realtime with the new post dialog + comment dialogs +// So hardcode for now +const FIXED_HEADER_HEIGHT = 56; + +const StyledIonModal = styled(IonModal)<{ viewportHeight: number }>` + ion-content::part(scroll) { + max-height: ${({ viewportHeight }) => viewportHeight}px; + } +`; + +export default function IonModalAutosizedForOnScreenKeyboard( + props: React.ComponentProps +) { + const [viewportHeight, setViewportHeight] = useState( + document.documentElement.clientHeight + ); + const isVisible = usePageVisibility(); + // eslint-disable-next-line no-undef + const modalRef = useRef(null); + + const updateViewport = useCallback(() => { + if (!props.isOpen) return; + + // For the rare legacy browsers that don't support it + if (!window.visualViewport) { + return; + } + + const page = modalRef.current?.querySelector( + ".ion-page:not(.ion-page-hidden)" + ); + + setViewportHeight( + window.visualViewport.height - + (page instanceof HTMLElement ? cumulativeOffset(page).top : 0) - + FIXED_HEADER_HEIGHT + ); + }, [props.isOpen]); + + const onScroll = useCallback(() => { + setTimeout(() => { + window.scrollTo(0, 0); + }, 100); + }, []); + + // Turning iPhone on/off can mess up the scrolling to top again + useEffect(() => { + if (!props.isOpen) return; + + updateViewport(); + }, [isVisible, updateViewport, props.isOpen]); + + useEffect(() => { + if (!props.isOpen) return; + + document.addEventListener("scroll", onScroll); + + return () => { + document.removeEventListener("scroll", onScroll); + }; + }, [onScroll, props.isOpen]); + + useEffect(() => { + if (!props.isOpen) return; + + const onResize = () => { + updateViewport(); + }; + + window.visualViewport?.addEventListener("resize", onResize); + + return () => { + window.visualViewport?.removeEventListener("resize", onResize); + }; + }, [updateViewport, props.isOpen]); + + return ( + { + window.scrollTo(0, 0); + }} + {...props} + /> + ); +} + +function cumulativeOffset(element: HTMLElement) { + let top = 0, + left = 0; + do { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent as HTMLElement; + } while (element instanceof HTMLElement); + + return { + top: top, + left: left, + }; +} diff --git a/src/features/shared/TextareaAutosizedForOnScreenKeyboard.tsx b/src/features/shared/TextareaAutosizedForOnScreenKeyboard.tsx new file mode 100644 index 0000000000..ffc8e480b9 --- /dev/null +++ b/src/features/shared/TextareaAutosizedForOnScreenKeyboard.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { isAppleDeviceInstalledToHomescreen } from "../../helpers/device"; +import { fixSafariAutoscroll } from "../../helpers/safari"; +import TextareaAutosize, { + TextareaAutosizeProps, +} from "react-textarea-autosize"; + +export default function TextareaAutosizedForOnScreenKeyboard( + props: Omit< + TextareaAutosizeProps & React.RefAttributes, + "onFocus" + > +) { + return ( + { + if (!isAppleDeviceInstalledToHomescreen()) return; + + // https://stackoverflow.com/a/74902393/1319878 + const target = e.currentTarget; + target.style.opacity = "0"; + setTimeout(() => (target.style.opacity = "1")); + + fixSafariAutoscroll(); + }} + {...props} + /> + ); +} diff --git a/src/helpers/safari.ts b/src/helpers/safari.ts new file mode 100644 index 0000000000..16bd4be759 --- /dev/null +++ b/src/helpers/safari.ts @@ -0,0 +1,13 @@ +export function fixSafariAutoscroll() { + let checkAttempts = 0; + + const interval = setInterval(() => { + window.scrollTo(0, 0); + + if (window.scrollY === 0) { + checkAttempts++; + + if (checkAttempts > 10) clearInterval(interval); + } + }, 100); +}