1
+ /* global ResizeObserver */
2
+ import { useRef , useState , useEffect } from '@wordpress/element' ;
1
3
import { useSelect } from '@wordpress/data' ;
2
4
import { nfdOnboardingStore } from '@/data/store' ;
3
5
import { Iframe } from '@/components' ;
@@ -14,7 +16,17 @@ const Preview = () => {
14
16
selectedHomepage : select ( nfdOnboardingStore ) . getSelectedHomepage ( ) ,
15
17
} ;
16
18
} ) ;
17
-
19
+
20
+ const refMap = useRef ( {
21
+ previewContainer : null ,
22
+ previewContainerResizeObserver : null ,
23
+ iframeElement : null ,
24
+ previousIframeDimensions : {
25
+ width : 0 ,
26
+ height : 0 ,
27
+ } ,
28
+ } ) ;
29
+
18
30
const StatusOverlay = ( ) => {
19
31
const appHeaderHeight = document . querySelector ( '.nfd-onboarding-header' ) ?. offsetHeight || 0 ;
20
32
@@ -29,7 +41,7 @@ const Preview = () => {
29
41
< Spinner
30
42
variant = "primary"
31
43
size = "8"
32
- />
44
+ />
33
45
</ div >
34
46
) ;
35
47
}
@@ -47,9 +59,10 @@ const Preview = () => {
47
59
// eslint-disable-next-line react-hooks/exhaustive-deps
48
60
} , [ selectedHomepage ] ) ;
49
61
62
+ // Calculate the iframe height.
50
63
const calculateIframeHeight = ( ) => {
51
- // Get the height from the iframe
52
- const iframeDoc = window . frames [ `nfd-onboarding- ${ preview . slug } ` ] ?. document ;
64
+ // Get the iframe content height.
65
+ const iframeDoc = refMap . current . iframeElement ?. contentWindow ?. document ;
53
66
if ( iframeDoc ) {
54
67
return iframeDoc . documentElement . scrollHeight ;
55
68
}
@@ -58,24 +71,115 @@ const Preview = () => {
58
71
return window . innerHeight - appHeaderHeight ;
59
72
} ;
60
73
61
- const iframeOnLoad = ( ) => {
62
- setIframeHeight ( calculateIframeHeight ( ) ) ;
74
+ /**
75
+ * Calculate the iframe content (preview) scale based on the parent container width.
76
+ */
77
+ const calculateIframeContentScale = ( ) => {
78
+ const previewContainer = refMap . current . previewContainer ;
79
+ const iframeDoc = refMap . current . iframeElement ?. contentWindow ?. document ;
80
+ if ( ! previewContainer || ! iframeDoc ) {
81
+ return ;
82
+ }
83
+
84
+ const containerWidth = previewContainer . offsetWidth ;
85
+ if ( containerWidth === 0 ) {
86
+ return ;
87
+ }
88
+
89
+ // Set the preview content scale by changing its zoom property.
90
+ const setContentScale = ( value ) => {
91
+ iframeDoc . documentElement . style . setProperty ( 'zoom' , value , 'important' ) ;
92
+ } ;
63
93
64
94
/**
65
- * Custom styles to add spacing around the preview.
95
+ * The scale breakpoints map.
96
+ * The order of the breakpoints is important.
97
+ * The first breakpoint that matches the container width will be used.
66
98
*/
67
- const iframeDoc = document . getElementById ( `nfd-onboarding-${ preview . slug } -selected` ) ?. contentWindow ?. document ;
99
+ const scaleBreakpointsMap = [
100
+ { minWidth : 1440 , scale : 1 } ,
101
+ { minWidth : 1280 , scale : 0.95 } ,
102
+ { minWidth : 1024 , scale : 0.9 } ,
103
+ { minWidth : 768 , scale : 0.85 } ,
104
+ { minWidth : 480 , scale : 0.8 } ,
105
+ ] ;
106
+
107
+ // Find the scale breakpoint that matches the container width.
108
+ const scaleBreakpoint = scaleBreakpointsMap . find ( ( breakpoint ) => containerWidth > breakpoint . minWidth ) ;
109
+ if ( scaleBreakpoint ) {
110
+ setContentScale ( scaleBreakpoint . scale ) ;
111
+ } else {
112
+ setContentScale ( 1 ) ;
113
+ }
114
+ } ;
115
+
116
+ /**
117
+ * Dynamically control the iframe content scale and height.
118
+ * Runs on iframe load/re-render.
119
+ *
120
+ * Don't let the code scare you, just follow the comments and you'll be fine.
121
+ */
122
+ const dynamicallyControlIframeScale = ( ) => {
123
+ // Disconnect lingering resize observer.
124
+ if ( refMap . current . previewContainerResizeObserver ) {
125
+ refMap . current . previewContainerResizeObserver . disconnect ( ) ;
126
+ }
127
+ // Observe the preview container for resize events (console, user window resize, etc).
128
+ refMap . current . previewContainerResizeObserver = new ResizeObserver ( ( entries ) => {
129
+ entries . forEach ( ( entry ) => {
130
+ // Get the new width and height values after the resize event.
131
+ const { width : newWidth , height : newHeight } = entry . contentRect ;
132
+
133
+ // Check which values have changed (width, height, both, none).
134
+ const widthHasChanged = newWidth !== refMap . current . previousIframeDimensions . width ;
135
+ const heightHasChanged = newHeight !== refMap . current . previousIframeDimensions . height ;
136
+
137
+ // Update the previous dimensions to the new values.
138
+ refMap . current . previousIframeDimensions = {
139
+ width : newWidth ,
140
+ height : newHeight ,
141
+ } ;
142
+
143
+ // If the width has changed, calculate the iframe content scale.
144
+ if ( widthHasChanged ) {
145
+ calculateIframeContentScale ( ) ;
146
+ }
147
+
148
+ // If the height has changed, set the iframe height.
149
+ if ( heightHasChanged ) {
150
+ setIframeHeight ( calculateIframeHeight ( ) ) ;
151
+ }
152
+ } ) ;
153
+ } ) ;
154
+ refMap . current . previewContainerResizeObserver . observe ( refMap . current . previewContainer ) ;
155
+ } ;
156
+
157
+ // On unmount: Disconnect the resize observer.
158
+ useEffect ( ( ) => {
159
+ return ( ) => {
160
+ if ( refMap . current . previewContainerResizeObserver ) {
161
+ refMap . current . previewContainerResizeObserver . disconnect ( ) ;
162
+ }
163
+ } ;
164
+ } , [ ] ) ;
165
+
166
+ const iframeOnLoad = ( ) => {
167
+ dynamicallyControlIframeScale ( ) ;
168
+
169
+ const iframeDoc = refMap . current . iframeElement ?. contentWindow ?. document ;
68
170
if ( iframeDoc ) {
69
- // Preview margins and styles
171
+ /**
172
+ * The styles below add the spacing around the preview.
173
+ */
70
174
// <html> styles
71
- iframeDoc . documentElement . style . setProperty ( " padding" , " 40px 25px 110px" , " important" ) ;
72
- iframeDoc . documentElement . style . setProperty ( " overflow" , " overlay" , " important" ) ;
73
- iframeDoc . documentElement . style . setProperty ( " background-color" , " #ECEDEE" , " important" ) ;
175
+ iframeDoc . documentElement . style . setProperty ( ' padding' , ' 40px 25px 110px' , ' important' ) ;
176
+ iframeDoc . documentElement . style . setProperty ( ' overflow' , ' overlay' , ' important' ) ;
177
+ iframeDoc . documentElement . style . setProperty ( ' background-color' , ' #ECEDEE' , ' important' ) ;
74
178
// <body> styles
75
- iframeDoc . body . style . setProperty ( " border-radius" , " 10px" , " important" ) ;
76
- iframeDoc . body . style . setProperty ( " overflow" , " overlay" , " important" ) ;
77
- iframeDoc . body . style . setProperty ( " border" , " 1px solid #CBD5E1" , " important" ) ;
78
- iframeDoc . body . style . setProperty ( " box-shadow" , " 0 0 0px 2px #c5cbd4, 0 0 25px 7px rgba(64, 77, 96, 0.1)" , " important" ) ;
179
+ iframeDoc . body . style . setProperty ( ' border-radius' , ' 10px' , ' important' ) ;
180
+ iframeDoc . body . style . setProperty ( ' overflow' , ' overlay' , ' important' ) ;
181
+ iframeDoc . body . style . setProperty ( ' border' , ' 1px solid #CBD5E1' , ' important' ) ;
182
+ iframeDoc . body . style . setProperty ( ' box-shadow' , ' 0 0 0px 2px #c5cbd4, 0 0 25px 7px rgba(64, 77, 96, 0.1)' , ' important' ) ;
79
183
// End: Preview styles
80
184
}
81
185
@@ -86,9 +190,17 @@ const Preview = () => {
86
190
} ;
87
191
88
192
return (
89
- < div className = "nfd-onboarding-canvas-preview nfd-relative nfd-w-full" >
193
+ < div
194
+ className = "nfd-onboarding-canvas-preview nfd-relative nfd-w-full"
195
+ ref = { ( el ) => {
196
+ refMap . current . previewContainer = el ;
197
+ } }
198
+ >
90
199
< StatusOverlay />
91
200
{ preview && < Iframe
201
+ ref = { ( el ) => {
202
+ refMap . current . iframeElement = el ;
203
+ } }
92
204
title = { preview . slug }
93
205
id = { `nfd-onboarding-${ preview . slug } -selected` }
94
206
src = { preview . iframeSrc }
@@ -99,7 +211,7 @@ const Preview = () => {
99
211
onLoad = { iframeOnLoad }
100
212
tabIndex = "-1"
101
213
inert
102
- className = "nfd-cursor-default"
214
+ className = "nfd-cursor-default "
103
215
/> }
104
216
</ div >
105
217
) ;
0 commit comments