Skip to content

Commit af5bd80

Browse files
feat(PrismicToolbar): add onPreviewUpdate and onPreviewEnd props (#249)
* feat: add onPreviewUpdate and onPreviewEnd callback props to PrismicToolbar Allows users to handle preview lifecycle events without manual useEffect and AbortController boilerplate. Closes #248 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: fix onPreviewUpdate JSDoc description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update TSDocs --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 67d00d1 commit af5bd80

4 files changed

Lines changed: 140 additions & 2 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
3+
import { type ReactNode, useState } from "react";
4+
import { PrismicToolbar } from "@prismicio/react";
5+
6+
export function PrismicToolbarPreviewEvents(props: {
7+
repositoryName: string;
8+
}): ReactNode {
9+
const [updateCount, setUpdateCount] = useState(0);
10+
const [endCount, setEndCount] = useState(0);
11+
const [updateRef, setUpdateRef] = useState<string>("");
12+
13+
return (
14+
<>
15+
<PrismicToolbar
16+
repositoryName={props.repositoryName}
17+
onPreviewUpdate={(event) => {
18+
setUpdateCount((c) => c + 1);
19+
setUpdateRef(event.detail.ref);
20+
}}
21+
onPreviewEnd={() => setEndCount((c) => c + 1)}
22+
/>
23+
<span data-testid="update-count">{updateCount}</span>
24+
<span data-testid="end-count">{endCount}</span>
25+
<span data-testid="update-ref">{updateRef}</span>
26+
</>
27+
);
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { ReactNode } from "react";
2+
3+
import { createClient } from "@/prismicio";
4+
import { PrismicToolbarPreviewEvents } from "./PrismicToolbarPreviewEvents";
5+
6+
export default async function Page(): Promise<ReactNode> {
7+
const client = await createClient();
8+
9+
return (
10+
<PrismicToolbarPreviewEvents repositoryName={client.repositoryName} />
11+
);
12+
}

src/PrismicToolbar.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import { type FC, useEffect } from "react";
43
import { getToolbarSrc } from "@prismicio/client";
4+
import { type FC, useEffect, useRef } from "react";
55

66
/** Props for `<PrismicToolbar>`. */
77
export type PrismicToolbarProps = {
@@ -10,6 +10,26 @@ export type PrismicToolbarProps = {
1010
* repository URL is `my-repo.prismic.io`.
1111
*/
1212
repositoryName: string;
13+
14+
/**
15+
* Called when the Prismic toolbar triggers a preview update. This happens
16+
* when the previewed content changes.
17+
*
18+
* The new ref can be read from `event.detail.ref`.
19+
*
20+
* The default page reload behavior can be cancelled with
21+
* `event.preventDefault()`.
22+
*/
23+
onPreviewUpdate?: (event: CustomEvent<{ ref: string }>) => void;
24+
25+
/**
26+
* Called when the Prismic toolbar triggers a preview end. This happens when
27+
* a preview session is closed.
28+
*
29+
* The default page reload behavior can be cancelled with
30+
* `event.preventDefault()`.
31+
*/
32+
onPreviewEnd?: (event: CustomEvent<null>) => void;
1333
};
1434

1535
/**
@@ -24,10 +44,16 @@ export type PrismicToolbarProps = {
2444
* @see Learn how to set up preview functionality and the toolbar's role in preview sessions: {@link https://prismic.io/docs/previews}
2545
*/
2646
export const PrismicToolbar: FC<PrismicToolbarProps> = (props) => {
27-
const { repositoryName } = props;
47+
const { repositoryName, onPreviewUpdate, onPreviewEnd } = props;
2848

2949
const src = getToolbarSrc(repositoryName);
3050

51+
const onPreviewUpdateRef = useRef(onPreviewUpdate);
52+
onPreviewUpdateRef.current = onPreviewUpdate;
53+
54+
const onPreviewEndRef = useRef(onPreviewEnd);
55+
onPreviewEndRef.current = onPreviewEnd;
56+
3157
useEffect(() => {
3258
const existingScript = document.querySelector(`script[src="${src}"]`);
3359

@@ -48,5 +74,24 @@ export const PrismicToolbar: FC<PrismicToolbarProps> = (props) => {
4874
}
4975
}, [repositoryName, src]);
5076

77+
useEffect(() => {
78+
const controller = new AbortController();
79+
80+
window.addEventListener(
81+
"prismicPreviewUpdate",
82+
(event) =>
83+
onPreviewUpdateRef.current?.(event as CustomEvent<{ ref: string }>),
84+
{ signal: controller.signal },
85+
);
86+
87+
window.addEventListener(
88+
"prismicPreviewEnd",
89+
(event) => onPreviewEndRef.current?.(event as CustomEvent<null>),
90+
{ signal: controller.signal },
91+
);
92+
93+
return () => controller.abort();
94+
}, []);
95+
5196
return null;
5297
};

tests/PrismicToolbar.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,56 @@ test("includes the repository name in the script element", async ({
1616
appPage.repository.domain,
1717
);
1818
});
19+
20+
test("calls onPreviewUpdate when prismicPreviewUpdate is dispatched", async ({
21+
page,
22+
}) => {
23+
await page.goto("/PrismicToolbar/preview-events");
24+
25+
const updateCount = page.getByTestId("update-count");
26+
const updateRef = page.getByTestId("update-ref");
27+
await expect(updateCount).toHaveText("0");
28+
29+
await page.evaluate(() => {
30+
window.dispatchEvent(
31+
new CustomEvent("prismicPreviewUpdate", {
32+
detail: { ref: "test-ref-1" },
33+
}),
34+
);
35+
});
36+
await expect(updateCount).toHaveText("1");
37+
await expect(updateRef).toHaveText("test-ref-1");
38+
39+
await page.evaluate(() => {
40+
window.dispatchEvent(
41+
new CustomEvent("prismicPreviewUpdate", {
42+
detail: { ref: "test-ref-2" },
43+
}),
44+
);
45+
});
46+
await expect(updateCount).toHaveText("2");
47+
await expect(updateRef).toHaveText("test-ref-2");
48+
});
49+
50+
test("calls onPreviewEnd when prismicPreviewEnd is dispatched", async ({
51+
page,
52+
}) => {
53+
await page.goto("/PrismicToolbar/preview-events");
54+
55+
const endCount = page.getByTestId("end-count");
56+
await expect(endCount).toHaveText("0");
57+
58+
await page.evaluate(() => {
59+
window.dispatchEvent(
60+
new CustomEvent("prismicPreviewEnd", { detail: null }),
61+
);
62+
});
63+
await expect(endCount).toHaveText("1");
64+
65+
await page.evaluate(() => {
66+
window.dispatchEvent(
67+
new CustomEvent("prismicPreviewEnd", { detail: null }),
68+
);
69+
});
70+
await expect(endCount).toHaveText("2");
71+
});

0 commit comments

Comments
 (0)