Skip to content

Commit 7c8d562

Browse files
authored
fix(next): live preview device position when using zoom (#6665)
1 parent 11c3a65 commit 7c8d562

File tree

7 files changed

+247
-82
lines changed

7 files changed

+247
-82
lines changed

packages/next/src/views/LivePreview/Device/index.tsx

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ export const DeviceContainer: React.FC<{
1010
const { children } = props
1111

1212
const deviceFrameRef = React.useRef<HTMLDivElement>(null)
13+
const outerFrameRef = React.useRef<HTMLDivElement>(null)
1314

14-
const { breakpoint, setMeasuredDeviceSize, size, zoom } = useLivePreviewContext()
15+
const { breakpoint, setMeasuredDeviceSize, size: desiredSize, zoom } = useLivePreviewContext()
1516

1617
// Keep an accurate measurement of the actual device size as it is truly rendered
1718
// This is helpful when `sizes` are non-number units like percentages, etc.
18-
const { size: measuredDeviceSize } = useResize(deviceFrameRef)
19+
const { size: measuredDeviceSize } = useResize(deviceFrameRef.current)
20+
const { size: outerFrameSize } = useResize(outerFrameRef.current)
21+
22+
let deviceIsLargerThanFrame: boolean = false
1923

2024
// Sync the measured device size with the context so that other components can use it
2125
// This happens from the bottom up so that as this component mounts and unmounts,
22-
// Its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
26+
// its size is freshly populated again upon re-mounting, i.e. going from iframe->popup->iframe
2327
useEffect(() => {
2428
if (measuredDeviceSize) {
2529
setMeasuredDeviceSize(measuredDeviceSize)
@@ -34,35 +38,64 @@ export const DeviceContainer: React.FC<{
3438

3539
if (
3640
typeof zoom === 'number' &&
37-
typeof size.width === 'number' &&
38-
typeof size.height === 'number'
41+
typeof desiredSize.width === 'number' &&
42+
typeof desiredSize.height === 'number' &&
43+
typeof measuredDeviceSize.width === 'number' &&
44+
typeof measuredDeviceSize.height === 'number'
3945
) {
40-
const scaledWidth = size.width / zoom
41-
const difference = scaledWidth - size.width
42-
x = `${difference / 2}px`
4346
margin = '0 auto'
47+
const scaledDesiredWidth = desiredSize.width / zoom
48+
const scaledDeviceWidth = measuredDeviceSize.width * zoom
49+
const scaledDeviceDifferencePixels = scaledDesiredWidth - desiredSize.width
50+
deviceIsLargerThanFrame = scaledDeviceWidth > outerFrameSize.width
51+
52+
if (deviceIsLargerThanFrame) {
53+
if (zoom > 1) {
54+
const differenceFromDeviceToFrame = measuredDeviceSize.width - outerFrameSize.width
55+
if (differenceFromDeviceToFrame < 0) x = `${differenceFromDeviceToFrame / 2}px`
56+
else x = '0'
57+
} else {
58+
x = '0'
59+
}
60+
} else {
61+
if (zoom >= 1) {
62+
x = `${scaledDeviceDifferencePixels / 2}px`
63+
} else {
64+
const differenceFromDeviceToFrame = outerFrameSize.width - scaledDeviceWidth
65+
x = `${differenceFromDeviceToFrame / 2}px`
66+
margin = '0'
67+
}
68+
}
4469
}
4570
}
4671

4772
let width = zoom ? `${100 / zoom}%` : '100%'
4873
let height = zoom ? `${100 / zoom}%` : '100%'
4974

5075
if (breakpoint !== 'responsive') {
51-
width = `${size?.width / (typeof zoom === 'number' ? zoom : 1)}px`
52-
height = `${size?.height / (typeof zoom === 'number' ? zoom : 1)}px`
76+
width = `${desiredSize?.width / (typeof zoom === 'number' ? zoom : 1)}px`
77+
height = `${desiredSize?.height / (typeof zoom === 'number' ? zoom : 1)}px`
5378
}
5479

5580
return (
5681
<div
57-
ref={deviceFrameRef}
82+
ref={outerFrameRef}
5883
style={{
59-
height,
60-
margin,
61-
transform: `translate3d(${x}, 0, 0)`,
62-
width,
84+
height: '100%',
85+
width: '100%',
6386
}}
6487
>
65-
{children}
88+
<div
89+
ref={deviceFrameRef}
90+
style={{
91+
height,
92+
margin,
93+
transform: `translate3d(${x}, 0, 0)`,
94+
width,
95+
}}
96+
>
97+
{children}
98+
</div>
6699
</div>
67100
)
68101
}

packages/ui/src/hooks/useIntersect.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
/* eslint-disable no-shadow */
33
import { useEffect, useRef, useState } from 'react'
44

5-
type Intersect = [setNode: React.Dispatch<Element>, entry: IntersectionObserverEntry]
5+
type Intersect = [
6+
setNode: React.Dispatch<HTMLElement>,
7+
entry: IntersectionObserverEntry,
8+
node: HTMLElement,
9+
]
610

711
export const useIntersect = (
812
{ root = null, rootMargin = '0px', threshold = 0 } = {},
@@ -33,5 +37,5 @@ export const useIntersect = (
3337
return () => currentObserver.disconnect()
3438
}, [node, disable])
3539

36-
return [setNode, entry]
40+
return [setNode, entry, node]
3741
}

packages/ui/src/hooks/useResize.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ interface Resize {
1212
size?: Size
1313
}
1414

15-
export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
15+
export const useResize = (element: HTMLElement): Resize => {
1616
const [size, setSize] = useState<Size>()
1717

1818
useEffect(() => {
1919
let observer: any // eslint-disable-line
2020

21-
const { current: currentRef } = ref
22-
23-
if (currentRef) {
21+
if (element) {
2422
observer = new ResizeObserver((entries) => {
2523
entries.forEach((entry) => {
2624
const {
@@ -53,15 +51,15 @@ export const useResize = (ref: React.MutableRefObject<HTMLElement>): Resize => {
5351
})
5452
})
5553

56-
observer.observe(currentRef)
54+
observer.observe(element)
5755
}
5856

5957
return () => {
6058
if (observer) {
61-
observer.unobserve(currentRef)
59+
observer.unobserve(element)
6260
}
6361
}
64-
}, [ref])
62+
}, [element])
6563

6664
return {
6765
size,

test/live-preview/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Footer } from './globals/Footer.js'
1111
import { Header } from './globals/Header.js'
1212
import { seed } from './seed/index.js'
1313
import {
14+
desktopBreakpoint,
1415
mobileBreakpoint,
1516
pagesSlug,
1617
postsSlug,
@@ -25,7 +26,7 @@ export default buildConfigWithDefaults({
2526
// You can define any of these properties on a per collection or global basis
2627
// The Live Preview config cascades from the top down, properties are inherited from here
2728
url: formatLivePreviewURL,
28-
breakpoints: [mobileBreakpoint],
29+
breakpoints: [mobileBreakpoint, desktopBreakpoint],
2930
collections: [pagesSlug, postsSlug, ssrPagesSlug, ssrAutosavePagesSlug],
3031
globals: ['header', 'footer'],
3132
},

0 commit comments

Comments
 (0)