fix: planning modal full-screen height on mobile
Two related bugs left the Planning Mode modal stuck at partial height on mobile. useModalResizePersist replayed desktop-saved pixel dimensions that overrode the mobile 100dvh rule, and React reconciled removed CSS custom properties to empty string rather than calling removeProperty(), which on iOS Safari defeated the var() fallback after keyboard dismiss. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import "./PlanningModeModal.css";
|
||||
import { useState, useCallback, useEffect, useRef, useMemo, type CSSProperties } from "react";
|
||||
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
||||
import type { Task, PlanningQuestion, PlanningSummary } from "@fusion/core";
|
||||
import { getErrorMessage } from "@fusion/core";
|
||||
import {
|
||||
@@ -193,13 +193,30 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
useMobileKeyboard({ enabled: viewportMode === "mobile" });
|
||||
useMobileScrollLock(viewportMode === "mobile" && isOpen);
|
||||
|
||||
const modalKeyboardStyle: CSSProperties = keyboardOpen
|
||||
? ({
|
||||
"--keyboard-overlap": `${keyboardOverlap}px`,
|
||||
"--vv-offset-top": `${viewportOffsetTop}px`,
|
||||
...(viewportHeight !== null ? { "--vv-height": `${viewportHeight}px` } : {}),
|
||||
} as CSSProperties)
|
||||
: {};
|
||||
// Drive --vv-height / --keyboard-overlap / --vv-offset-top imperatively
|
||||
// rather than via React's style prop. Reason: when React removes a CSS
|
||||
// custom property between renders it sets it to empty string instead of
|
||||
// calling removeProperty(). On iOS Safari that leaves the variable defined
|
||||
// as "", so `height: var(--vv-height, 100dvh)` resolves to empty (the
|
||||
// fallback only applies when the var is *undefined*) and the modal
|
||||
// collapses to content height after the keyboard is dismissed.
|
||||
useEffect(() => {
|
||||
const node = modalRef.current;
|
||||
if (!node) return;
|
||||
if (keyboardOpen) {
|
||||
node.style.setProperty("--keyboard-overlap", `${keyboardOverlap}px`);
|
||||
node.style.setProperty("--vv-offset-top", `${viewportOffsetTop}px`);
|
||||
if (viewportHeight !== null) {
|
||||
node.style.setProperty("--vv-height", `${viewportHeight}px`);
|
||||
} else {
|
||||
node.style.removeProperty("--vv-height");
|
||||
}
|
||||
} else {
|
||||
node.style.removeProperty("--keyboard-overlap");
|
||||
node.style.removeProperty("--vv-offset-top");
|
||||
node.style.removeProperty("--vv-height");
|
||||
}
|
||||
}, [keyboardOpen, keyboardOverlap, viewportOffsetTop, viewportHeight]);
|
||||
|
||||
// Mirror streamingOutput into a ref so SSE handlers can read the latest
|
||||
// value without stale closure issues.
|
||||
@@ -1568,7 +1585,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div className="modal modal-lg planning-modal" ref={modalRef} style={modalKeyboardStyle}>
|
||||
<div className="modal modal-lg planning-modal" ref={modalRef}>
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
{mobileShowDetail && (
|
||||
|
||||
@@ -32,6 +32,21 @@ export function useModalResizePersist(
|
||||
const node = ref.current;
|
||||
if (!node) return;
|
||||
|
||||
// On mobile, modals render full-screen via CSS (height: 100dvh) and the
|
||||
// resize grip is disabled. Replaying a desktop-saved pixel height here
|
||||
// would override the mobile CSS and leave the modal stuck at a partial
|
||||
// height. Skip restoration; also clear any width/height left over from
|
||||
// a prior desktop render of the same modal instance.
|
||||
const isMobile =
|
||||
typeof window !== "undefined" &&
|
||||
("ontouchstart" in window || navigator.maxTouchPoints > 0) &&
|
||||
window.innerWidth <= 768;
|
||||
if (isMobile) {
|
||||
node.style.removeProperty("width");
|
||||
node.style.removeProperty("height");
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the persisted size on open.
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
|
||||
Reference in New Issue
Block a user