Skip to content

Commit

Permalink
feat: add API to opt-out of iframes
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Mar 24, 2024
1 parent 27f550b commit 03dd90b
Show file tree
Hide file tree
Showing 15 changed files with 145 additions and 94 deletions.
1 change: 1 addition & 0 deletions apps/demo/app/custom-ui/[...puckPath]/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export function Client({ path, isEdit }: { path: string; isEdit: boolean }) {
<Puck<UserConfig>
config={config}
data={data}
iframe={{ enabled: false }}
headerPath={path}
overrides={{
outline: ({ children }) => (
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/Preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const PuckPreview = ({
style?: CSSProperties;
}) => {
return (
<Puck {...puckProps}>
<Puck {...puckProps} iframe={{ enabled: false }}>
<PreviewFrame label={label} style={style}>
{children}
</PreviewFrame>
Expand Down
28 changes: 28 additions & 0 deletions apps/docs/pages/docs/api-reference/components/puck.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function Editor() {
| [`children`](#children) | `children: <Puck.Preview />` | ReactNode | - |
| [`headerPath`](#headerpath) | `headerPath: "/my-page"` | String | - |
| [`headerTitle`](#headertitle) | `headerTitle: "My Page"` | String | - |
| [`iframe`](#iframe) | `iframe: {}` | [IframeConfig](#iframe-params) | - |
| [`onChange()`](#onchangedata) | `onChange: (data) => {}` | Function | - |
| [`onPublish()`](#onpublishdata) | `onPublish: async (data) => {}` | Function | - |
| [`overrides`](#overrides) | `overrides: { header: () => <div /> }` | [Overrides](/docs/api-reference/overrides) | Experimental |
Expand Down Expand Up @@ -138,6 +139,33 @@ export function Editor() {
}
```

### `iframe`

Configure the iframe behaviour.

```tsx {4} copy
export function Editor() {
return (
<Puck
iframe={{ enabled: false }}
// ...
/>
);
}
```

#### iframe params

| Param | Example | Type | Status |
| --------------------- | ---------------- | ------- | ------ |
| [`enabled`](#enabled) | `enabled: false` | boolean | - |

##### `enabled`

Render the Puck preview within iframe. Defaults to `true`.

Disabling iframes will also disable [viewports](#viewports).

### `onChange(data)`

Callback that triggers when the user makes a change.
Expand Down
15 changes: 4 additions & 11 deletions packages/core/components/LayerTree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { dropZoneContext } from "../DropZone/context";
import { findZonesForArea } from "../../lib/find-zones-for-area";
import { getZoneId } from "../../lib/get-zone-id";
import { isChildOfZone } from "../../lib/is-child-of-zone";
import { useFrame } from "../../lib/use-frame";

const getClassName = getClassNameFactory("LayerTree", styles);
const getClassNameLayer = getClassNameFactory("Layer", styles);
Expand All @@ -33,6 +34,8 @@ export const LayerTree = ({
}) => {
const zones = data.zones || {};
const ctx = useContext(dropZoneContext);
const frame = useFrame();

return (
<>
{label && (
Expand Down Expand Up @@ -95,18 +98,8 @@ export const LayerTree = ({

const id = zoneContent[i].props.id;

const iframe = document.querySelector("#preview-iframe") as
| HTMLIFrameElement
| undefined;

if (!iframe?.contentDocument) {
throw new Error(
`Preview iframe could not be found when trying to scroll to item ${id}`
);
}

scrollIntoView(
iframe.contentDocument.querySelector(
frame?.querySelector(
`[data-rfd-drag-handle-draggable-id="draggable-${id}"]`
) as HTMLElement
);
Expand Down
15 changes: 9 additions & 6 deletions packages/core/components/Puck/components/Canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const getClassName = getClassNameFactory("PuckCanvas", styles);
const ZOOM_ON_CHANGE = true;

export const Canvas = () => {
const { status } = useAppContext();
const { status, iframe } = useAppContext();
const { dispatch, state, overrides, setUi, zoomConfig, setZoomConfig } =
useAppContext();
const { ui } = state;
Expand Down Expand Up @@ -102,7 +102,9 @@ export const Canvas = () => {

return (
<div
className={getClassName({ ready: status === "READY" })}
className={getClassName({
ready: status === "READY" || !iframe.enabled,
})}
onClick={() =>
dispatch({
type: "setUi",
Expand All @@ -111,7 +113,7 @@ export const Canvas = () => {
})
}
>
{ui.viewports.controlsVisible && (
{ui.viewports.controlsVisible && iframe.enabled && (
<div className={getClassName("controls")}>
<ViewportControls
autoZoom={zoomConfig.autoZoom}
Expand Down Expand Up @@ -144,16 +146,17 @@ export const Canvas = () => {
/>
</div>
)}
<div className={getClassName("frame")} ref={frameRef}>
<div className={getClassName("inner")} ref={frameRef}>
<div
className={getClassName("root")}
style={{
width: ui.viewports.current.width,
width: iframe.enabled ? ui.viewports.current.width : undefined,
height: zoomConfig.rootHeight,
transform: `scale(${zoomConfig.zoom})`,
transform: iframe.enabled ? `scale(${zoomConfig.zoom})` : undefined,
transition: showTransition
? "width 150ms ease-out, height 150ms ease-out, transform 150ms ease-out"
: "",
overflow: iframe.enabled ? undefined : "auto",
}}
>
<CustomPreview>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}
}

.PuckCanvas-frame {
.PuckCanvas-inner {
box-sizing: border-box;
display: flex;
height: 100%;
Expand Down
38 changes: 23 additions & 15 deletions packages/core/components/Puck/components/Preview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DropZone } from "../../../DropZone";
import { rootDroppableId } from "../../../../lib/root-droppable-id";
import { useCallback, useRef, useState } from "react";
import { useCallback, useRef } from "react";
import { useAppContext } from "../../context";
import AutoFrame from "@measured/auto-frame-component";
import styles from "./styles.module.css";
Expand All @@ -9,7 +9,7 @@ import { getClassNameFactory } from "../../../../lib";
const getClassName = getClassNameFactory("PuckPreview", styles);

export const Preview = ({ id = "puck-preview" }: { id?: string }) => {
const { config, dispatch, state, setStatus } = useAppContext();
const { config, dispatch, state, setStatus, iframe } = useAppContext();

const Page = useCallback(
(pageProps) =>
Expand All @@ -32,19 +32,27 @@ export const Preview = ({ id = "puck-preview" }: { id?: string }) => {
dispatch({ type: "setUi", ui: { ...state.ui, itemSelector: null } });
}}
>
<AutoFrame
id="preview-iframe"
className={getClassName("iframe")}
data-rfd-iframe
ref={ref}
onStylesLoaded={() => {
setStatus("READY");
}}
>
<Page dispatch={dispatch} state={state} {...rootProps}>
<DropZone zone={rootDroppableId} />
</Page>
</AutoFrame>
{iframe.enabled ? (
<AutoFrame
id="preview-frame"
className={getClassName("frame")}
data-rfd-iframe
ref={ref}
onStylesLoaded={() => {
setStatus("READY");
}}
>
<Page dispatch={dispatch} state={state} {...rootProps}>
<DropZone zone={rootDroppableId} />
</Page>
</AutoFrame>
) : (
<div id="preview-frame" className={getClassName("frame")}>
<Page dispatch={dispatch} state={state} {...rootProps}>
<DropZone zone={rootDroppableId} />
</Page>
</div>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
height: 100%;
}

.PuckPreview-iframe {
.PuckPreview-frame {
border: none;
height: 100%;
width: 100%;
Expand Down
3 changes: 3 additions & 0 deletions packages/core/components/Puck/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Overrides } from "../../types/Overrides";
import { PuckHistory } from "../../lib/use-puck-history";
import { defaultViewports } from "../ViewportControls/default-viewports";
import { Viewports } from "../../types/Viewports";
import { IframeConfig } from "../../types/IframeConfig";

export const defaultAppState: AppState = {
data: { content: [], root: { props: { title: "" } } },
Expand Down Expand Up @@ -58,6 +59,7 @@ type AppContext<
setZoomConfig: (zoomConfig: ZoomConfig) => void;
status: Status;
setStatus: (status: Status) => void;
iframe: IframeConfig;
};

const defaultContext: AppContext = {
Expand All @@ -78,6 +80,7 @@ const defaultContext: AppContext = {
setZoomConfig: () => null,
status: "LOADING",
setStatus: () => null,
iframe: {},
};

export const appContext = createContext<AppContext>(defaultContext);
Expand Down
52 changes: 30 additions & 22 deletions packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { Canvas } from "./components/Canvas";
import { defaultViewports } from "../ViewportControls/default-viewports";
import { Viewports } from "../../types/Viewports";
import { DragDropContext } from "../DragDropContext";
import { IframeConfig } from "../../types/IframeConfig";

const getClassName = getClassNameFactory("Puck", styles);

Expand All @@ -65,6 +66,9 @@ export function Puck<
headerTitle,
headerPath,
viewports = defaultViewports,
iframe = {
enabled: true,
},
}: {
children?: ReactNode;
config: UserConfig;
Expand All @@ -86,6 +90,7 @@ export function Puck<
headerTitle?: string;
headerPath?: string;
viewports?: Viewports;
iframe?: IframeConfig;
}) {
const historyStore = useHistoryStore();

Expand All @@ -110,29 +115,31 @@ export function Puck<

const closestViewport = viewportDifferences[0].key;

clientUiState = {
// Hide side bars on mobile
...(window.matchMedia("(min-width: 638px)").matches
? {}
: {
leftSideBarVisible: false,
rightSideBarVisible: false,
}),
viewports: {
...initial.viewports,

current: {
...initial.viewports.current,
height:
initialUi?.viewports?.current?.height ||
viewports[closestViewport].height ||
"auto",
width:
initialUi?.viewports?.current?.width ||
viewports[closestViewport].width,
if (iframe.enabled) {
clientUiState = {
// Hide side bars on mobile
...(window.matchMedia("(min-width: 638px)").matches
? {}
: {
leftSideBarVisible: false,
rightSideBarVisible: false,
}),
viewports: {
...initial.viewports,

current: {
...initial.viewports.current,
height:
initialUi?.viewports?.current?.height ||
viewports[closestViewport].height ||
"auto",
width:
initialUi?.viewports?.current?.width ||
viewports[closestViewport].width,
},
},
},
};
};
}
}

return {
Expand Down Expand Up @@ -351,6 +358,7 @@ export function Puck<
overrides: loadedOverrides,
history,
viewports,
iframe,
}}
>
<DragDropContext
Expand Down
11 changes: 11 additions & 0 deletions packages/core/lib/get-frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const getFrame = () => {
let frame = document.querySelector("#preview-frame") as
| HTMLElement
| undefined;

if (frame?.tagName === "IFRAME") {
frame = (frame as HTMLIFrameElement)!.contentDocument!.body;
}

return frame;
};
12 changes: 12 additions & 0 deletions packages/core/lib/use-frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useEffect, useState } from "react";
import { getFrame } from "./get-frame";

export const useFrame = () => {
const [el, setEl] = useState<Element>();

useEffect(() => {
setEl(getFrame());
}, []);

return el;
};
12 changes: 4 additions & 8 deletions packages/core/lib/use-placeholder-style.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { CSSProperties, useState } from "react";
import { DragStart, DragUpdate } from "@measured/dnd";
import { useFrame } from "./use-frame";

export const usePlaceholderStyle = () => {
const queryAttr = "data-rfd-drag-handle-draggable-id";

const [placeholderStyle, setPlaceholderStyle] = useState<CSSProperties>();
const frame = useFrame();

const onDragStartOrUpdate = (
draggedItem: DragStart & Partial<DragUpdate>
Expand All @@ -17,19 +19,13 @@ export const usePlaceholderStyle = () => {

const domQuery = `[${queryAttr}='${draggableId}']`;

const iframe = document.querySelector(`#preview-iframe`) as
| HTMLIFrameElement
| undefined;

const draggedDOM =
document.querySelector(domQuery) ||
iframe?.contentWindow?.document.querySelector(domQuery);
const draggedDOM = frame?.ownerDocument.querySelector(domQuery);

if (!draggedDOM) {
return;
}

const targetListElement = iframe?.contentWindow?.document.querySelector(
const targetListElement = frame?.ownerDocument.querySelector(
`[data-rfd-droppable-id='${droppableId}']`
);

Expand Down
3 changes: 3 additions & 0 deletions packages/core/types/IframeConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type IframeConfig = {
enabled?: boolean;
};
Loading

0 comments on commit 03dd90b

Please sign in to comment.