Skip to content

Commit c4c94e8

Browse files
committed
Fix browser viewport keyboard resize, toolbar blur, and responsive re-snap bugs
- Keyboard resize: Use a ref to accumulate dimensions between renders and debounce commits so held arrow keys correctly step by 10px each event instead of repeating the same stale delta. - Toolbar blur: Always apply edited dimensions when focus leaves the form, instead of silently discarding them when focus moves to another toolbar control like rotate or preset select. - Responsive re-snap: Pass computed responsive size to the device toolbar and use it when re-selecting 'Responsive', so users can snap back to the panel-fitted size after manual resizes.
1 parent 4582dc6 commit c4c94e8

3 files changed

Lines changed: 26 additions & 13 deletions

File tree

apps/web/src/browser/BrowserDeviceToolbar.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
PREVIEW_VIEWPORT_MAX_DIMENSION,
66
PREVIEW_VIEWPORT_MIN_DIMENSION,
77
type PreviewViewportSetting,
8+
type PreviewViewportSize,
89
} from "@t3tools/contracts";
910
import { PREVIEW_VIEWPORT_PRESETS, resolvePreviewViewport } from "@t3tools/shared/previewViewport";
1011
import { RotateCw, X } from "lucide-react";
@@ -38,10 +39,11 @@ const SELECT_ITEMS = [
3839
interface Props {
3940
readonly setting: Exclude<PreviewViewportSetting, { readonly _tag: "fill" }>;
4041
readonly width: number;
42+
readonly responsiveSize?: PreviewViewportSize | undefined;
4143
readonly onChange: (setting: PreviewViewportSetting) => Promise<void>;
4244
}
4345

44-
export function BrowserDeviceToolbar({ setting, width, onChange }: Props) {
46+
export function BrowserDeviceToolbar({ setting, width, responsiveSize, onChange }: Props) {
4547
const [pending, setPending] = useState(false);
4648
const [customSize, setCustomSize] = useState<{
4749
readonly width: string;
@@ -85,8 +87,9 @@ export function BrowserDeviceToolbar({ setting, width, onChange }: Props) {
8587
const selectViewport = (value: string | null) => {
8688
if (!value) return;
8789
if (value === RESPONSIVE_VALUE) {
88-
if (setting._tag === "freeform") return;
89-
apply({ _tag: "freeform", width: setting.width, height: setting.height });
90+
const size = responsiveSize ?? { width: setting.width, height: setting.height };
91+
if (size.width === setting.width && size.height === setting.height) return;
92+
apply({ _tag: "freeform", ...size });
9093
return;
9194
}
9295
const preset = PREVIEW_VIEWPORT_PRESETS.find((candidate) => candidate.id === value);
@@ -154,13 +157,6 @@ export function BrowserDeviceToolbar({ setting, width, onChange }: Props) {
154157
onBlur={(event) => {
155158
const nextTarget = event.relatedTarget;
156159
if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) return;
157-
if (
158-
nextTarget instanceof HTMLElement &&
159-
nextTarget.closest("[data-browser-device-toolbar]")
160-
) {
161-
setCustomSize(null);
162-
return;
163-
}
164160
applyCustomSize();
165161
}}
166162
>

apps/web/src/browser/HostedBrowserWebview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { cn } from "~/lib/utils";
1111
import { useActiveBrowserRecordingTabId } from "./browserRecording";
1212
import { useBrowserSurfaceStore } from "./browserSurfaceStore";
1313
import { BrowserDeviceToolbar } from "./BrowserDeviceToolbar";
14+
import { resolveResponsiveBrowserViewportSize } from "./browserViewportLayout";
1415
import { BrowserViewportResizeHandles } from "./BrowserViewportResizeHandles";
1516
import { acquireDesktopTab, type AcquiredDesktopTab } from "./desktopTabLifetime";
1617
import { usePreviewWebviewConfig } from "./previewWebviewConfigState";
@@ -193,6 +194,7 @@ export function HostedBrowserWebview(props: {
193194
<BrowserDeviceToolbar
194195
setting={effectiveViewport}
195196
width={Math.max(1, Math.round(containerSize.width))}
197+
responsiveSize={resolveResponsiveBrowserViewportSize(containerSize, zoomFactor)}
196198
onChange={commitViewportChange}
197199
/>
198200
) : null}

apps/web/src/browser/useBrowserViewportResize.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export function useBrowserViewportResize(options: {
4040
const { tabId, viewport, zoomFactor, containerSize, deviceToolbarVisible } = options;
4141
const dragCleanupRef = useRef<(() => void) | null>(null);
4242
const dragVersionRef = useRef(0);
43+
const keyboardSizeRef = useRef<PreviewViewportSize | null>(null);
44+
const keyboardTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
4345
const [dragViewport, setDragViewport] = useState<ViewportDrag | null>(null);
4446
const sourceViewportKey = viewportSettingKey(viewport);
4547
const sourceViewportKeyRef = useRef(sourceViewportKey);
@@ -65,6 +67,7 @@ export function useBrowserViewportResize(options: {
6567
() => () => {
6668
dragVersionRef.current += 1;
6769
dragCleanupRef.current?.();
70+
if (keyboardTimerRef.current !== null) clearTimeout(keyboardTimerRef.current);
6871
},
6972
[],
7073
);
@@ -73,6 +76,11 @@ export function useBrowserViewportResize(options: {
7376
(next: PreviewViewportSetting) => {
7477
dragVersionRef.current += 1;
7578
dragCleanupRef.current?.();
79+
if (keyboardTimerRef.current !== null) {
80+
clearTimeout(keyboardTimerRef.current);
81+
keyboardTimerRef.current = null;
82+
}
83+
keyboardSizeRef.current = null;
7684
setDragViewport(null);
7785
return commitBrowserViewportChange(tabId, next);
7886
},
@@ -109,10 +117,17 @@ export function useBrowserViewportResize(options: {
109117
if (!delta) return;
110118
event.preventDefault();
111119
event.stopPropagation();
112-
const next = resizeFreeformViewport(effectiveViewport, delta, zoomFactor, direction);
113-
if (next.width === effectiveViewport.width && next.height === effectiveViewport.height) return;
120+
const base = keyboardSizeRef.current ?? effectiveViewport;
121+
const next = resizeFreeformViewport(base, delta, zoomFactor, direction);
122+
if (next.width === base.width && next.height === base.height) return;
123+
keyboardSizeRef.current = next;
114124
setDragViewport({ sourceKey: sourceViewportKey, ...next, direction });
115-
commitDrag({ _tag: "freeform", ...next });
125+
if (keyboardTimerRef.current !== null) clearTimeout(keyboardTimerRef.current);
126+
keyboardTimerRef.current = setTimeout(() => {
127+
keyboardTimerRef.current = null;
128+
keyboardSizeRef.current = null;
129+
commitDrag({ _tag: "freeform", ...next });
130+
}, 150);
116131
};
117132

118133
const handleResizePointerDown = (

0 commit comments

Comments
 (0)