#3475759 Implement an overlay component that handles all iFrame interaction
Compare changes
docs/react-codebase/page-preview.md
0 → 100644
+ 85
− 0
A Redux slice called `uiSlice` is used to contain a number of state related variables that allow synchronizing the UI display across not only the multiple `<Viewport>` components, but also the "Layers" view in the left sidebar. So when hovering or selecting a component in the preview, the same component will also display as hovered in the other viewports and the "Layers" view. In turn hovering or selecting a component in the "Layers" view will also show in the preview viewports.
The `<IFrameSwapper>` is so named because it renders two `<iframe>` elements and swaps between them. This approach allows loading the new page into a hidden `<iframe>` and swapping it in only once it has finished loading. This prevents [flickering](https://www.drupal.org/project/experience_builder/issues/3469677), layout shifts, and/or any [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) issues.
The UI layer responds to [element resizing](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), [browser resizing](https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event), and [DOM mutations](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to ensure that each `<ComponentOverlay>` is precisely overlaid onto its corresponding component inside the `<iframe>`.
Allowing users to interact with the page displayed in the `<iframe>` is problematic for several reasons. The biggest issue initially faced was capturing various mouse and keyboard events happening inside the `<iframe>` and passing them up to the parent window to be handled by React. For example, if a user focuses on an element inside the `<iframe>` and presses a keyboard shortcut, the `keydown` event is fired inside the `<iframe>`. However, because our event handler is in the React app of the parent window, the keyboard shortcut wouldn't work!
Furthermore, we encountered [numerous](https://www.drupal.org/project/experience_builder/issues/3458535) [browser](https://www.drupal.org/project/experience_builder/issues/3466063) [quirks](https://www.drupal.org/project/experience_builder/issues/3475749) related to pinch, mousemove, and mousewheel events when the mouse cursor moves over an `<iframe>`.
For each component and slot, a transparent element is rendered and positioned (see 1. Dynamic positioning) over the top of the corresponding component inside the `<iframe>`. This allows the UI to render borders around components and slots, display the name of the component, and show interactive buttons (e.g., "Add component") without injecting markup into the `<iframe>`, which may cause styling or layout issues.
The overlaid components handle interactions like hover, click, drag, and right-click. This means we don't have to inject event listeners into the `<iframe>`. For instance, showing a border around a component when hovering over it becomes trivial, as we just add a class to the element and apply a border with CSS!
It was a [requirement](https://www.drupal.org/project/experience_builder/issues/3469672) that zooming the canvas should not also scale the XB UI. If a user zooms way out, we don't want the component's name in the UI to become illegibly small! To avoid these scaling issues, the `<ViewportOverlay>` uses a React portal to render into a `<div id="xbPreviewOverlay">` that exists above the element that scales when a user zooms the canvas.
Handling the sort/drop operation in the actual rendered markup was necessary because the [SortableJS library](https://sortablejs.github.io/Sortable) is not compatible with absolutely positioned elements (which our overlay components must be to ensure they are correctly positioned over their corresponding component).
It may well become necessary to allow users to interact with the page inside the `<iframe>` in the future. One approach to this might be to introduce a toggleable state that allows a user to switch between a "layout mode" for editing the layout and an "interactive mode" that will allow them to click inside the `<iframe>`.
Hopefully this approach to handling the quirks and challenges of previewing a page in an `<iframe>` provides a robust intuitive developer experience. This documentation aims to clarify the core concepts and functionality of the preview system, empowering you to make the most of its features. If you have any questions or feedback, feel free to reach out to our team!