diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 8950f3fd3f..4171a46c16 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -2630,7 +2630,7 @@ iframe.print-iframe { } } - #root-widget.virtual-keyboard-opened .note-split:not(.active) { + body:not(.ios) #root-widget.virtual-keyboard-opened .note-split:not(.active) { max-height: 80px; opacity: 0.4; } diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 1b2cc8f8cc..206b2a325a 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -1,7 +1,6 @@ import "./NoteDetail.css"; import clsx from "clsx"; -import { note } from "mermaid/dist/rendering-util/rendering-elements/shapes/note.js"; import { isValidElement, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; diff --git a/apps/client/src/widgets/Toast.css b/apps/client/src/widgets/Toast.css index ecc20c2056..b58e6a13ab 100644 --- a/apps/client/src/widgets/Toast.css +++ b/apps/client/src/widgets/Toast.css @@ -5,7 +5,7 @@ align-items: center; position: absolute; width: 100%; - top: 20px; + top: calc(env(safe-area-inset-top) + 20px); pointer-events: none; contain: none; } diff --git a/apps/client/src/widgets/containers/root_container.ts b/apps/client/src/widgets/containers/root_container.ts index ea79893d19..2e852033c4 100644 --- a/apps/client/src/widgets/containers/root_container.ts +++ b/apps/client/src/widgets/containers/root_container.ts @@ -3,7 +3,7 @@ import { LOCALES } from "@triliumnext/commons"; import { EventData } from "../../components/app_context.js"; import { getEnabledExperimentalFeatureIds } from "../../services/experimental_features.js"; import options from "../../services/options.js"; -import utils, { isMobile } from "../../services/utils.js"; +import utils, { isIOS, isMobile } from "../../services/utils.js"; import { readCssVar } from "../../utils/css-var.js"; import type BasicWidget from "../basic_widget.js"; import FlexContainer from "./flex_container.js"; @@ -19,9 +19,12 @@ import FlexContainer from "./flex_container.js"; */ export default class RootContainer extends FlexContainer { + private originalWindowHeight: number; + constructor(isHorizontalLayout: boolean) { super(isHorizontalLayout ? "column" : "row"); + this.originalWindowHeight = window.innerHeight ?? 0; this.id("root-widget"); this.css("height", "100dvh"); } @@ -31,6 +34,7 @@ export default class RootContainer extends FlexContainer { window.visualViewport?.addEventListener("resize", () => this.#onMobileResize()); } + this.#setDeviceSpecificClasses(); this.#setMaxContentWidth(); this.#setMotion(); this.#setShadows(); @@ -65,7 +69,7 @@ export default class RootContainer extends FlexContainer { #onMobileResize() { const viewportHeight = window.visualViewport?.height ?? window.innerHeight; - const windowHeight = window.innerHeight; + const windowHeight = Math.max(window.innerHeight, this.originalWindowHeight); // inner height changes when keyboard is opened, we need to compare with the original height to detect it. // If viewport is significantly smaller, keyboard is likely open const isKeyboardOpened = windowHeight - viewportHeight > 150; @@ -117,6 +121,12 @@ export default class RootContainer extends FlexContainer { document.body.dir = correspondingLocale?.rtl ? "rtl" : "ltr"; } + #setDeviceSpecificClasses() { + if (isIOS()) { + document.body.classList.add("ios"); + } + } + #initPWATopbarColor() { if (!utils.isPWA()) return; const tracker = $("#background-color-tracker"); diff --git a/apps/client/src/widgets/note_wrapper.ts b/apps/client/src/widgets/note_wrapper.ts index 2328608480..642a851be1 100644 --- a/apps/client/src/widgets/note_wrapper.ts +++ b/apps/client/src/widgets/note_wrapper.ts @@ -1,11 +1,11 @@ -import FlexContainer from "./containers/flex_container.js"; -import utils from "../services/utils.js"; -import attributeService from "../services/attributes.js"; -import type BasicWidget from "./basic_widget.js"; import type { EventData } from "../components/app_context.js"; import type NoteContext from "../components/note_context.js"; import type FNote from "../entities/fnote.js"; +import attributeService from "../services/attributes.js"; import { getLocaleById } from "../services/i18n.js"; +import utils from "../services/utils.js"; +import type BasicWidget from "./basic_widget.js"; +import FlexContainer from "./containers/flex_container.js"; export default class NoteWrapperWidget extends FlexContainer { @@ -43,11 +43,16 @@ export default class NoteWrapperWidget extends FlexContainer { refresh() { const isHiddenExt = this.isHiddenExt(); // preserve through class reset + const isActive = this.$widget.hasClass("active"); this.$widget.removeClass(); this.toggleExt(!isHiddenExt); + if (isActive) { + this.$widget.addClass("active"); + } + this.$widget.addClass("component note-split"); const note = this.noteContext?.note; @@ -92,7 +97,7 @@ export default class NoteWrapperWidget extends FlexContainer { #hasBackgroundEffects(note: FNote): boolean { const MIME_TYPES_WITH_BACKGROUND_EFFECTS = [ "application/pdf" - ] + ]; const COLLECTIONS_WITH_BACKGROUND_EFFECTS = [ "grid", diff --git a/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.css b/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.css index 2286cc013d..1ccc139f87 100644 --- a/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.css +++ b/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.css @@ -2,22 +2,14 @@ body.mobile { .classic-toolbar-outer-container { contain: none !important; } - + .classic-toolbar-outer-container.visible { height: 38px; - background-color: var(--main-background-color); position: relative; overflow: visible; flex-shrink: 0; } - - #root-widget.virtual-keyboard-opened .classic-toolbar-outer-container.ios { - position: absolute; - inset-inline-start: 0; - inset-inline-end: 0; - bottom: 0; - } - + .classic-toolbar-widget { position: absolute; bottom: 0; @@ -28,27 +20,27 @@ body.mobile { display: flex; align-items: flex-end; user-select: none; + touch-action: pan-x; scrollbar-width: 0 !important; } - + .classic-toolbar-widget::-webkit-scrollbar:horizontal { height: 0 !important; } - + .classic-toolbar-widget.dropdown-active { height: 50vh; } - + .classic-toolbar-widget .ck.ck-toolbar { - --ck-color-toolbar-background: transparent; + --ck-color-toolbar-background: var(--main-background-color); --ck-color-button-default-background: transparent; --ck-color-button-default-disabled-background: transparent; position: absolute; - background-color: transparent; border: none; } - + .classic-toolbar-widget .ck.ck-button.ck-disabled { opacity: 0.3; } -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.tsx b/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.tsx index 2d40315525..c5e5882746 100644 --- a/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.tsx +++ b/apps/client/src/widgets/type_widgets/text/mobile_editor_toolbar.tsx @@ -66,11 +66,22 @@ export default function MobileEditorToolbar({ inPopupEditor }: MobileEditorToolb } function usePositioningOniOS(enabled: boolean, wrapperRef: MutableRef) { + // Capture the baseline offset (Safari nav bar height) before the keyboard opens. + const baselineOffset = useRef(window.innerHeight - (window.visualViewport?.height ?? window.innerHeight)); + const adjustPosition = useCallback(() => { if (!wrapperRef.current) return; - const bottom = window.innerHeight - (window.visualViewport?.height || 0); - wrapperRef.current.style.bottom = `${bottom}px`; - }, []); + const viewport = window.visualViewport; + if (!viewport) return; + // Subtract the baseline so only the keyboard's contribution remains. + const bottom = window.innerHeight - viewport.height - viewport.offsetTop; + if (bottom - baselineOffset.current <= 0) { + // Keyboard is hidden — clear the inline style so CSS controls positioning. + wrapperRef.current.style.removeProperty("bottom"); + } else { + wrapperRef.current.style.bottom = `${bottom}px`; + } + }, [ wrapperRef ]); useEffect(() => { if (!isIOS() || !enabled) return; @@ -82,7 +93,7 @@ function usePositioningOniOS(enabled: boolean, wrapperRef: MutableRef