sanity-plugin-iframe-pane 3.1.5 → 3.1.6-corel.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ yarn add sanity-plugin-iframe-pane
25
25
 
26
26
  This is designed to be used as a [Component inside of a View](https://www.sanity.io/docs/structure-builder-reference#c0c8284844b7).
27
27
 
28
- The simplest way to configure views is by customizing the `defaultDocumentNode` setting in the `deskTool()` plugin.
28
+ The simplest way to configure views is by customizing the `defaultDocumentNode` setting in the `structureTool()` plugin.
29
29
 
30
30
  ```ts
31
31
  // ./sanity.config.ts
@@ -33,7 +33,7 @@ The simplest way to configure views is by customizing the `defaultDocumentNode`
33
33
  export default defineConfig({
34
34
  // ...other config settings
35
35
  plugins: [
36
- deskTool({
36
+ structureTool({
37
37
  defaultDocumentNode,
38
38
  structure, // not required
39
39
  }),
@@ -47,14 +47,14 @@ A basic example of a custom `defaultDocumentNode` function, to only show the Ifr
47
47
  ```ts
48
48
  // ./src/defaultDocumentNode.ts
49
49
 
50
- import {DefaultDocumentNodeResolver} from 'sanity/desk'
51
- import {Iframe} from 'sanity-plugin-iframe-pane'
52
- import {SanityDocument} from 'sanity'
50
+ import {type DefaultDocumentNodeResolver} from 'sanity/structure'
51
+ import {Iframe, UrlResolver} from 'sanity-plugin-iframe-pane'
52
+ import {type SanityDocument} from 'sanity'
53
53
 
54
- // Customise this function to show the correct URL based on the current document
55
- function getPreviewUrl(doc: SanityDocument) {
54
+ // Customise this function to show the correct URL based on the current document and the current studio perspective
55
+ const getPreviewUrl: UrlResolver = (doc, perspective) => {
56
56
  return doc?.slug?.current
57
- ? `${window.location.host}/${doc.slug.current}`
57
+ ? `${window.location.host}/${doc.slug.current}?perspective=${perspective.perspectiveStack}`
58
58
  : `${window.location.host}`
59
59
  }
60
60
 
@@ -68,7 +68,7 @@ export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}
68
68
  S.view
69
69
  .component(Iframe)
70
70
  .options({
71
- url: (doc: SanityDocument) => getPreviewUrl(doc),
71
+ url: getPreviewUrl,
72
72
  })
73
73
  .title('Preview'),
74
74
  ])
@@ -82,7 +82,7 @@ export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}
82
82
 
83
83
  ```js
84
84
  // Required: Accepts an async function
85
- url: (doc) => resolveProductionUrl(doc),
85
+ url: (doc, {perspectiveStack, selectedPerspectiveName}) => resolveProductionUrl(doc),
86
86
 
87
87
  // OR a string
88
88
  url: `https://sanity.io`,
@@ -90,7 +90,7 @@ url: `https://sanity.io`,
90
90
  // OR a configuration for usage with `@sanity/preview-url-secret` and Next.js Draft Mode
91
91
  url: {
92
92
  origin: 'https://sanity.io' // or 'same-origin' if the app and studio are on the same origin
93
- preview: (document) => document?.slug?.current ? `/posts/${document.slug.current}` : new Error('Missing slug'),
93
+ preview: (document, {perspectiveStack, selectedPerspective}) => document?.slug?.current ? `/posts/${document.slug.current}` : new Error('Missing slug'),
94
94
  draftMode: '/api/draft' // the route you enable draft mode, see: https://github.com/sanity-io/visual-editing/tree/main/packages/preview-url-secret#sanitypreview-url-secret
95
95
  },
96
96
 
package/lib/index.cjs CHANGED
@@ -91,12 +91,17 @@ function Toolbar(props) {
91
91
  padding: [2],
92
92
  "aria-label": "Copy URL",
93
93
  onClick: () => {
94
- var _a;
95
- (_a = input == null ? void 0 : input.current) != null && _a.value && (copy(input.current.value), pushToast({
96
- closable: !0,
97
- status: "success",
98
- title: "The URL is copied to the clipboard"
99
- }));
94
+ input?.current?.value && copy(input.current.value).then((copied) => {
95
+ pushToast(copied ? {
96
+ closable: !0,
97
+ status: "success",
98
+ title: "The URL is copied to the clipboard"
99
+ } : {
100
+ closable: !0,
101
+ status: "error",
102
+ title: "Failed to copy the URL to the clipboard"
103
+ });
104
+ });
100
105
  }
101
106
  }
102
107
  )
@@ -128,7 +133,7 @@ function Toolbar(props) {
128
133
  ] }) })
129
134
  ] });
130
135
  }
131
- const MotionFlex = framerMotion.motion(ui.Flex);
136
+ const MotionFlex = framerMotion.motion(ui.Flex), MotionIFrame = framerMotion.motion("iframe");
132
137
  function Iframe(props) {
133
138
  const { document, options } = props, draft = document.draft || document.published || document.displayed, { defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = !0, key } = options, urlRef = react.useRef(options.url), [draftSnapshot, setDraftSnapshot] = react.useState(() => ({ key, draft }));
134
139
  react.useEffect(() => {
@@ -136,7 +141,13 @@ function Iframe(props) {
136
141
  }, [options.url]), react.useEffect(() => {
137
142
  JSON.stringify({ key, draft }) !== JSON.stringify(draftSnapshot) && startTransition(() => setDraftSnapshot({ key, draft }));
138
143
  }, [draft, draftSnapshot, key]);
139
- const currentUser = sanity.useCurrentUser(), client = sanity.useClient({ apiVersion: "2023-10-16" }), [expiresAt, setExpiresAt] = react.useState(), previewSecretRef = react.useRef(), [isResolvingUrl, startTransition] = react.useTransition(), url = react.useCallback(
144
+ const currentUser = sanity.useCurrentUser(), client = sanity.useClient({ apiVersion: "2023-10-16" }), [expiresAt, setExpiresAt] = react.useState(), previewSecretRef = react.useRef(void 0), [isResolvingUrl, startTransition] = react.useTransition(), { perspectiveStack, selectedPerspectiveName } = sanity.usePerspective(), perspective = react.useMemo(
145
+ () => ({
146
+ perspectiveStack,
147
+ selectedPerspectiveName
148
+ }),
149
+ [perspectiveStack, selectedPerspectiveName]
150
+ ), url = react.useCallback(
140
151
  // eslint-disable-next-line @typescript-eslint/no-shadow
141
152
  async (draft2) => {
142
153
  if (typeof location > "u")
@@ -145,11 +156,11 @@ function Iframe(props) {
145
156
  if (typeof urlProp == "string")
146
157
  return new URL(urlProp, location.origin);
147
158
  if (typeof urlProp == "function") {
148
- const url2 = await urlProp(draft2);
159
+ const url2 = await urlProp(draft2, perspective);
149
160
  return typeof url2 == "string" ? new URL(url2, location.origin) : url2;
150
161
  }
151
162
  if (typeof urlProp == "object") {
152
- const preview = typeof urlProp.preview == "function" ? await urlProp.preview(draft2) : urlProp.preview;
163
+ const preview = typeof urlProp.preview == "function" ? await urlProp.preview(draft2, perspective) : urlProp.preview;
153
164
  if (typeof preview != "string")
154
165
  return preview;
155
166
  if (!previewSecretRef.current) {
@@ -157,7 +168,7 @@ function Iframe(props) {
157
168
  client,
158
169
  "sanity-plugin-iframe-pane",
159
170
  location.href,
160
- currentUser == null ? void 0 : currentUser.id
171
+ currentUser?.id
161
172
  );
162
173
  previewSecretRef.current = secret, startTransition(() => setExpiresAt(expiresAt2.getTime()));
163
174
  }
@@ -175,7 +186,7 @@ function Iframe(props) {
175
186
  return new URL(url2, location.origin);
176
187
  }
177
188
  },
178
- [client, currentUser == null ? void 0 : currentUser.id]
189
+ [client, currentUser?.id, perspective]
179
190
  );
180
191
  return react.useEffect(() => {
181
192
  if (expiresAt) {
@@ -195,16 +206,16 @@ function Iframe(props) {
195
206
  url,
196
207
  isResolvingUrl,
197
208
  attributes,
209
+ perspective,
198
210
  defaultSize,
199
211
  reload,
200
212
  showDisplayUrl,
201
- userId: currentUser == null ? void 0 : currentUser.id
213
+ userId: currentUser?.id
202
214
  },
203
- draftSnapshot.key
215
+ `${draftSnapshot.key}-${selectedPerspectiveName || "draft"}`
204
216
  ) });
205
217
  }
206
218
  const IframeInner = react.memo(function(props) {
207
- var _a;
208
219
  const {
209
220
  isResolvingUrl,
210
221
  defaultSize = DEFAULT_SIZE,
@@ -214,20 +225,23 @@ const IframeInner = react.memo(function(props) {
214
225
  draftSnapshot,
215
226
  userId,
216
227
  expiresAt,
228
+ perspective: { selectedPerspectiveName, perspectiveStack },
217
229
  _key
218
- } = props, [iframeSize, setIframeSize] = react.useState((_a = sizes) != null && _a[defaultSize] ? defaultSize : DEFAULT_SIZE), prefersReducedMotion = ui.usePrefersReducedMotion(), url = suspendReact.suspend(
230
+ } = props, [iframeSize, setIframeSize] = react.useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE), prefersReducedMotion = ui.usePrefersReducedMotion(), url = suspendReact.suspend(
219
231
  () => props.url(draftSnapshot),
220
232
  [
221
233
  // Cache based on a few specific conditions
222
234
  "sanity-plugin-iframe-pane",
223
235
  draftSnapshot,
236
+ selectedPerspectiveName,
237
+ perspectiveStack,
224
238
  userId,
225
239
  expiresAt,
226
240
  _key,
227
241
  resolveUUID
228
242
  ]
229
243
  ), [loading, setLoading] = react.useState(!0), [_reloading, setReloading] = react.useState(!1), reloading = _reloading || isResolvingUrl, iframe = react.useRef(null), handleReload = react.useCallback(() => {
230
- iframe != null && iframe.current && (iframe.current.src = iframe.current.src, setReloading(!0));
244
+ iframe?.current && (iframe.current.src = iframe.current.src, setReloading(!0));
231
245
  }, []);
232
246
  return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.MotionConfig, { transition: prefersReducedMotion ? { duration: 0 } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { direction: "column", style: { height: "100%" }, children: [
233
247
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -238,7 +252,7 @@ const IframeInner = react.memo(function(props) {
238
252
  reloading,
239
253
  setIframeSize,
240
254
  showUrl: showDisplayUrl,
241
- reloadButton: !!(reload != null && reload.button),
255
+ reloadButton: !!reload?.button,
242
256
  handleReload
243
257
  }
244
258
  ),
@@ -276,7 +290,7 @@ const IframeInner = react.memo(function(props) {
276
290
  }
277
291
  ) }),
278
292
  url && /* @__PURE__ */ jsxRuntime.jsx(
279
- framerMotion.motion.iframe,
293
+ MotionIFrame,
280
294
  {
281
295
  ref: iframe,
282
296
  title: "preview",
package/lib/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/DisplayUrl.tsx","../src/Toolbar.tsx","../src/Iframe.tsx"],"sourcesContent":["import {getRedirectTo} from '@sanity/preview-url-secret/get-redirect-to'\nimport {Text} from '@sanity/ui'\nimport React, {useMemo} from 'react'\n\nexport function DisplayUrl(props: {url: URL}) {\n const truncatedUrl = useMemo(() => {\n const url = getRedirectTo(props.url)\n return `${url.origin === location.origin ? '' : url.origin}${url.pathname}${url.search}`\n }, [props.url])\n\n return (\n <Text size={0} textOverflow=\"ellipsis\">\n {truncatedUrl}\n </Text>\n )\n}\n","import {CopyIcon, LaunchIcon, MobileDeviceIcon, RefreshIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Text, Tooltip, useToast} from '@sanity/ui'\nimport React, {useRef} from 'react'\nimport {useCopyToClipboard} from 'usehooks-ts'\n\nimport {DisplayUrl} from './DisplayUrl'\nimport {IframeSizeKey, type SizeProps} from './types'\n\nexport const sizes: SizeProps = {\n desktop: {\n width: '100%',\n height: '100%',\n },\n mobile: {\n width: 414,\n height: 746,\n },\n}\n\nexport const DEFAULT_SIZE = `desktop`\n\nexport interface ToolbarProps {\n url: URL | Error | undefined\n iframeSize: IframeSizeKey\n setIframeSize: (size: IframeSizeKey) => void\n showUrl: boolean\n reloading: boolean\n reloadButton: boolean\n handleReload: () => void\n}\nexport function Toolbar(props: ToolbarProps) {\n const {url, iframeSize, setIframeSize, reloading, showUrl, reloadButton, handleReload} = props\n const validUrl = url instanceof URL\n\n const input = useRef<HTMLTextAreaElement>(null)\n const {push: pushToast} = useToast()\n const [, copy] = useCopyToClipboard()\n\n return (\n <>\n <textarea\n style={{position: `absolute`, pointerEvents: `none`, opacity: 0}}\n ref={input}\n value={validUrl ? url.toString() : ''}\n readOnly\n tabIndex={-1}\n />\n <Card padding={2} borderBottom>\n <Flex align=\"center\" gap={2}>\n <Flex align=\"center\" gap={1}>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {iframeSize === 'mobile' ? 'Exit mobile preview' : 'Preview mobile viewport'}\n </Text>\n }\n padding={2}\n placement=\"bottom-start\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n padding={2}\n mode={iframeSize === 'mobile' ? 'default' : 'ghost'}\n icon={MobileDeviceIcon}\n onClick={() => setIframeSize(iframeSize === 'mobile' ? 'desktop' : 'mobile')}\n />\n </Tooltip>\n </Flex>\n <Box flex={1}>{showUrl && validUrl && <DisplayUrl url={url} />}</Box>\n <Flex align=\"center\" gap={1}>\n {reloadButton ? (\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {reloading ? 'Reloading…' : 'Reload'}\n </Text>\n }\n padding={2}\n >\n <Button\n disabled={!validUrl}\n mode=\"bleed\"\n fontSize={[1]}\n padding={2}\n icon={RefreshIcon}\n loading={reloading}\n aria-label=\"Reload\"\n onClick={() => handleReload()}\n />\n </Tooltip>\n ) : null}\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Copy URL\n </Text>\n }\n padding={2}\n >\n <Button\n mode=\"bleed\"\n disabled={!validUrl}\n fontSize={[1]}\n icon={CopyIcon}\n padding={[2]}\n aria-label=\"Copy URL\"\n onClick={() => {\n if (!input?.current?.value) return\n\n copy(input.current.value)\n pushToast({\n closable: true,\n status: 'success',\n title: 'The URL is copied to the clipboard',\n })\n }}\n />\n </Tooltip>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Open URL in a new tab\n </Text>\n }\n padding={2}\n placement=\"bottom-end\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n icon={LaunchIcon}\n mode=\"ghost\"\n paddingY={[2]}\n text=\"Open\"\n aria-label=\"Open URL in a new tab\"\n onClick={validUrl ? () => window.open(url.toString()) : undefined}\n />\n </Tooltip>\n </Flex>\n </Flex>\n </Card>\n </>\n )\n}\n","import {WarningOutlineIcon} from '@sanity/icons'\nimport {createPreviewSecret} from '@sanity/preview-url-secret/create-secret'\nimport {definePreviewUrl} from '@sanity/preview-url-secret/define-preview-url'\nimport {Box, Card, Container, Flex, Spinner, Stack, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {AnimatePresence, motion, MotionConfig} from 'framer-motion'\nimport React, {\n forwardRef,\n memo,\n Suspense,\n useCallback,\n useEffect,\n useRef,\n useState,\n useTransition,\n} from 'react'\nimport {HTMLAttributeReferrerPolicy} from 'react'\nimport {SanityDocument, useClient, useCurrentUser} from 'sanity'\nimport {suspend} from 'suspend-react'\n\nimport {DEFAULT_SIZE, sizes, Toolbar} from './Toolbar'\nimport {IframeSizeKey} from './types'\n\nexport type UrlResolver = (\n document: SanityDocument | null,\n) => string | Error | undefined | Promise<string | Error | undefined>\n\nexport type {IframeSizeKey}\n\nexport interface IframeOptions {\n /**\n * If you have multiple iframe instances side-by-side you need to give each a unique key.\n */\n key?: string\n url:\n | string\n | UrlResolver\n | {\n /**\n * The URL origin of where the preview is hosted, for example `https://example.com`.\n * If it's an embedded Studio then set it to `'same-origin'`.\n */\n origin: 'same-origin' | string\n /**\n * The route to redirect to after enabling Draft Mode.\n * If you don't have enough data to build the URL, return an `Error` instance to show an error message.\n * @example `return new Error('Missing slug')`\n * To prolong the loading state, return `undefined`\n */\n preview: string | UrlResolver\n /**\n * The route that enables Draft Mode\n * @example '/api/draft'\n */\n draftMode: string\n }\n defaultSize?: IframeSizeKey\n showDisplayUrl?: boolean\n reload?: {\n button?: boolean\n }\n attributes?: Partial<{\n allow: string\n referrerPolicy: HTMLAttributeReferrerPolicy | undefined\n sandbox: string\n onLoad: () => void\n }>\n}\n\nconst MotionFlex = motion(Flex)\n\nexport interface IframeProps {\n document: {\n displayed: SanityDocument\n draft: SanityDocument | null\n published: SanityDocument | null\n }\n options: IframeOptions\n}\n\nexport function Iframe(props: IframeProps) {\n const {document, options} = props\n const draft = document.draft || document.published || document.displayed\n\n const {defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = true, key} = options\n\n const urlRef = useRef(options.url)\n const [draftSnapshot, setDraftSnapshot] = useState(() => ({key, draft}))\n useEffect(() => {\n urlRef.current = options.url\n }, [options.url])\n useEffect(() => {\n if (JSON.stringify({key, draft}) !== JSON.stringify(draftSnapshot)) {\n startTransition(() => setDraftSnapshot({key, draft}))\n }\n }, [draft, draftSnapshot, key])\n const currentUser = useCurrentUser()\n const client = useClient({apiVersion: '2023-10-16'})\n const [expiresAt, setExpiresAt] = useState<number | undefined>()\n const previewSecretRef = useRef<string | undefined>()\n const [isResolvingUrl, startTransition] = useTransition()\n const url = useCallback(\n // eslint-disable-next-line @typescript-eslint/no-shadow\n async (draft: SanityDocument | null) => {\n if (typeof location === 'undefined') {\n return undefined\n }\n const urlProp = urlRef.current\n if (typeof urlProp === 'string') {\n return new URL(urlProp, location.origin)\n }\n if (typeof urlProp === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await urlProp(draft)\n return typeof url === 'string' ? new URL(url, location.origin) : url\n }\n if (typeof urlProp === 'object') {\n const preview =\n typeof urlProp.preview === 'function' ? await urlProp.preview(draft) : urlProp.preview\n if (typeof preview !== 'string') {\n return preview\n }\n if (!previewSecretRef.current) {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const {secret, expiresAt} = await createPreviewSecret(\n client,\n 'sanity-plugin-iframe-pane',\n location.href,\n currentUser?.id,\n )\n previewSecretRef.current = secret\n startTransition(() => setExpiresAt(expiresAt.getTime()))\n }\n\n const resolvePreviewUrl = definePreviewUrl({\n origin: urlProp.origin === 'same-origin' ? location.origin : urlProp.origin,\n preview,\n draftMode: {\n enable: urlProp.draftMode,\n },\n })\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await resolvePreviewUrl({\n client,\n previewUrlSecret: previewSecretRef.current,\n previewSearchParam: null,\n })\n return new URL(url, location.origin)\n }\n return undefined\n },\n [client, currentUser?.id],\n )\n useEffect(() => {\n if (expiresAt) {\n const timeout = setTimeout(\n () => {\n startTransition(() => setExpiresAt(undefined))\n previewSecretRef.current = undefined\n },\n Math.max(0, expiresAt - Date.now()),\n )\n return () => clearTimeout(timeout)\n }\n return undefined\n }, [expiresAt])\n\n return (\n <Suspense fallback={<Loading iframeSize=\"desktop\" />}>\n <IframeInner\n key={draftSnapshot.key}\n _key={draftSnapshot.key}\n draftSnapshot={draftSnapshot.draft}\n url={url}\n isResolvingUrl={isResolvingUrl}\n attributes={attributes}\n defaultSize={defaultSize}\n reload={reload}\n showDisplayUrl={showDisplayUrl}\n userId={currentUser?.id}\n />\n </Suspense>\n )\n}\n\nexport interface IframeInnerProps extends Omit<IframeOptions, 'url'> {\n url: (draftSnapshot: SanityDocument | null) => Promise<URL | Error | undefined>\n isResolvingUrl: boolean\n draftSnapshot: SanityDocument | null\n userId?: string\n expiresAt?: number\n _key?: string\n}\nconst IframeInner = memo(function IframeInner(props: IframeInnerProps) {\n const {\n isResolvingUrl,\n defaultSize = DEFAULT_SIZE,\n reload,\n attributes = {},\n showDisplayUrl = true,\n draftSnapshot,\n userId,\n expiresAt,\n _key,\n } = props\n const [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE)\n\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const url = suspend(\n () => props.url(draftSnapshot),\n [\n // Cache based on a few specific conditions\n 'sanity-plugin-iframe-pane',\n draftSnapshot,\n userId,\n expiresAt,\n _key,\n resolveUUID,\n ],\n )\n\n const [loading, setLoading] = useState(true)\n const [_reloading, setReloading] = useState(false)\n const reloading = _reloading || isResolvingUrl\n\n const iframe = useRef<HTMLIFrameElement>(null)\n\n const handleReload = useCallback(() => {\n if (!iframe?.current) {\n return\n }\n\n // Funky way to reload an iframe without CORS issues\n // eslint-disable-next-line no-self-assign\n iframe.current.src = iframe.current.src\n\n setReloading(true)\n }, [])\n\n return (\n <MotionConfig transition={prefersReducedMotion ? {duration: 0} : undefined}>\n <Flex direction=\"column\" style={{height: `100%`}}>\n <Toolbar\n url={url}\n iframeSize={iframeSize}\n reloading={reloading}\n setIframeSize={setIframeSize}\n showUrl={showDisplayUrl}\n reloadButton={!!reload?.button}\n handleReload={handleReload}\n />\n {url instanceof Error ? (\n <ErrorCard error={url} />\n ) : (\n <Card tone=\"transparent\" style={{height: `100%`}}>\n <Frame\n ref={iframe}\n loading={loading}\n reloading={reloading}\n iframeSize={iframeSize}\n setReloading={setReloading}\n setLoading={setLoading}\n url={url}\n attributes={attributes}\n />\n </Card>\n )}\n </Flex>\n </MotionConfig>\n )\n})\n\ninterface FrameProps extends Required<Pick<IframeOptions, 'attributes'>> {\n loading: boolean\n reloading: boolean\n setLoading: (loading: boolean) => void\n setReloading: (reloading: boolean) => void\n iframeSize: IframeSizeKey\n url: URL | undefined\n}\nconst Frame = forwardRef(function Frame(\n props: FrameProps,\n iframe: React.ForwardedRef<HTMLIFrameElement>,\n) {\n const {loading, setLoading, iframeSize, attributes, reloading, url, setReloading} = props\n\n function handleIframeLoad() {\n setLoading(false)\n setReloading(false)\n // Run onLoad from attributes\n if (attributes.onLoad && typeof attributes.onLoad === 'function') {\n attributes.onLoad()\n }\n }\n\n return (\n <Flex align=\"center\" justify=\"center\" style={{height: `100%`, position: `relative`}}>\n <AnimatePresence>\n {!url ||\n (loading && (\n <MotionFlex\n initial=\"initial\"\n animate=\"animate\"\n exit=\"exit\"\n variants={spinnerVariants}\n justify=\"center\"\n align=\"center\"\n style={{inset: `0`, position: `absolute`}}\n >\n <Loading iframeSize={iframeSize} />\n </MotionFlex>\n ))}\n </AnimatePresence>\n {url && (\n <motion.iframe\n ref={iframe}\n title=\"preview\"\n frameBorder=\"0\"\n style={{maxHeight: '100%'}}\n src={url.toString()}\n initial={['background', iframeSize]}\n variants={iframeVariants}\n animate={[\n loading ? 'background' : 'active',\n reloading ? 'reloading' : 'idle',\n iframeSize,\n ]}\n {...attributes}\n onLoad={handleIframeLoad}\n />\n )}\n </Flex>\n )\n})\n\nconst spinnerVariants = {\n initial: {opacity: 1},\n animate: {opacity: [0, 0, 1]},\n exit: {opacity: [1, 0, 0]},\n}\n\nconst iframeVariants = {\n ...sizes,\n desktop: {\n ...sizes.desktop,\n boxShadow: '0 0 0 0px var(--card-shadow-outline-color)',\n },\n mobile: {\n ...sizes.mobile,\n boxShadow: '0 0 0 1px var(--card-shadow-outline-color)',\n },\n background: {\n opacity: 0,\n scale: 1,\n },\n idle: {\n scale: 1,\n },\n reloading: {\n scale: [1, 1, 1, 0.98],\n },\n active: {\n opacity: [0, 0, 1],\n scale: 1,\n },\n}\n\nfunction Loading({iframeSize}: {iframeSize: IframeSizeKey}) {\n return (\n <Flex style={{...sizes[iframeSize]}} justify=\"center\" align=\"center\" direction=\"column\" gap={4}>\n <Spinner muted />\n <Text muted size={1}>\n Loading…\n </Text>\n </Flex>\n )\n}\n\nexport function ErrorCard({error}: {error: Error}) {\n return (\n <Card height=\"fill\">\n <Flex align=\"center\" height=\"fill\" justify=\"center\" padding={4} sizing=\"border\">\n <Container width={0}>\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Flex>\n <Box>\n <Text size={1}>\n <WarningOutlineIcon />\n </Text>\n </Box>\n <Stack flex={1} marginLeft={3} space={3}>\n <Text as=\"h1\" size={1} weight=\"bold\">\n {error.name}\n </Text>\n <Text as=\"p\" muted size={1}>\n {error.message}\n </Text>\n </Stack>\n </Flex>\n </Card>\n </Container>\n </Flex>\n </Card>\n )\n}\n\n// https://github.com/pmndrs/suspend-react?tab=readme-ov-file#making-cache-keys-unique\nconst resolveUUID = Symbol()\n"],"names":["useMemo","getRedirectTo","Text","useRef","useToast","useCopyToClipboard","jsxs","Fragment","jsx","Card","Flex","Tooltip","Button","MobileDeviceIcon","Box","RefreshIcon","CopyIcon","LaunchIcon","motion","useState","useEffect","useCurrentUser","useClient","useTransition","useCallback","draft","url","expiresAt","createPreviewSecret","definePreviewUrl","Suspense","memo","usePrefersReducedMotion","suspend","MotionConfig","forwardRef","AnimatePresence","Spinner","Container","WarningOutlineIcon","Stack"],"mappings":";;;AAIO,SAAS,WAAW,OAAmB;AACtC,QAAA,eAAeA,MAAAA,QAAQ,MAAM;AAC3B,UAAA,MAAMC,cAAAA,cAAc,MAAM,GAAG;AACnC,WAAO,GAAG,IAAI,WAAW,SAAS,SAAS,KAAK,IAAI,MAAM,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM;AAAA,EAAA,GACrF,CAAC,MAAM,GAAG,CAAC;AAEd,wCACGC,SAAK,EAAA,MAAM,GAAG,cAAa,YACzB,UACH,aAAA,CAAA;AAEJ;ACPO,MAAM,QAAmB;AAAA,EAC9B,SAAS;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF,GAEa,eAAe;AAWrB,SAAS,QAAQ,OAAqB;AACrC,QAAA,EAAC,KAAK,YAAY,eAAe,WAAW,SAAS,cAAc,aAAY,IAAI,OACnF,WAAW,eAAe,KAE1B,QAAQC,MAAA,OAA4B,IAAI,GACxC,EAAC,MAAM,cAAaC,GAAAA,YACpB,CAAA,EAAG,IAAI,IAAIC,WAAmB,mBAAA;AAEpC,SAEIC,2BAAA,KAAAC,qBAAA,EAAA,UAAA;AAAA,IAAAC,2BAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO,EAAC,UAAU,YAAY,eAAe,QAAQ,SAAS,EAAC;AAAA,QAC/D,KAAK;AAAA,QACL,OAAO,WAAW,IAAI,SAAa,IAAA;AAAA,QACnC,UAAQ;AAAA,QACR,UAAU;AAAA,MAAA;AAAA,IACZ;AAAA,IACAA,2BAAA,IAACC,GAAK,MAAA,EAAA,SAAS,GAAG,cAAY,IAC5B,UAAAH,2BAAAA,KAACI,GAAAA,MAAK,EAAA,OAAM,UAAS,KAAK,GACxB,UAAA;AAAA,MAAAF,2BAAA,IAACE,GAAK,MAAA,EAAA,OAAM,UAAS,KAAK,GACxB,UAAAF,2BAAA;AAAA,QAACG,GAAA;AAAA,QAAA;AAAA,UACC,SAAO;AAAA,UACP,SACEH,2BAAA,IAACN,GAAK,MAAA,EAAA,MAAM,GAAG,OAAO,EAAC,YAAY,SAAQ,GACxC,UAAe,eAAA,WAAW,wBAAwB,2BACrD;AAAA,UAEF,SAAS;AAAA,UACT,WAAU;AAAA,UAEV,UAAAM,2BAAA;AAAA,YAACI,GAAA;AAAA,YAAA;AAAA,cACC,UAAU,CAAC;AAAA,cACX,UAAU,CAAC,CAAC;AAAA,cACZ,SAAS;AAAA,cACT,MAAM,eAAe,WAAW,YAAY;AAAA,cAC5C,MAAMC,MAAA;AAAA,cACN,SAAS,MAAM,cAAc,eAAe,WAAW,YAAY,QAAQ;AAAA,YAAA;AAAA,UAC7E;AAAA,QAAA;AAAA,MAAA,GAEJ;AAAA,MACAL,2BAAAA,IAACM,GAAAA,OAAI,MAAM,GAAI,qBAAW,YAAYN,2BAAA,IAAC,YAAW,EAAA,IAAA,CAAU,EAAG,CAAA;AAAA,MAC9DF,2BAAA,KAAAI,GAAA,MAAA,EAAK,OAAM,UAAS,KAAK,GACvB,UAAA;AAAA,QACC,eAAAF,2BAAA;AAAA,UAACG,GAAA;AAAA,UAAA;AAAA,YACC,SAAO;AAAA,YACP,SACEH,2BAAAA,IAACN,GAAAA,MAAK,EAAA,MAAM,GAAG,OAAO,EAAC,YAAY,SAAQ,GACxC,UAAY,YAAA,oBAAe,SAC9B,CAAA;AAAA,YAEF,SAAS;AAAA,YAET,UAAAM,2BAAA;AAAA,cAACI,GAAA;AAAA,cAAA;AAAA,gBACC,UAAU,CAAC;AAAA,gBACX,MAAK;AAAA,gBACL,UAAU,CAAC,CAAC;AAAA,gBACZ,SAAS;AAAA,gBACT,MAAMG,MAAA;AAAA,gBACN,SAAS;AAAA,gBACT,cAAW;AAAA,gBACX,SAAS,MAAM,aAAa;AAAA,cAAA;AAAA,YAC9B;AAAA,UAAA;AAAA,QAAA,IAEA;AAAA,QACJP,2BAAA;AAAA,UAACG,GAAA;AAAA,UAAA;AAAA,YACC,SAAO;AAAA,YACP,SACGH,2BAAA,IAAAN,SAAA,EAAK,MAAM,GAAG,OAAO,EAAC,YAAY,SAAQ,GAAG,UAE9C,WAAA,CAAA;AAAA,YAEF,SAAS;AAAA,YAET,UAAAM,2BAAA;AAAA,cAACI,GAAA;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,UAAU,CAAC;AAAA,gBACX,UAAU,CAAC,CAAC;AAAA,gBACZ,MAAMI,MAAA;AAAA,gBACN,SAAS,CAAC,CAAC;AAAA,gBACX,cAAW;AAAA,gBACX,SAAS,MAAM;AA9G/B,sBAAA;AA+GuB,mBAAA,KAAA,SAAA,OAAA,SAAA,MAAO,YAAP,QAAgB,GAAA,UAErB,KAAK,MAAM,QAAQ,KAAK,GACxB,UAAU;AAAA,oBACR,UAAU;AAAA,oBACV,QAAQ;AAAA,oBACR,OAAO;AAAA,kBACR,CAAA;AAAA,gBACH;AAAA,cAAA;AAAA,YACF;AAAA,UAAA;AAAA,QACF;AAAA,QACAR,2BAAA;AAAA,UAACG,GAAA;AAAA,UAAA;AAAA,YACC,SAAO;AAAA,YACP,SACGH,2BAAA,IAAAN,SAAA,EAAK,MAAM,GAAG,OAAO,EAAC,YAAY,SAAQ,GAAG,UAE9C,wBAAA,CAAA;AAAA,YAEF,SAAS;AAAA,YACT,WAAU;AAAA,YAEV,UAAAM,2BAAA;AAAA,cAACI,GAAA;AAAA,cAAA;AAAA,gBACC,UAAU,CAAC;AAAA,gBACX,UAAU,CAAC,CAAC;AAAA,gBACZ,MAAMK,MAAA;AAAA,gBACN,MAAK;AAAA,gBACL,UAAU,CAAC,CAAC;AAAA,gBACZ,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,SAAS,WAAW,MAAM,OAAO,KAAK,IAAI,SAAA,CAAU,IAAI;AAAA,cAAA;AAAA,YAC1D;AAAA,UAAA;AAAA,QACF;AAAA,MAAA,GACF;AAAA,IAAA,EAAA,CACF,EACF,CAAA;AAAA,EACF,EAAA,CAAA;AAEJ;AChFA,MAAM,aAAaC,aAAAA,OAAOR,GAAAA,IAAI;AAWvB,SAAS,OAAO,OAAoB;AACzC,QAAM,EAAC,UAAU,QAAA,IAAW,OACtB,QAAQ,SAAS,SAAS,SAAS,aAAa,SAAS,WAEzD,EAAC,cAAc,cAAc,QAAQ,YAAY,iBAAiB,IAAM,IAAA,IAAO,SAE/E,SAASP,MAAO,OAAA,QAAQ,GAAG,GAC3B,CAAC,eAAe,gBAAgB,IAAIgB,eAAS,OAAO,EAAC,KAAK,MAAO,EAAA;AACvEC,QAAAA,UAAU,MAAM;AACd,WAAO,UAAU,QAAQ;AAAA,KACxB,CAAC,QAAQ,GAAG,CAAC,GAChBA,gBAAU,MAAM;AACV,SAAK,UAAU,EAAC,KAAK,MAAM,CAAA,MAAM,KAAK,UAAU,aAAa,KAC/D,gBAAgB,MAAM,iBAAiB,EAAC,KAAK,MAAM,CAAA,CAAC;AAAA,EAErD,GAAA,CAAC,OAAO,eAAe,GAAG,CAAC;AACxB,QAAA,cAAcC,sBAAe,GAC7B,SAASC,OAAAA,UAAU,EAAC,YAAY,aAAA,CAAa,GAC7C,CAAC,WAAW,YAAY,IAAIH,eAC5B,GAAA,mBAAmBhB,MAAAA,UACnB,CAAC,gBAAgB,eAAe,IAAIoB,MAAAA,iBACpC,MAAMC,MAAA;AAAA;AAAA,IAEV,OAAOC,WAAiC;AACtC,UAAI,OAAO,WAAa;AACtB;AAEF,YAAM,UAAU,OAAO;AACvB,UAAI,OAAO,WAAY;AACrB,eAAO,IAAI,IAAI,SAAS,SAAS,MAAM;AAErC,UAAA,OAAO,WAAY,YAAY;AAE3BC,cAAAA,OAAM,MAAM,QAAQD,MAAK;AACxB,eAAA,OAAOC,QAAQ,WAAW,IAAI,IAAIA,MAAK,SAAS,MAAM,IAAIA;AAAAA,MACnE;AACI,UAAA,OAAO,WAAY,UAAU;AACzB,cAAA,UACJ,OAAO,QAAQ,WAAY,aAAa,MAAM,QAAQ,QAAQD,MAAK,IAAI,QAAQ;AACjF,YAAI,OAAO,WAAY;AACd,iBAAA;AAEL,YAAA,CAAC,iBAAiB,SAAS;AAE7B,gBAAM,EAAC,QAAQ,WAAAE,WAAAA,IAAa,MAAMC,aAAA;AAAA,YAChC;AAAA,YACA;AAAA,YACA,SAAS;AAAA,YACT,eAAa,OAAA,SAAA,YAAA;AAAA,UAAA;AAEE,2BAAA,UAAU,QAC3B,gBAAgB,MAAM,aAAaD,WAAU,QAAS,CAAA,CAAC;AAAA,QACzD;AAUMD,cAAAA,OAAM,MARcG,kCAAiB;AAAA,UACzC,QAAQ,QAAQ,WAAW,gBAAgB,SAAS,SAAS,QAAQ;AAAA,UACrE;AAAA,UACA,WAAW;AAAA,YACT,QAAQ,QAAQ;AAAA,UAClB;AAAA,QAAA,CACD,EAEmC;AAAA,UAClC;AAAA,UACA,kBAAkB,iBAAiB;AAAA,UACnC,oBAAoB;AAAA,QAAA,CACrB;AACD,eAAO,IAAI,IAAIH,MAAK,SAAS,MAAM;AAAA,MACrC;AAAA,IAEF;AAAA,IACA,CAAC,QAAQ,eAAA,OAAA,SAAA,YAAa,EAAE;AAAA,EAAA;AAE1B,SAAAN,MAAA,UAAU,MAAM;AACd,QAAI,WAAW;AACb,YAAM,UAAU;AAAA,QACd,MAAM;AACJ,0BAAgB,MAAM,aAAa,MAAS,CAAC,GAC7C,iBAAiB,UAAU;AAAA,QAC7B;AAAA,QACA,KAAK,IAAI,GAAG,YAAY,KAAK,KAAK;AAAA,MAAA;AAE7B,aAAA,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EAEC,GAAA,CAAC,SAAS,CAAC,GAGZZ,2BAAAA,IAACsB,MAAAA,UAAS,EAAA,UAAWtB,+BAAA,SAAA,EAAQ,YAAW,UAAA,CAAU,GAChD,UAAAA,2BAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,MAAM,cAAc;AAAA,MACpB,eAAe,cAAc;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,eAAa,OAAA,SAAA,YAAA;AAAA,IAAA;AAAA,IAThB,cAAc;AAAA,EAWvB,EAAA,CAAA;AAEJ;AAUA,MAAM,cAAcuB,MAAAA,KAAK,SAAqB,OAAyB;AAhMvE,MAAA;AAiMQ,QAAA;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA,aAAa,CAAC;AAAA,IACd,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE,OACE,CAAC,YAAY,aAAa,IAAIZ,gBAAS,KAAQ,UAAA,QAAA,GAAA,WAAA,IAAe,cAAc,YAAY,GAExF,uBAAuBa,GAAA,wBAAA,GAEvB,MAAMC,aAAA;AAAA,IACV,MAAM,MAAM,IAAI,aAAa;AAAA,IAC7B;AAAA;AAAA,MAEE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EAAA,GAGI,CAAC,SAAS,UAAU,IAAId,eAAS,EAAI,GACrC,CAAC,YAAY,YAAY,IAAIA,eAAS,EAAK,GAC3C,YAAY,cAAc,gBAE1B,SAAShB,aAA0B,IAAI,GAEvC,eAAeqB,MAAAA,YAAY,MAAM;AAChC,cAAA,QAAA,OAAQ,YAMb,OAAO,QAAQ,MAAM,OAAO,QAAQ,KAEpC,aAAa,EAAI;AAAA,EACnB,GAAG,CAAE,CAAA;AAEL,wCACGU,aAAa,cAAA,EAAA,YAAY,uBAAuB,EAAC,UAAU,EAAC,IAAI,QAC/D,UAAA5B,gCAACI,GAAAA,QAAK,WAAU,UAAS,OAAO,EAAC,QAAQ,OACvC,GAAA,UAAA;AAAA,IAAAF,2BAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,cAAc,CAAC,EAAC,UAAQ,QAAA,OAAA;AAAA,QACxB;AAAA,MAAA;AAAA,IACF;AAAA,IACC,eAAe,QACbA,+BAAA,WAAA,EAAU,OAAO,IAAK,CAAA,IAEtBA,2BAAA,IAAAC,GAAA,MAAA,EAAK,MAAK,eAAc,OAAO,EAAC,QAAQ,UACvC,UAAAD,2BAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAAA,GAEJ;AAAA,EAAA,EAEJ,CAAA,EACF,CAAA;AAEJ,CAAC,GAUK,QAAQ2B,MAAAA,WAAW,SACvB,OACA,QACA;AACM,QAAA,EAAC,SAAS,YAAY,YAAY,YAAY,WAAW,KAAK,aAAgB,IAAA;AAEpF,WAAS,mBAAmB;AAC1B,eAAW,EAAK,GAChB,aAAa,EAAK,GAEd,WAAW,UAAU,OAAO,WAAW,UAAW,cACpD,WAAW,OAAO;AAAA,EAEtB;AAEA,SACG7B,2BAAA,KAAAI,GAAA,MAAA,EAAK,OAAM,UAAS,SAAQ,UAAS,OAAO,EAAC,QAAQ,QAAQ,UAAU,WAAA,GACtE,UAAA;AAAA,IAACF,2BAAA,IAAA4B,aAAA,iBAAA,EACE,UAAC,CAAA,OACC,WACC5B,2BAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,UAAU;AAAA,QACV,SAAQ;AAAA,QACR,OAAM;AAAA,QACN,OAAO,EAAC,OAAO,KAAK,UAAU,WAAU;AAAA,QAExC,UAAAA,2BAAA,IAAC,WAAQ,WAAwB,CAAA;AAAA,MAAA;AAAA,IAAA,GAGzC;AAAA,IACC,OACCA,2BAAA;AAAA,MAACU,aAAAA,OAAO;AAAA,MAAP;AAAA,QACC,KAAK;AAAA,QACL,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,OAAO,EAAC,WAAW,OAAM;AAAA,QACzB,KAAK,IAAI,SAAS;AAAA,QAClB,SAAS,CAAC,cAAc,UAAU;AAAA,QAClC,UAAU;AAAA,QACV,SAAS;AAAA,UACP,UAAU,eAAe;AAAA,UACzB,YAAY,cAAc;AAAA,UAC1B;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QACJ,QAAQ;AAAA,MAAA;AAAA,IACV;AAAA,EAEJ,EAAA,CAAA;AAEJ,CAAC,GAEK,kBAAkB;AAAA,EACtB,SAAS,EAAC,SAAS,EAAC;AAAA,EACpB,SAAS,EAAC,SAAS,CAAC,GAAG,GAAG,CAAC,EAAC;AAAA,EAC5B,MAAM,EAAC,SAAS,CAAC,GAAG,GAAG,CAAC,EAAC;AAC3B,GAEM,iBAAiB;AAAA,EACrB,GAAG;AAAA,EACH,SAAS;AAAA,IACP,GAAG,MAAM;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,IACN,GAAG,MAAM;AAAA,IACT,WAAW;AAAA,EACb;AAAA,EACA,YAAY;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,EACT;AAAA,EACA,WAAW;AAAA,IACT,OAAO,CAAC,GAAG,GAAG,GAAG,IAAI;AAAA,EACvB;AAAA,EACA,QAAQ;AAAA,IACN,SAAS,CAAC,GAAG,GAAG,CAAC;AAAA,IACjB,OAAO;AAAA,EACT;AACF;AAEA,SAAS,QAAQ,EAAC,cAA0C;AAC1D,yCACGR,GAAAA,MAAK,EAAA,OAAO,EAAC,GAAG,MAAM,UAAU,EAAI,GAAA,SAAQ,UAAS,OAAM,UAAS,WAAU,UAAS,KAAK,GAC3F,UAAA;AAAA,IAACF,2BAAAA,IAAA6B,GAAAA,SAAA,EAAQ,OAAK,GAAC,CAAA;AAAA,mCACdnC,GAAAA,MAAK,EAAA,OAAK,IAAC,MAAM,GAAG,UAErB,iBAAA;AAAA,EACF,EAAA,CAAA;AAEJ;AAEgB,SAAA,UAAU,EAAC,SAAwB;AACjD,SACGM,2BAAA,IAAAC,GAAA,MAAA,EAAK,QAAO,QACX,UAACD,2BAAAA,IAAAE,GAAAA,MAAA,EAAK,OAAM,UAAS,QAAO,QAAO,SAAQ,UAAS,SAAS,GAAG,QAAO,UACrE,UAAAF,2BAAA,IAAC8B,cAAU,EAAA,OAAO,GAChB,UAAA9B,2BAAAA,IAACC,WAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAK,WAC3C,0CAACC,SACC,EAAA,UAAA;AAAA,IAACF,2BAAAA,IAAAM,GAAAA,KAAA,EACC,yCAACZ,GAAK,MAAA,EAAA,MAAM,GACV,UAACM,2BAAAA,IAAA+B,MAAAA,oBAAA,CAAA,CAAmB,GACtB,EACF,CAAA;AAAA,oCACCC,GAAAA,OAAM,EAAA,MAAM,GAAG,YAAY,GAAG,OAAO,GACpC,UAAA;AAAA,MAAChC,2BAAAA,IAAAN,GAAA,MAAA,EAAK,IAAG,MAAK,MAAM,GAAG,QAAO,QAC3B,gBAAM,KACT,CAAA;AAAA,MACAM,2BAAAA,IAACN,GAAAA,QAAK,IAAG,KAAI,OAAK,IAAC,MAAM,GACtB,UAAA,MAAM,QACT,CAAA;AAAA,IAAA,GACF;AAAA,EACF,EAAA,CAAA,EAAA,CACF,EACF,CAAA,EACF,CAAA,EACF,CAAA;AAEJ;AAGA,MAAM,cAAc,OAAO;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/DisplayUrl.tsx","../src/Toolbar.tsx","../src/Iframe.tsx"],"sourcesContent":["import {getRedirectTo} from '@sanity/preview-url-secret/get-redirect-to'\nimport {Text} from '@sanity/ui'\nimport {useMemo} from 'react'\n\nexport function DisplayUrl(props: {url: URL}) {\n const truncatedUrl = useMemo(() => {\n const url = getRedirectTo(props.url)\n return `${url.origin === location.origin ? '' : url.origin}${url.pathname}${url.search}`\n }, [props.url])\n\n return (\n <Text size={0} textOverflow=\"ellipsis\">\n {truncatedUrl}\n </Text>\n )\n}\n","import {CopyIcon, LaunchIcon, MobileDeviceIcon, RefreshIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Text, Tooltip, useToast} from '@sanity/ui'\nimport {useRef} from 'react'\nimport {useCopyToClipboard} from 'usehooks-ts'\n\nimport {DisplayUrl} from './DisplayUrl'\nimport type {IframeSizeKey, SizeProps} from './types'\n\nexport const sizes: SizeProps = {\n desktop: {\n width: '100%',\n height: '100%',\n },\n mobile: {\n width: 414,\n height: 746,\n },\n}\n\nexport const DEFAULT_SIZE = 'desktop'\n\nexport interface ToolbarProps {\n url: URL | Error | undefined\n iframeSize: IframeSizeKey\n setIframeSize: (size: IframeSizeKey) => void\n showUrl: boolean\n reloading: boolean\n reloadButton: boolean\n handleReload: () => void\n}\nexport function Toolbar(props: ToolbarProps) {\n const {url, iframeSize, setIframeSize, reloading, showUrl, reloadButton, handleReload} = props\n const validUrl = url instanceof URL\n\n const input = useRef<HTMLTextAreaElement>(null)\n const {push: pushToast} = useToast()\n const [, copy] = useCopyToClipboard()\n\n return (\n <>\n <textarea\n style={{position: 'absolute', pointerEvents: 'none', opacity: 0}}\n ref={input}\n value={validUrl ? url.toString() : ''}\n readOnly\n tabIndex={-1}\n />\n <Card padding={2} borderBottom>\n <Flex align=\"center\" gap={2}>\n <Flex align=\"center\" gap={1}>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {iframeSize === 'mobile' ? 'Exit mobile preview' : 'Preview mobile viewport'}\n </Text>\n }\n padding={2}\n placement=\"bottom-start\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n padding={2}\n mode={iframeSize === 'mobile' ? 'default' : 'ghost'}\n icon={MobileDeviceIcon}\n onClick={() => setIframeSize(iframeSize === 'mobile' ? 'desktop' : 'mobile')}\n />\n </Tooltip>\n </Flex>\n <Box flex={1}>{showUrl && validUrl && <DisplayUrl url={url} />}</Box>\n <Flex align=\"center\" gap={1}>\n {reloadButton ? (\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {reloading ? 'Reloading…' : 'Reload'}\n </Text>\n }\n padding={2}\n >\n <Button\n disabled={!validUrl}\n mode=\"bleed\"\n fontSize={[1]}\n padding={2}\n icon={RefreshIcon}\n loading={reloading}\n aria-label=\"Reload\"\n onClick={() => handleReload()}\n />\n </Tooltip>\n ) : null}\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Copy URL\n </Text>\n }\n padding={2}\n >\n <Button\n mode=\"bleed\"\n disabled={!validUrl}\n fontSize={[1]}\n icon={CopyIcon}\n padding={[2]}\n aria-label=\"Copy URL\"\n onClick={() => {\n if (!input?.current?.value) return\n\n copy(input.current.value).then((copied) => {\n if (copied) {\n pushToast({\n closable: true,\n status: 'success',\n title: 'The URL is copied to the clipboard',\n })\n } else {\n pushToast({\n closable: true,\n status: 'error',\n title: 'Failed to copy the URL to the clipboard',\n })\n }\n })\n }}\n />\n </Tooltip>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Open URL in a new tab\n </Text>\n }\n padding={2}\n placement=\"bottom-end\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n icon={LaunchIcon}\n mode=\"ghost\"\n paddingY={[2]}\n text=\"Open\"\n aria-label=\"Open URL in a new tab\"\n onClick={validUrl ? () => window.open(url.toString()) : undefined}\n />\n </Tooltip>\n </Flex>\n </Flex>\n </Card>\n </>\n )\n}\n","import {WarningOutlineIcon} from '@sanity/icons'\nimport {createPreviewSecret} from '@sanity/preview-url-secret/create-secret'\nimport {definePreviewUrl} from '@sanity/preview-url-secret/define-preview-url'\nimport {Box, Card, Container, Flex, Spinner, Stack, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {AnimatePresence, HTMLMotionProps, motion, MotionConfig} from 'framer-motion'\nimport type {HTMLAttributeReferrerPolicy} from 'react'\nimport {\n forwardRef,\n memo,\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n} from 'react'\nimport {type SanityDocument, useClient, useCurrentUser, usePerspective} from 'sanity'\nimport {suspend} from 'suspend-react'\n\nimport {DEFAULT_SIZE, sizes, Toolbar} from './Toolbar'\nimport type {IframeSizeKey} from './types'\n\nexport type UrlResolver = (\n document: SanityDocument | null,\n perspective: Pick<\n ReturnType<typeof usePerspective>,\n 'selectedPerspectiveName' | 'perspectiveStack'\n >,\n) => string | Error | undefined | Promise<string | Error | undefined>\n\nexport type {IframeSizeKey}\n\nexport interface IframeOptions {\n /**\n * If you have multiple iframe instances side-by-side you need to give each a unique key.\n */\n key?: string\n url:\n | string\n | UrlResolver\n | {\n /**\n * The URL origin of where the preview is hosted, for example `https://example.com`.\n * If it's an embedded Studio then set it to `'same-origin'`.\n */\n origin: 'same-origin' | string\n /**\n * The route to redirect to after enabling Draft Mode.\n * If you don't have enough data to build the URL, return an `Error` instance to show an error message.\n * @example `return new Error('Missing slug')`\n * To prolong the loading state, return `undefined`\n */\n preview: string | UrlResolver\n /**\n * The route that enables Draft Mode\n * @example '/api/draft'\n */\n draftMode: string\n }\n defaultSize?: IframeSizeKey\n showDisplayUrl?: boolean\n reload?: {\n button?: boolean\n }\n attributes?: Partial<{\n allow: string\n referrerPolicy: HTMLAttributeReferrerPolicy | undefined\n sandbox: string\n onLoad: () => void\n }>\n}\n\ntype MotionIFrameProps = HTMLMotionProps<'iframe'> & React.IframeHTMLAttributes<HTMLIFrameElement>\n\nconst MotionFlex = motion(Flex)\nconst MotionIFrame = motion<MotionIFrameProps>('iframe')\n\nexport interface IframeProps {\n document: {\n displayed: SanityDocument\n draft: SanityDocument | null\n published: SanityDocument | null\n }\n options: IframeOptions\n}\n\nexport function Iframe(props: IframeProps) {\n const {document, options} = props\n const draft = document.draft || document.published || document.displayed\n\n const {defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = true, key} = options\n\n const urlRef = useRef(options.url)\n const [draftSnapshot, setDraftSnapshot] = useState(() => ({key, draft}))\n useEffect(() => {\n urlRef.current = options.url\n }, [options.url])\n useEffect(() => {\n if (JSON.stringify({key, draft}) !== JSON.stringify(draftSnapshot)) {\n startTransition(() => setDraftSnapshot({key, draft}))\n }\n }, [draft, draftSnapshot, key])\n const currentUser = useCurrentUser()\n const client = useClient({apiVersion: '2023-10-16'})\n const [expiresAt, setExpiresAt] = useState<number | undefined>()\n const previewSecretRef = useRef<string | undefined>(undefined)\n const [isResolvingUrl, startTransition] = useTransition()\n const {perspectiveStack, selectedPerspectiveName} = usePerspective()\n const perspective = useMemo(\n () => ({\n perspectiveStack,\n selectedPerspectiveName,\n }),\n [perspectiveStack, selectedPerspectiveName],\n )\n\n const url = useCallback(\n // eslint-disable-next-line @typescript-eslint/no-shadow\n async (draft: SanityDocument | null) => {\n if (typeof location === 'undefined') {\n return undefined\n }\n const urlProp = urlRef.current\n if (typeof urlProp === 'string') {\n return new URL(urlProp, location.origin)\n }\n if (typeof urlProp === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await urlProp(draft, perspective)\n return typeof url === 'string' ? new URL(url, location.origin) : url\n }\n if (typeof urlProp === 'object') {\n const preview =\n typeof urlProp.preview === 'function'\n ? await urlProp.preview(draft, perspective)\n : urlProp.preview\n if (typeof preview !== 'string') {\n return preview\n }\n if (!previewSecretRef.current) {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const {secret, expiresAt} = await createPreviewSecret(\n client,\n 'sanity-plugin-iframe-pane',\n location.href,\n currentUser?.id,\n )\n previewSecretRef.current = secret\n startTransition(() => setExpiresAt(expiresAt.getTime()))\n }\n\n const resolvePreviewUrl = definePreviewUrl({\n origin: urlProp.origin === 'same-origin' ? location.origin : urlProp.origin,\n preview,\n draftMode: {\n enable: urlProp.draftMode,\n },\n })\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await resolvePreviewUrl({\n client,\n previewUrlSecret: previewSecretRef.current,\n previewSearchParam: null,\n })\n return new URL(url, location.origin)\n }\n return undefined\n },\n [client, currentUser?.id, perspective],\n )\n useEffect(() => {\n if (expiresAt) {\n const timeout = setTimeout(\n () => {\n startTransition(() => setExpiresAt(undefined))\n previewSecretRef.current = undefined\n },\n Math.max(0, expiresAt - Date.now()),\n )\n return () => clearTimeout(timeout)\n }\n return undefined\n }, [expiresAt])\n\n return (\n <Suspense fallback={<Loading iframeSize=\"desktop\" />}>\n <IframeInner\n key={`${draftSnapshot.key}-${selectedPerspectiveName || 'draft'}`}\n _key={draftSnapshot.key}\n draftSnapshot={draftSnapshot.draft}\n url={url}\n isResolvingUrl={isResolvingUrl}\n attributes={attributes}\n perspective={perspective}\n defaultSize={defaultSize}\n reload={reload}\n showDisplayUrl={showDisplayUrl}\n userId={currentUser?.id}\n />\n </Suspense>\n )\n}\n\nexport interface IframeInnerProps extends Omit<IframeOptions, 'url'> {\n url: (draftSnapshot: SanityDocument | null) => Promise<URL | Error | undefined>\n isResolvingUrl: boolean\n draftSnapshot: SanityDocument | null\n perspective: Parameters<UrlResolver>[1]\n userId?: string\n expiresAt?: number\n _key?: string\n}\nconst IframeInner = memo(function IframeInner(props: IframeInnerProps) {\n const {\n isResolvingUrl,\n defaultSize = DEFAULT_SIZE,\n reload,\n attributes = {},\n showDisplayUrl = true,\n draftSnapshot,\n userId,\n expiresAt,\n perspective: {selectedPerspectiveName, perspectiveStack},\n _key,\n } = props\n const [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE)\n\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const url = suspend(\n () => props.url(draftSnapshot),\n [\n // Cache based on a few specific conditions\n 'sanity-plugin-iframe-pane',\n draftSnapshot,\n selectedPerspectiveName,\n perspectiveStack,\n userId,\n expiresAt,\n _key,\n resolveUUID,\n ],\n )\n\n const [loading, setLoading] = useState(true)\n const [_reloading, setReloading] = useState(false)\n const reloading = _reloading || isResolvingUrl\n\n const iframe = useRef<HTMLIFrameElement>(null)\n\n const handleReload = useCallback(() => {\n if (!iframe?.current) {\n return\n }\n\n // Funky way to reload an iframe without CORS issues\n // eslint-disable-next-line no-self-assign\n iframe.current.src = iframe.current.src\n\n setReloading(true)\n }, [])\n\n return (\n <MotionConfig transition={prefersReducedMotion ? {duration: 0} : undefined}>\n <Flex direction=\"column\" style={{height: '100%'}}>\n <Toolbar\n url={url}\n iframeSize={iframeSize}\n reloading={reloading}\n setIframeSize={setIframeSize}\n showUrl={showDisplayUrl}\n reloadButton={!!reload?.button}\n handleReload={handleReload}\n />\n {url instanceof Error ? (\n <ErrorCard error={url} />\n ) : (\n <Card tone=\"transparent\" style={{height: '100%'}}>\n <Frame\n ref={iframe}\n loading={loading}\n reloading={reloading}\n iframeSize={iframeSize}\n setReloading={setReloading}\n setLoading={setLoading}\n url={url}\n attributes={attributes}\n />\n </Card>\n )}\n </Flex>\n </MotionConfig>\n )\n})\n\ninterface FrameProps extends Required<Pick<IframeOptions, 'attributes'>> {\n loading: boolean\n reloading: boolean\n setLoading: (loading: boolean) => void\n setReloading: (reloading: boolean) => void\n iframeSize: IframeSizeKey\n url: URL | undefined\n}\nconst Frame = forwardRef(function Frame(\n props: FrameProps,\n iframe: React.ForwardedRef<HTMLIFrameElement>,\n) {\n const {loading, setLoading, iframeSize, attributes, reloading, url, setReloading} = props\n\n function handleIframeLoad() {\n setLoading(false)\n setReloading(false)\n // Run onLoad from attributes\n if (attributes.onLoad && typeof attributes.onLoad === 'function') {\n attributes.onLoad()\n }\n }\n\n return (\n <Flex align=\"center\" justify=\"center\" style={{height: '100%', position: 'relative'}}>\n <AnimatePresence>\n {!url ||\n (loading && (\n <MotionFlex\n initial=\"initial\"\n animate=\"animate\"\n exit=\"exit\"\n variants={spinnerVariants}\n justify=\"center\"\n align=\"center\"\n style={{inset: '0', position: 'absolute'}}\n >\n <Loading iframeSize={iframeSize} />\n </MotionFlex>\n ))}\n </AnimatePresence>\n {url && (\n <MotionIFrame\n ref={iframe}\n title=\"preview\"\n frameBorder=\"0\"\n style={{maxHeight: '100%'}}\n src={url.toString()}\n initial={['background', iframeSize]}\n variants={iframeVariants}\n animate={[\n loading ? 'background' : 'active',\n reloading ? 'reloading' : 'idle',\n iframeSize,\n ]}\n {...attributes}\n onLoad={handleIframeLoad}\n />\n )}\n </Flex>\n )\n})\n\nconst spinnerVariants = {\n initial: {opacity: 1},\n animate: {opacity: [0, 0, 1]},\n exit: {opacity: [1, 0, 0]},\n}\n\nconst iframeVariants = {\n ...sizes,\n desktop: {\n ...sizes.desktop,\n boxShadow: '0 0 0 0px var(--card-shadow-outline-color)',\n },\n mobile: {\n ...sizes.mobile,\n boxShadow: '0 0 0 1px var(--card-shadow-outline-color)',\n },\n background: {\n opacity: 0,\n scale: 1,\n },\n idle: {\n scale: 1,\n },\n reloading: {\n scale: [1, 1, 1, 0.98],\n },\n active: {\n opacity: [0, 0, 1],\n scale: 1,\n },\n}\n\nfunction Loading({iframeSize}: {iframeSize: IframeSizeKey}) {\n return (\n <Flex style={{...sizes[iframeSize]}} justify=\"center\" align=\"center\" direction=\"column\" gap={4}>\n <Spinner muted />\n <Text muted size={1}>\n Loading…\n </Text>\n </Flex>\n )\n}\n\nexport function ErrorCard({error}: {error: Error}) {\n return (\n <Card height=\"fill\">\n <Flex align=\"center\" height=\"fill\" justify=\"center\" padding={4} sizing=\"border\">\n <Container width={0}>\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Flex>\n <Box>\n <Text size={1}>\n <WarningOutlineIcon />\n </Text>\n </Box>\n <Stack flex={1} marginLeft={3} space={3}>\n <Text as=\"h1\" size={1} weight=\"bold\">\n {error.name}\n </Text>\n <Text as=\"p\" muted size={1}>\n {error.message}\n </Text>\n </Stack>\n </Flex>\n </Card>\n </Container>\n </Flex>\n </Card>\n )\n}\n\n// https://github.com/pmndrs/suspend-react?tab=readme-ov-file#making-cache-keys-unique\nconst resolveUUID = Symbol()\n"],"names":["useMemo","getRedirectTo","Text","useRef","useToast","useCopyToClipboard","jsxs","Fragment","jsx","Card","Flex","Tooltip","Button","MobileDeviceIcon","Box","RefreshIcon","CopyIcon","LaunchIcon","motion","useState","useEffect","useCurrentUser","useClient","useTransition","usePerspective","useCallback","draft","url","expiresAt","createPreviewSecret","definePreviewUrl","Suspense","memo","usePrefersReducedMotion","suspend","MotionConfig","forwardRef","AnimatePresence","Spinner","Container","WarningOutlineIcon","Stack"],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import {HTMLAttributeReferrerPolicy} from 'react'
1
+ import type {HTMLAttributeReferrerPolicy} from 'react'
2
2
  import {JSX as JSX_2} from 'react/jsx-runtime'
3
3
  import {SanityDocument} from 'sanity'
4
+ import {usePerspective} from 'sanity'
4
5
 
5
6
  export declare function Iframe(props: IframeProps): JSX_2.Element
6
7
 
@@ -66,6 +67,10 @@ export declare type SizeProps = {
66
67
 
67
68
  export declare type UrlResolver = (
68
69
  document: SanityDocument | null,
70
+ perspective: Pick<
71
+ ReturnType<typeof usePerspective>,
72
+ 'selectedPerspectiveName' | 'perspectiveStack'
73
+ >,
69
74
  ) => string | Error | undefined | Promise<string | Error | undefined>
70
75
 
71
76
  export {}
package/lib/index.js CHANGED
@@ -5,7 +5,7 @@ import { definePreviewUrl } from "@sanity/preview-url-secret/define-preview-url"
5
5
  import { Text, useToast, Card, Flex, Tooltip, Button, Box, usePrefersReducedMotion, Spinner, Container, Stack } from "@sanity/ui";
6
6
  import { motion, MotionConfig, AnimatePresence } from "framer-motion";
7
7
  import { useMemo, useRef, memo, useState, useCallback, forwardRef, useEffect, useTransition, Suspense } from "react";
8
- import { useCurrentUser, useClient } from "sanity";
8
+ import { useCurrentUser, useClient, usePerspective } from "sanity";
9
9
  import { suspend } from "suspend-react";
10
10
  import { useCopyToClipboard } from "usehooks-ts";
11
11
  import { getRedirectTo } from "@sanity/preview-url-secret/get-redirect-to";
@@ -99,12 +99,17 @@ function Toolbar(props) {
99
99
  padding: [2],
100
100
  "aria-label": "Copy URL",
101
101
  onClick: () => {
102
- var _a;
103
- (_a = input == null ? void 0 : input.current) != null && _a.value && (copy(input.current.value), pushToast({
104
- closable: !0,
105
- status: "success",
106
- title: "The URL is copied to the clipboard"
107
- }));
102
+ input?.current?.value && copy(input.current.value).then((copied) => {
103
+ pushToast(copied ? {
104
+ closable: !0,
105
+ status: "success",
106
+ title: "The URL is copied to the clipboard"
107
+ } : {
108
+ closable: !0,
109
+ status: "error",
110
+ title: "Failed to copy the URL to the clipboard"
111
+ });
112
+ });
108
113
  }
109
114
  }
110
115
  )
@@ -136,7 +141,7 @@ function Toolbar(props) {
136
141
  ] }) })
137
142
  ] });
138
143
  }
139
- const MotionFlex = motion(Flex);
144
+ const MotionFlex = motion(Flex), MotionIFrame = motion("iframe");
140
145
  function Iframe(props) {
141
146
  const { document, options } = props, draft = document.draft || document.published || document.displayed, { defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = !0, key } = options, urlRef = useRef(options.url), [draftSnapshot, setDraftSnapshot] = useState(() => ({ key, draft }));
142
147
  useEffect(() => {
@@ -144,7 +149,13 @@ function Iframe(props) {
144
149
  }, [options.url]), useEffect(() => {
145
150
  JSON.stringify({ key, draft }) !== JSON.stringify(draftSnapshot) && startTransition(() => setDraftSnapshot({ key, draft }));
146
151
  }, [draft, draftSnapshot, key]);
147
- const currentUser = useCurrentUser(), client = useClient({ apiVersion: "2023-10-16" }), [expiresAt, setExpiresAt] = useState(), previewSecretRef = useRef(), [isResolvingUrl, startTransition] = useTransition(), url = useCallback(
152
+ const currentUser = useCurrentUser(), client = useClient({ apiVersion: "2023-10-16" }), [expiresAt, setExpiresAt] = useState(), previewSecretRef = useRef(void 0), [isResolvingUrl, startTransition] = useTransition(), { perspectiveStack, selectedPerspectiveName } = usePerspective(), perspective = useMemo(
153
+ () => ({
154
+ perspectiveStack,
155
+ selectedPerspectiveName
156
+ }),
157
+ [perspectiveStack, selectedPerspectiveName]
158
+ ), url = useCallback(
148
159
  // eslint-disable-next-line @typescript-eslint/no-shadow
149
160
  async (draft2) => {
150
161
  if (typeof location > "u")
@@ -153,11 +164,11 @@ function Iframe(props) {
153
164
  if (typeof urlProp == "string")
154
165
  return new URL(urlProp, location.origin);
155
166
  if (typeof urlProp == "function") {
156
- const url2 = await urlProp(draft2);
167
+ const url2 = await urlProp(draft2, perspective);
157
168
  return typeof url2 == "string" ? new URL(url2, location.origin) : url2;
158
169
  }
159
170
  if (typeof urlProp == "object") {
160
- const preview = typeof urlProp.preview == "function" ? await urlProp.preview(draft2) : urlProp.preview;
171
+ const preview = typeof urlProp.preview == "function" ? await urlProp.preview(draft2, perspective) : urlProp.preview;
161
172
  if (typeof preview != "string")
162
173
  return preview;
163
174
  if (!previewSecretRef.current) {
@@ -165,7 +176,7 @@ function Iframe(props) {
165
176
  client,
166
177
  "sanity-plugin-iframe-pane",
167
178
  location.href,
168
- currentUser == null ? void 0 : currentUser.id
179
+ currentUser?.id
169
180
  );
170
181
  previewSecretRef.current = secret, startTransition(() => setExpiresAt(expiresAt2.getTime()));
171
182
  }
@@ -183,7 +194,7 @@ function Iframe(props) {
183
194
  return new URL(url2, location.origin);
184
195
  }
185
196
  },
186
- [client, currentUser == null ? void 0 : currentUser.id]
197
+ [client, currentUser?.id, perspective]
187
198
  );
188
199
  return useEffect(() => {
189
200
  if (expiresAt) {
@@ -203,16 +214,16 @@ function Iframe(props) {
203
214
  url,
204
215
  isResolvingUrl,
205
216
  attributes,
217
+ perspective,
206
218
  defaultSize,
207
219
  reload,
208
220
  showDisplayUrl,
209
- userId: currentUser == null ? void 0 : currentUser.id
221
+ userId: currentUser?.id
210
222
  },
211
- draftSnapshot.key
223
+ `${draftSnapshot.key}-${selectedPerspectiveName || "draft"}`
212
224
  ) });
213
225
  }
214
226
  const IframeInner = memo(function(props) {
215
- var _a;
216
227
  const {
217
228
  isResolvingUrl,
218
229
  defaultSize = DEFAULT_SIZE,
@@ -222,20 +233,23 @@ const IframeInner = memo(function(props) {
222
233
  draftSnapshot,
223
234
  userId,
224
235
  expiresAt,
236
+ perspective: { selectedPerspectiveName, perspectiveStack },
225
237
  _key
226
- } = props, [iframeSize, setIframeSize] = useState((_a = sizes) != null && _a[defaultSize] ? defaultSize : DEFAULT_SIZE), prefersReducedMotion = usePrefersReducedMotion(), url = suspend(
238
+ } = props, [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE), prefersReducedMotion = usePrefersReducedMotion(), url = suspend(
227
239
  () => props.url(draftSnapshot),
228
240
  [
229
241
  // Cache based on a few specific conditions
230
242
  "sanity-plugin-iframe-pane",
231
243
  draftSnapshot,
244
+ selectedPerspectiveName,
245
+ perspectiveStack,
232
246
  userId,
233
247
  expiresAt,
234
248
  _key,
235
249
  resolveUUID
236
250
  ]
237
251
  ), [loading, setLoading] = useState(!0), [_reloading, setReloading] = useState(!1), reloading = _reloading || isResolvingUrl, iframe = useRef(null), handleReload = useCallback(() => {
238
- iframe != null && iframe.current && (iframe.current.src = iframe.current.src, setReloading(!0));
252
+ iframe?.current && (iframe.current.src = iframe.current.src, setReloading(!0));
239
253
  }, []);
240
254
  return /* @__PURE__ */ jsx(MotionConfig, { transition: prefersReducedMotion ? { duration: 0 } : void 0, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%" }, children: [
241
255
  /* @__PURE__ */ jsx(
@@ -246,7 +260,7 @@ const IframeInner = memo(function(props) {
246
260
  reloading,
247
261
  setIframeSize,
248
262
  showUrl: showDisplayUrl,
249
- reloadButton: !!(reload != null && reload.button),
263
+ reloadButton: !!reload?.button,
250
264
  handleReload
251
265
  }
252
266
  ),
@@ -284,7 +298,7 @@ const IframeInner = memo(function(props) {
284
298
  }
285
299
  ) }),
286
300
  url && /* @__PURE__ */ jsx(
287
- motion.iframe,
301
+ MotionIFrame,
288
302
  {
289
303
  ref: iframe,
290
304
  title: "preview",
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/DisplayUrl.tsx","../src/Toolbar.tsx","../src/Iframe.tsx"],"sourcesContent":["import {getRedirectTo} from '@sanity/preview-url-secret/get-redirect-to'\nimport {Text} from '@sanity/ui'\nimport React, {useMemo} from 'react'\n\nexport function DisplayUrl(props: {url: URL}) {\n const truncatedUrl = useMemo(() => {\n const url = getRedirectTo(props.url)\n return `${url.origin === location.origin ? '' : url.origin}${url.pathname}${url.search}`\n }, [props.url])\n\n return (\n <Text size={0} textOverflow=\"ellipsis\">\n {truncatedUrl}\n </Text>\n )\n}\n","import {CopyIcon, LaunchIcon, MobileDeviceIcon, RefreshIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Text, Tooltip, useToast} from '@sanity/ui'\nimport React, {useRef} from 'react'\nimport {useCopyToClipboard} from 'usehooks-ts'\n\nimport {DisplayUrl} from './DisplayUrl'\nimport {IframeSizeKey, type SizeProps} from './types'\n\nexport const sizes: SizeProps = {\n desktop: {\n width: '100%',\n height: '100%',\n },\n mobile: {\n width: 414,\n height: 746,\n },\n}\n\nexport const DEFAULT_SIZE = `desktop`\n\nexport interface ToolbarProps {\n url: URL | Error | undefined\n iframeSize: IframeSizeKey\n setIframeSize: (size: IframeSizeKey) => void\n showUrl: boolean\n reloading: boolean\n reloadButton: boolean\n handleReload: () => void\n}\nexport function Toolbar(props: ToolbarProps) {\n const {url, iframeSize, setIframeSize, reloading, showUrl, reloadButton, handleReload} = props\n const validUrl = url instanceof URL\n\n const input = useRef<HTMLTextAreaElement>(null)\n const {push: pushToast} = useToast()\n const [, copy] = useCopyToClipboard()\n\n return (\n <>\n <textarea\n style={{position: `absolute`, pointerEvents: `none`, opacity: 0}}\n ref={input}\n value={validUrl ? url.toString() : ''}\n readOnly\n tabIndex={-1}\n />\n <Card padding={2} borderBottom>\n <Flex align=\"center\" gap={2}>\n <Flex align=\"center\" gap={1}>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {iframeSize === 'mobile' ? 'Exit mobile preview' : 'Preview mobile viewport'}\n </Text>\n }\n padding={2}\n placement=\"bottom-start\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n padding={2}\n mode={iframeSize === 'mobile' ? 'default' : 'ghost'}\n icon={MobileDeviceIcon}\n onClick={() => setIframeSize(iframeSize === 'mobile' ? 'desktop' : 'mobile')}\n />\n </Tooltip>\n </Flex>\n <Box flex={1}>{showUrl && validUrl && <DisplayUrl url={url} />}</Box>\n <Flex align=\"center\" gap={1}>\n {reloadButton ? (\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {reloading ? 'Reloading…' : 'Reload'}\n </Text>\n }\n padding={2}\n >\n <Button\n disabled={!validUrl}\n mode=\"bleed\"\n fontSize={[1]}\n padding={2}\n icon={RefreshIcon}\n loading={reloading}\n aria-label=\"Reload\"\n onClick={() => handleReload()}\n />\n </Tooltip>\n ) : null}\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Copy URL\n </Text>\n }\n padding={2}\n >\n <Button\n mode=\"bleed\"\n disabled={!validUrl}\n fontSize={[1]}\n icon={CopyIcon}\n padding={[2]}\n aria-label=\"Copy URL\"\n onClick={() => {\n if (!input?.current?.value) return\n\n copy(input.current.value)\n pushToast({\n closable: true,\n status: 'success',\n title: 'The URL is copied to the clipboard',\n })\n }}\n />\n </Tooltip>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Open URL in a new tab\n </Text>\n }\n padding={2}\n placement=\"bottom-end\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n icon={LaunchIcon}\n mode=\"ghost\"\n paddingY={[2]}\n text=\"Open\"\n aria-label=\"Open URL in a new tab\"\n onClick={validUrl ? () => window.open(url.toString()) : undefined}\n />\n </Tooltip>\n </Flex>\n </Flex>\n </Card>\n </>\n )\n}\n","import {WarningOutlineIcon} from '@sanity/icons'\nimport {createPreviewSecret} from '@sanity/preview-url-secret/create-secret'\nimport {definePreviewUrl} from '@sanity/preview-url-secret/define-preview-url'\nimport {Box, Card, Container, Flex, Spinner, Stack, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {AnimatePresence, motion, MotionConfig} from 'framer-motion'\nimport React, {\n forwardRef,\n memo,\n Suspense,\n useCallback,\n useEffect,\n useRef,\n useState,\n useTransition,\n} from 'react'\nimport {HTMLAttributeReferrerPolicy} from 'react'\nimport {SanityDocument, useClient, useCurrentUser} from 'sanity'\nimport {suspend} from 'suspend-react'\n\nimport {DEFAULT_SIZE, sizes, Toolbar} from './Toolbar'\nimport {IframeSizeKey} from './types'\n\nexport type UrlResolver = (\n document: SanityDocument | null,\n) => string | Error | undefined | Promise<string | Error | undefined>\n\nexport type {IframeSizeKey}\n\nexport interface IframeOptions {\n /**\n * If you have multiple iframe instances side-by-side you need to give each a unique key.\n */\n key?: string\n url:\n | string\n | UrlResolver\n | {\n /**\n * The URL origin of where the preview is hosted, for example `https://example.com`.\n * If it's an embedded Studio then set it to `'same-origin'`.\n */\n origin: 'same-origin' | string\n /**\n * The route to redirect to after enabling Draft Mode.\n * If you don't have enough data to build the URL, return an `Error` instance to show an error message.\n * @example `return new Error('Missing slug')`\n * To prolong the loading state, return `undefined`\n */\n preview: string | UrlResolver\n /**\n * The route that enables Draft Mode\n * @example '/api/draft'\n */\n draftMode: string\n }\n defaultSize?: IframeSizeKey\n showDisplayUrl?: boolean\n reload?: {\n button?: boolean\n }\n attributes?: Partial<{\n allow: string\n referrerPolicy: HTMLAttributeReferrerPolicy | undefined\n sandbox: string\n onLoad: () => void\n }>\n}\n\nconst MotionFlex = motion(Flex)\n\nexport interface IframeProps {\n document: {\n displayed: SanityDocument\n draft: SanityDocument | null\n published: SanityDocument | null\n }\n options: IframeOptions\n}\n\nexport function Iframe(props: IframeProps) {\n const {document, options} = props\n const draft = document.draft || document.published || document.displayed\n\n const {defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = true, key} = options\n\n const urlRef = useRef(options.url)\n const [draftSnapshot, setDraftSnapshot] = useState(() => ({key, draft}))\n useEffect(() => {\n urlRef.current = options.url\n }, [options.url])\n useEffect(() => {\n if (JSON.stringify({key, draft}) !== JSON.stringify(draftSnapshot)) {\n startTransition(() => setDraftSnapshot({key, draft}))\n }\n }, [draft, draftSnapshot, key])\n const currentUser = useCurrentUser()\n const client = useClient({apiVersion: '2023-10-16'})\n const [expiresAt, setExpiresAt] = useState<number | undefined>()\n const previewSecretRef = useRef<string | undefined>()\n const [isResolvingUrl, startTransition] = useTransition()\n const url = useCallback(\n // eslint-disable-next-line @typescript-eslint/no-shadow\n async (draft: SanityDocument | null) => {\n if (typeof location === 'undefined') {\n return undefined\n }\n const urlProp = urlRef.current\n if (typeof urlProp === 'string') {\n return new URL(urlProp, location.origin)\n }\n if (typeof urlProp === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await urlProp(draft)\n return typeof url === 'string' ? new URL(url, location.origin) : url\n }\n if (typeof urlProp === 'object') {\n const preview =\n typeof urlProp.preview === 'function' ? await urlProp.preview(draft) : urlProp.preview\n if (typeof preview !== 'string') {\n return preview\n }\n if (!previewSecretRef.current) {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const {secret, expiresAt} = await createPreviewSecret(\n client,\n 'sanity-plugin-iframe-pane',\n location.href,\n currentUser?.id,\n )\n previewSecretRef.current = secret\n startTransition(() => setExpiresAt(expiresAt.getTime()))\n }\n\n const resolvePreviewUrl = definePreviewUrl({\n origin: urlProp.origin === 'same-origin' ? location.origin : urlProp.origin,\n preview,\n draftMode: {\n enable: urlProp.draftMode,\n },\n })\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await resolvePreviewUrl({\n client,\n previewUrlSecret: previewSecretRef.current,\n previewSearchParam: null,\n })\n return new URL(url, location.origin)\n }\n return undefined\n },\n [client, currentUser?.id],\n )\n useEffect(() => {\n if (expiresAt) {\n const timeout = setTimeout(\n () => {\n startTransition(() => setExpiresAt(undefined))\n previewSecretRef.current = undefined\n },\n Math.max(0, expiresAt - Date.now()),\n )\n return () => clearTimeout(timeout)\n }\n return undefined\n }, [expiresAt])\n\n return (\n <Suspense fallback={<Loading iframeSize=\"desktop\" />}>\n <IframeInner\n key={draftSnapshot.key}\n _key={draftSnapshot.key}\n draftSnapshot={draftSnapshot.draft}\n url={url}\n isResolvingUrl={isResolvingUrl}\n attributes={attributes}\n defaultSize={defaultSize}\n reload={reload}\n showDisplayUrl={showDisplayUrl}\n userId={currentUser?.id}\n />\n </Suspense>\n )\n}\n\nexport interface IframeInnerProps extends Omit<IframeOptions, 'url'> {\n url: (draftSnapshot: SanityDocument | null) => Promise<URL | Error | undefined>\n isResolvingUrl: boolean\n draftSnapshot: SanityDocument | null\n userId?: string\n expiresAt?: number\n _key?: string\n}\nconst IframeInner = memo(function IframeInner(props: IframeInnerProps) {\n const {\n isResolvingUrl,\n defaultSize = DEFAULT_SIZE,\n reload,\n attributes = {},\n showDisplayUrl = true,\n draftSnapshot,\n userId,\n expiresAt,\n _key,\n } = props\n const [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE)\n\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const url = suspend(\n () => props.url(draftSnapshot),\n [\n // Cache based on a few specific conditions\n 'sanity-plugin-iframe-pane',\n draftSnapshot,\n userId,\n expiresAt,\n _key,\n resolveUUID,\n ],\n )\n\n const [loading, setLoading] = useState(true)\n const [_reloading, setReloading] = useState(false)\n const reloading = _reloading || isResolvingUrl\n\n const iframe = useRef<HTMLIFrameElement>(null)\n\n const handleReload = useCallback(() => {\n if (!iframe?.current) {\n return\n }\n\n // Funky way to reload an iframe without CORS issues\n // eslint-disable-next-line no-self-assign\n iframe.current.src = iframe.current.src\n\n setReloading(true)\n }, [])\n\n return (\n <MotionConfig transition={prefersReducedMotion ? {duration: 0} : undefined}>\n <Flex direction=\"column\" style={{height: `100%`}}>\n <Toolbar\n url={url}\n iframeSize={iframeSize}\n reloading={reloading}\n setIframeSize={setIframeSize}\n showUrl={showDisplayUrl}\n reloadButton={!!reload?.button}\n handleReload={handleReload}\n />\n {url instanceof Error ? (\n <ErrorCard error={url} />\n ) : (\n <Card tone=\"transparent\" style={{height: `100%`}}>\n <Frame\n ref={iframe}\n loading={loading}\n reloading={reloading}\n iframeSize={iframeSize}\n setReloading={setReloading}\n setLoading={setLoading}\n url={url}\n attributes={attributes}\n />\n </Card>\n )}\n </Flex>\n </MotionConfig>\n )\n})\n\ninterface FrameProps extends Required<Pick<IframeOptions, 'attributes'>> {\n loading: boolean\n reloading: boolean\n setLoading: (loading: boolean) => void\n setReloading: (reloading: boolean) => void\n iframeSize: IframeSizeKey\n url: URL | undefined\n}\nconst Frame = forwardRef(function Frame(\n props: FrameProps,\n iframe: React.ForwardedRef<HTMLIFrameElement>,\n) {\n const {loading, setLoading, iframeSize, attributes, reloading, url, setReloading} = props\n\n function handleIframeLoad() {\n setLoading(false)\n setReloading(false)\n // Run onLoad from attributes\n if (attributes.onLoad && typeof attributes.onLoad === 'function') {\n attributes.onLoad()\n }\n }\n\n return (\n <Flex align=\"center\" justify=\"center\" style={{height: `100%`, position: `relative`}}>\n <AnimatePresence>\n {!url ||\n (loading && (\n <MotionFlex\n initial=\"initial\"\n animate=\"animate\"\n exit=\"exit\"\n variants={spinnerVariants}\n justify=\"center\"\n align=\"center\"\n style={{inset: `0`, position: `absolute`}}\n >\n <Loading iframeSize={iframeSize} />\n </MotionFlex>\n ))}\n </AnimatePresence>\n {url && (\n <motion.iframe\n ref={iframe}\n title=\"preview\"\n frameBorder=\"0\"\n style={{maxHeight: '100%'}}\n src={url.toString()}\n initial={['background', iframeSize]}\n variants={iframeVariants}\n animate={[\n loading ? 'background' : 'active',\n reloading ? 'reloading' : 'idle',\n iframeSize,\n ]}\n {...attributes}\n onLoad={handleIframeLoad}\n />\n )}\n </Flex>\n )\n})\n\nconst spinnerVariants = {\n initial: {opacity: 1},\n animate: {opacity: [0, 0, 1]},\n exit: {opacity: [1, 0, 0]},\n}\n\nconst iframeVariants = {\n ...sizes,\n desktop: {\n ...sizes.desktop,\n boxShadow: '0 0 0 0px var(--card-shadow-outline-color)',\n },\n mobile: {\n ...sizes.mobile,\n boxShadow: '0 0 0 1px var(--card-shadow-outline-color)',\n },\n background: {\n opacity: 0,\n scale: 1,\n },\n idle: {\n scale: 1,\n },\n reloading: {\n scale: [1, 1, 1, 0.98],\n },\n active: {\n opacity: [0, 0, 1],\n scale: 1,\n },\n}\n\nfunction Loading({iframeSize}: {iframeSize: IframeSizeKey}) {\n return (\n <Flex style={{...sizes[iframeSize]}} justify=\"center\" align=\"center\" direction=\"column\" gap={4}>\n <Spinner muted />\n <Text muted size={1}>\n Loading…\n </Text>\n </Flex>\n )\n}\n\nexport function ErrorCard({error}: {error: Error}) {\n return (\n <Card height=\"fill\">\n <Flex align=\"center\" height=\"fill\" justify=\"center\" padding={4} sizing=\"border\">\n <Container width={0}>\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Flex>\n <Box>\n <Text size={1}>\n <WarningOutlineIcon />\n </Text>\n </Box>\n <Stack flex={1} marginLeft={3} space={3}>\n <Text as=\"h1\" size={1} weight=\"bold\">\n {error.name}\n </Text>\n <Text as=\"p\" muted size={1}>\n {error.message}\n </Text>\n </Stack>\n </Flex>\n </Card>\n </Container>\n </Flex>\n </Card>\n )\n}\n\n// https://github.com/pmndrs/suspend-react?tab=readme-ov-file#making-cache-keys-unique\nconst resolveUUID = Symbol()\n"],"names":["draft","url","expiresAt"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/DisplayUrl.tsx","../src/Toolbar.tsx","../src/Iframe.tsx"],"sourcesContent":["import {getRedirectTo} from '@sanity/preview-url-secret/get-redirect-to'\nimport {Text} from '@sanity/ui'\nimport {useMemo} from 'react'\n\nexport function DisplayUrl(props: {url: URL}) {\n const truncatedUrl = useMemo(() => {\n const url = getRedirectTo(props.url)\n return `${url.origin === location.origin ? '' : url.origin}${url.pathname}${url.search}`\n }, [props.url])\n\n return (\n <Text size={0} textOverflow=\"ellipsis\">\n {truncatedUrl}\n </Text>\n )\n}\n","import {CopyIcon, LaunchIcon, MobileDeviceIcon, RefreshIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Text, Tooltip, useToast} from '@sanity/ui'\nimport {useRef} from 'react'\nimport {useCopyToClipboard} from 'usehooks-ts'\n\nimport {DisplayUrl} from './DisplayUrl'\nimport type {IframeSizeKey, SizeProps} from './types'\n\nexport const sizes: SizeProps = {\n desktop: {\n width: '100%',\n height: '100%',\n },\n mobile: {\n width: 414,\n height: 746,\n },\n}\n\nexport const DEFAULT_SIZE = 'desktop'\n\nexport interface ToolbarProps {\n url: URL | Error | undefined\n iframeSize: IframeSizeKey\n setIframeSize: (size: IframeSizeKey) => void\n showUrl: boolean\n reloading: boolean\n reloadButton: boolean\n handleReload: () => void\n}\nexport function Toolbar(props: ToolbarProps) {\n const {url, iframeSize, setIframeSize, reloading, showUrl, reloadButton, handleReload} = props\n const validUrl = url instanceof URL\n\n const input = useRef<HTMLTextAreaElement>(null)\n const {push: pushToast} = useToast()\n const [, copy] = useCopyToClipboard()\n\n return (\n <>\n <textarea\n style={{position: 'absolute', pointerEvents: 'none', opacity: 0}}\n ref={input}\n value={validUrl ? url.toString() : ''}\n readOnly\n tabIndex={-1}\n />\n <Card padding={2} borderBottom>\n <Flex align=\"center\" gap={2}>\n <Flex align=\"center\" gap={1}>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {iframeSize === 'mobile' ? 'Exit mobile preview' : 'Preview mobile viewport'}\n </Text>\n }\n padding={2}\n placement=\"bottom-start\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n padding={2}\n mode={iframeSize === 'mobile' ? 'default' : 'ghost'}\n icon={MobileDeviceIcon}\n onClick={() => setIframeSize(iframeSize === 'mobile' ? 'desktop' : 'mobile')}\n />\n </Tooltip>\n </Flex>\n <Box flex={1}>{showUrl && validUrl && <DisplayUrl url={url} />}</Box>\n <Flex align=\"center\" gap={1}>\n {reloadButton ? (\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n {reloading ? 'Reloading…' : 'Reload'}\n </Text>\n }\n padding={2}\n >\n <Button\n disabled={!validUrl}\n mode=\"bleed\"\n fontSize={[1]}\n padding={2}\n icon={RefreshIcon}\n loading={reloading}\n aria-label=\"Reload\"\n onClick={() => handleReload()}\n />\n </Tooltip>\n ) : null}\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Copy URL\n </Text>\n }\n padding={2}\n >\n <Button\n mode=\"bleed\"\n disabled={!validUrl}\n fontSize={[1]}\n icon={CopyIcon}\n padding={[2]}\n aria-label=\"Copy URL\"\n onClick={() => {\n if (!input?.current?.value) return\n\n copy(input.current.value).then((copied) => {\n if (copied) {\n pushToast({\n closable: true,\n status: 'success',\n title: 'The URL is copied to the clipboard',\n })\n } else {\n pushToast({\n closable: true,\n status: 'error',\n title: 'Failed to copy the URL to the clipboard',\n })\n }\n })\n }}\n />\n </Tooltip>\n <Tooltip\n animate\n content={\n <Text size={1} style={{whiteSpace: 'nowrap'}}>\n Open URL in a new tab\n </Text>\n }\n padding={2}\n placement=\"bottom-end\"\n >\n <Button\n disabled={!validUrl}\n fontSize={[1]}\n icon={LaunchIcon}\n mode=\"ghost\"\n paddingY={[2]}\n text=\"Open\"\n aria-label=\"Open URL in a new tab\"\n onClick={validUrl ? () => window.open(url.toString()) : undefined}\n />\n </Tooltip>\n </Flex>\n </Flex>\n </Card>\n </>\n )\n}\n","import {WarningOutlineIcon} from '@sanity/icons'\nimport {createPreviewSecret} from '@sanity/preview-url-secret/create-secret'\nimport {definePreviewUrl} from '@sanity/preview-url-secret/define-preview-url'\nimport {Box, Card, Container, Flex, Spinner, Stack, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {AnimatePresence, HTMLMotionProps, motion, MotionConfig} from 'framer-motion'\nimport type {HTMLAttributeReferrerPolicy} from 'react'\nimport {\n forwardRef,\n memo,\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n} from 'react'\nimport {type SanityDocument, useClient, useCurrentUser, usePerspective} from 'sanity'\nimport {suspend} from 'suspend-react'\n\nimport {DEFAULT_SIZE, sizes, Toolbar} from './Toolbar'\nimport type {IframeSizeKey} from './types'\n\nexport type UrlResolver = (\n document: SanityDocument | null,\n perspective: Pick<\n ReturnType<typeof usePerspective>,\n 'selectedPerspectiveName' | 'perspectiveStack'\n >,\n) => string | Error | undefined | Promise<string | Error | undefined>\n\nexport type {IframeSizeKey}\n\nexport interface IframeOptions {\n /**\n * If you have multiple iframe instances side-by-side you need to give each a unique key.\n */\n key?: string\n url:\n | string\n | UrlResolver\n | {\n /**\n * The URL origin of where the preview is hosted, for example `https://example.com`.\n * If it's an embedded Studio then set it to `'same-origin'`.\n */\n origin: 'same-origin' | string\n /**\n * The route to redirect to after enabling Draft Mode.\n * If you don't have enough data to build the URL, return an `Error` instance to show an error message.\n * @example `return new Error('Missing slug')`\n * To prolong the loading state, return `undefined`\n */\n preview: string | UrlResolver\n /**\n * The route that enables Draft Mode\n * @example '/api/draft'\n */\n draftMode: string\n }\n defaultSize?: IframeSizeKey\n showDisplayUrl?: boolean\n reload?: {\n button?: boolean\n }\n attributes?: Partial<{\n allow: string\n referrerPolicy: HTMLAttributeReferrerPolicy | undefined\n sandbox: string\n onLoad: () => void\n }>\n}\n\ntype MotionIFrameProps = HTMLMotionProps<'iframe'> & React.IframeHTMLAttributes<HTMLIFrameElement>\n\nconst MotionFlex = motion(Flex)\nconst MotionIFrame = motion<MotionIFrameProps>('iframe')\n\nexport interface IframeProps {\n document: {\n displayed: SanityDocument\n draft: SanityDocument | null\n published: SanityDocument | null\n }\n options: IframeOptions\n}\n\nexport function Iframe(props: IframeProps) {\n const {document, options} = props\n const draft = document.draft || document.published || document.displayed\n\n const {defaultSize = DEFAULT_SIZE, reload, attributes, showDisplayUrl = true, key} = options\n\n const urlRef = useRef(options.url)\n const [draftSnapshot, setDraftSnapshot] = useState(() => ({key, draft}))\n useEffect(() => {\n urlRef.current = options.url\n }, [options.url])\n useEffect(() => {\n if (JSON.stringify({key, draft}) !== JSON.stringify(draftSnapshot)) {\n startTransition(() => setDraftSnapshot({key, draft}))\n }\n }, [draft, draftSnapshot, key])\n const currentUser = useCurrentUser()\n const client = useClient({apiVersion: '2023-10-16'})\n const [expiresAt, setExpiresAt] = useState<number | undefined>()\n const previewSecretRef = useRef<string | undefined>(undefined)\n const [isResolvingUrl, startTransition] = useTransition()\n const {perspectiveStack, selectedPerspectiveName} = usePerspective()\n const perspective = useMemo(\n () => ({\n perspectiveStack,\n selectedPerspectiveName,\n }),\n [perspectiveStack, selectedPerspectiveName],\n )\n\n const url = useCallback(\n // eslint-disable-next-line @typescript-eslint/no-shadow\n async (draft: SanityDocument | null) => {\n if (typeof location === 'undefined') {\n return undefined\n }\n const urlProp = urlRef.current\n if (typeof urlProp === 'string') {\n return new URL(urlProp, location.origin)\n }\n if (typeof urlProp === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await urlProp(draft, perspective)\n return typeof url === 'string' ? new URL(url, location.origin) : url\n }\n if (typeof urlProp === 'object') {\n const preview =\n typeof urlProp.preview === 'function'\n ? await urlProp.preview(draft, perspective)\n : urlProp.preview\n if (typeof preview !== 'string') {\n return preview\n }\n if (!previewSecretRef.current) {\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const {secret, expiresAt} = await createPreviewSecret(\n client,\n 'sanity-plugin-iframe-pane',\n location.href,\n currentUser?.id,\n )\n previewSecretRef.current = secret\n startTransition(() => setExpiresAt(expiresAt.getTime()))\n }\n\n const resolvePreviewUrl = definePreviewUrl({\n origin: urlProp.origin === 'same-origin' ? location.origin : urlProp.origin,\n preview,\n draftMode: {\n enable: urlProp.draftMode,\n },\n })\n // eslint-disable-next-line @typescript-eslint/no-shadow\n const url = await resolvePreviewUrl({\n client,\n previewUrlSecret: previewSecretRef.current,\n previewSearchParam: null,\n })\n return new URL(url, location.origin)\n }\n return undefined\n },\n [client, currentUser?.id, perspective],\n )\n useEffect(() => {\n if (expiresAt) {\n const timeout = setTimeout(\n () => {\n startTransition(() => setExpiresAt(undefined))\n previewSecretRef.current = undefined\n },\n Math.max(0, expiresAt - Date.now()),\n )\n return () => clearTimeout(timeout)\n }\n return undefined\n }, [expiresAt])\n\n return (\n <Suspense fallback={<Loading iframeSize=\"desktop\" />}>\n <IframeInner\n key={`${draftSnapshot.key}-${selectedPerspectiveName || 'draft'}`}\n _key={draftSnapshot.key}\n draftSnapshot={draftSnapshot.draft}\n url={url}\n isResolvingUrl={isResolvingUrl}\n attributes={attributes}\n perspective={perspective}\n defaultSize={defaultSize}\n reload={reload}\n showDisplayUrl={showDisplayUrl}\n userId={currentUser?.id}\n />\n </Suspense>\n )\n}\n\nexport interface IframeInnerProps extends Omit<IframeOptions, 'url'> {\n url: (draftSnapshot: SanityDocument | null) => Promise<URL | Error | undefined>\n isResolvingUrl: boolean\n draftSnapshot: SanityDocument | null\n perspective: Parameters<UrlResolver>[1]\n userId?: string\n expiresAt?: number\n _key?: string\n}\nconst IframeInner = memo(function IframeInner(props: IframeInnerProps) {\n const {\n isResolvingUrl,\n defaultSize = DEFAULT_SIZE,\n reload,\n attributes = {},\n showDisplayUrl = true,\n draftSnapshot,\n userId,\n expiresAt,\n perspective: {selectedPerspectiveName, perspectiveStack},\n _key,\n } = props\n const [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE)\n\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const url = suspend(\n () => props.url(draftSnapshot),\n [\n // Cache based on a few specific conditions\n 'sanity-plugin-iframe-pane',\n draftSnapshot,\n selectedPerspectiveName,\n perspectiveStack,\n userId,\n expiresAt,\n _key,\n resolveUUID,\n ],\n )\n\n const [loading, setLoading] = useState(true)\n const [_reloading, setReloading] = useState(false)\n const reloading = _reloading || isResolvingUrl\n\n const iframe = useRef<HTMLIFrameElement>(null)\n\n const handleReload = useCallback(() => {\n if (!iframe?.current) {\n return\n }\n\n // Funky way to reload an iframe without CORS issues\n // eslint-disable-next-line no-self-assign\n iframe.current.src = iframe.current.src\n\n setReloading(true)\n }, [])\n\n return (\n <MotionConfig transition={prefersReducedMotion ? {duration: 0} : undefined}>\n <Flex direction=\"column\" style={{height: '100%'}}>\n <Toolbar\n url={url}\n iframeSize={iframeSize}\n reloading={reloading}\n setIframeSize={setIframeSize}\n showUrl={showDisplayUrl}\n reloadButton={!!reload?.button}\n handleReload={handleReload}\n />\n {url instanceof Error ? (\n <ErrorCard error={url} />\n ) : (\n <Card tone=\"transparent\" style={{height: '100%'}}>\n <Frame\n ref={iframe}\n loading={loading}\n reloading={reloading}\n iframeSize={iframeSize}\n setReloading={setReloading}\n setLoading={setLoading}\n url={url}\n attributes={attributes}\n />\n </Card>\n )}\n </Flex>\n </MotionConfig>\n )\n})\n\ninterface FrameProps extends Required<Pick<IframeOptions, 'attributes'>> {\n loading: boolean\n reloading: boolean\n setLoading: (loading: boolean) => void\n setReloading: (reloading: boolean) => void\n iframeSize: IframeSizeKey\n url: URL | undefined\n}\nconst Frame = forwardRef(function Frame(\n props: FrameProps,\n iframe: React.ForwardedRef<HTMLIFrameElement>,\n) {\n const {loading, setLoading, iframeSize, attributes, reloading, url, setReloading} = props\n\n function handleIframeLoad() {\n setLoading(false)\n setReloading(false)\n // Run onLoad from attributes\n if (attributes.onLoad && typeof attributes.onLoad === 'function') {\n attributes.onLoad()\n }\n }\n\n return (\n <Flex align=\"center\" justify=\"center\" style={{height: '100%', position: 'relative'}}>\n <AnimatePresence>\n {!url ||\n (loading && (\n <MotionFlex\n initial=\"initial\"\n animate=\"animate\"\n exit=\"exit\"\n variants={spinnerVariants}\n justify=\"center\"\n align=\"center\"\n style={{inset: '0', position: 'absolute'}}\n >\n <Loading iframeSize={iframeSize} />\n </MotionFlex>\n ))}\n </AnimatePresence>\n {url && (\n <MotionIFrame\n ref={iframe}\n title=\"preview\"\n frameBorder=\"0\"\n style={{maxHeight: '100%'}}\n src={url.toString()}\n initial={['background', iframeSize]}\n variants={iframeVariants}\n animate={[\n loading ? 'background' : 'active',\n reloading ? 'reloading' : 'idle',\n iframeSize,\n ]}\n {...attributes}\n onLoad={handleIframeLoad}\n />\n )}\n </Flex>\n )\n})\n\nconst spinnerVariants = {\n initial: {opacity: 1},\n animate: {opacity: [0, 0, 1]},\n exit: {opacity: [1, 0, 0]},\n}\n\nconst iframeVariants = {\n ...sizes,\n desktop: {\n ...sizes.desktop,\n boxShadow: '0 0 0 0px var(--card-shadow-outline-color)',\n },\n mobile: {\n ...sizes.mobile,\n boxShadow: '0 0 0 1px var(--card-shadow-outline-color)',\n },\n background: {\n opacity: 0,\n scale: 1,\n },\n idle: {\n scale: 1,\n },\n reloading: {\n scale: [1, 1, 1, 0.98],\n },\n active: {\n opacity: [0, 0, 1],\n scale: 1,\n },\n}\n\nfunction Loading({iframeSize}: {iframeSize: IframeSizeKey}) {\n return (\n <Flex style={{...sizes[iframeSize]}} justify=\"center\" align=\"center\" direction=\"column\" gap={4}>\n <Spinner muted />\n <Text muted size={1}>\n Loading…\n </Text>\n </Flex>\n )\n}\n\nexport function ErrorCard({error}: {error: Error}) {\n return (\n <Card height=\"fill\">\n <Flex align=\"center\" height=\"fill\" justify=\"center\" padding={4} sizing=\"border\">\n <Container width={0}>\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Flex>\n <Box>\n <Text size={1}>\n <WarningOutlineIcon />\n </Text>\n </Box>\n <Stack flex={1} marginLeft={3} space={3}>\n <Text as=\"h1\" size={1} weight=\"bold\">\n {error.name}\n </Text>\n <Text as=\"p\" muted size={1}>\n {error.message}\n </Text>\n </Stack>\n </Flex>\n </Card>\n </Container>\n </Flex>\n </Card>\n )\n}\n\n// https://github.com/pmndrs/suspend-react?tab=readme-ov-file#making-cache-keys-unique\nconst resolveUUID = Symbol()\n"],"names":["draft","url","expiresAt"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-iframe-pane",
3
- "version": "3.1.5",
3
+ "version": "3.1.6-corel.1",
4
4
  "description": "Display any URL in a View Pane, along with helpful buttons to Copy the URL or open in a new tab",
5
5
  "homepage": "https://github.com/sanity-io/sanity-plugin-iframe-pane#readme",
6
6
  "bugs": {
@@ -48,13 +48,13 @@
48
48
  },
49
49
  "browserslist": "extends @sanity/browserslist-config",
50
50
  "dependencies": {
51
- "@sanity/icons": "^2.7.0",
51
+ "@sanity/icons": "^3.5.7",
52
52
  "@sanity/incompatible-plugin": "^1.0.4",
53
53
  "@sanity/preview-url-secret": "^1.6.4",
54
- "@sanity/ui": "^2.0.10",
54
+ "@sanity/ui": "^2.11.4",
55
55
  "framer-motion": "11.0.8",
56
56
  "suspend-react": "0.1.3",
57
- "usehooks-ts": "2.16.0"
57
+ "usehooks-ts": "3.0.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@commitlint/cli": "^19.2.0",
@@ -62,16 +62,16 @@
62
62
  "@sanity/pkg-utils": "^5.0.4",
63
63
  "@sanity/plugin-kit": "^3.1.10",
64
64
  "@sanity/semantic-release-preset": "^4.1.7",
65
- "@typescript-eslint/eslint-plugin": "^5.62.0",
66
- "@typescript-eslint/parser": "^5.62.0",
67
- "eslint": "^8.57.0",
65
+ "@typescript-eslint/eslint-plugin": "^7.3.1",
66
+ "@typescript-eslint/parser": "^7.3.1",
68
67
  "eslint-config-prettier": "^9.1.0",
69
68
  "eslint-config-react-app": "^7.0.1",
70
- "eslint-config-sanity": "^6.0.0",
69
+ "eslint-config-sanity": "^7.1.2",
71
70
  "eslint-plugin-import": "^2.29.1",
72
71
  "eslint-plugin-prettier": "^5.1.3",
73
- "eslint-plugin-react": "^7.34.1",
74
72
  "eslint-plugin-react-hooks": "^4.6.0",
73
+ "eslint-plugin-react": "^7.34.1",
74
+ "eslint": "^8.57.0",
75
75
  "eslint-plugin-simple-import-sort": "^12.0.0",
76
76
  "husky": "^8.0.3",
77
77
  "lint-staged": "^15.0.1",
@@ -82,7 +82,7 @@
82
82
  "react-dom": "^18.2.0",
83
83
  "react-is": "^18.2.0",
84
84
  "rimraf": "^5.0.1",
85
- "sanity": "^3.34.0",
85
+ "sanity": "3.70.1-corel.560",
86
86
  "styled-components": "^6.1.8",
87
87
  "typescript": "5.3.3"
88
88
  },
@@ -95,7 +95,6 @@
95
95
  "node": ">=14"
96
96
  },
97
97
  "publishConfig": {
98
- "access": "public",
99
- "provenance": true
98
+ "access": "public"
100
99
  }
101
100
  }
@@ -1,6 +1,6 @@
1
1
  import {getRedirectTo} from '@sanity/preview-url-secret/get-redirect-to'
2
2
  import {Text} from '@sanity/ui'
3
- import React, {useMemo} from 'react'
3
+ import {useMemo} from 'react'
4
4
 
5
5
  export function DisplayUrl(props: {url: URL}) {
6
6
  const truncatedUrl = useMemo(() => {
package/src/Iframe.tsx CHANGED
@@ -2,26 +2,31 @@ import {WarningOutlineIcon} from '@sanity/icons'
2
2
  import {createPreviewSecret} from '@sanity/preview-url-secret/create-secret'
3
3
  import {definePreviewUrl} from '@sanity/preview-url-secret/define-preview-url'
4
4
  import {Box, Card, Container, Flex, Spinner, Stack, Text, usePrefersReducedMotion} from '@sanity/ui'
5
- import {AnimatePresence, motion, MotionConfig} from 'framer-motion'
6
- import React, {
5
+ import {AnimatePresence, HTMLMotionProps, motion, MotionConfig} from 'framer-motion'
6
+ import type {HTMLAttributeReferrerPolicy} from 'react'
7
+ import {
7
8
  forwardRef,
8
9
  memo,
9
10
  Suspense,
10
11
  useCallback,
11
12
  useEffect,
13
+ useMemo,
12
14
  useRef,
13
15
  useState,
14
16
  useTransition,
15
17
  } from 'react'
16
- import {HTMLAttributeReferrerPolicy} from 'react'
17
- import {SanityDocument, useClient, useCurrentUser} from 'sanity'
18
+ import {type SanityDocument, useClient, useCurrentUser, usePerspective} from 'sanity'
18
19
  import {suspend} from 'suspend-react'
19
20
 
20
21
  import {DEFAULT_SIZE, sizes, Toolbar} from './Toolbar'
21
- import {IframeSizeKey} from './types'
22
+ import type {IframeSizeKey} from './types'
22
23
 
23
24
  export type UrlResolver = (
24
25
  document: SanityDocument | null,
26
+ perspective: Pick<
27
+ ReturnType<typeof usePerspective>,
28
+ 'selectedPerspectiveName' | 'perspectiveStack'
29
+ >,
25
30
  ) => string | Error | undefined | Promise<string | Error | undefined>
26
31
 
27
32
  export type {IframeSizeKey}
@@ -66,7 +71,10 @@ export interface IframeOptions {
66
71
  }>
67
72
  }
68
73
 
74
+ type MotionIFrameProps = HTMLMotionProps<'iframe'> & React.IframeHTMLAttributes<HTMLIFrameElement>
75
+
69
76
  const MotionFlex = motion(Flex)
77
+ const MotionIFrame = motion<MotionIFrameProps>('iframe')
70
78
 
71
79
  export interface IframeProps {
72
80
  document: {
@@ -96,8 +104,17 @@ export function Iframe(props: IframeProps) {
96
104
  const currentUser = useCurrentUser()
97
105
  const client = useClient({apiVersion: '2023-10-16'})
98
106
  const [expiresAt, setExpiresAt] = useState<number | undefined>()
99
- const previewSecretRef = useRef<string | undefined>()
107
+ const previewSecretRef = useRef<string | undefined>(undefined)
100
108
  const [isResolvingUrl, startTransition] = useTransition()
109
+ const {perspectiveStack, selectedPerspectiveName} = usePerspective()
110
+ const perspective = useMemo(
111
+ () => ({
112
+ perspectiveStack,
113
+ selectedPerspectiveName,
114
+ }),
115
+ [perspectiveStack, selectedPerspectiveName],
116
+ )
117
+
101
118
  const url = useCallback(
102
119
  // eslint-disable-next-line @typescript-eslint/no-shadow
103
120
  async (draft: SanityDocument | null) => {
@@ -110,12 +127,14 @@ export function Iframe(props: IframeProps) {
110
127
  }
111
128
  if (typeof urlProp === 'function') {
112
129
  // eslint-disable-next-line @typescript-eslint/no-shadow
113
- const url = await urlProp(draft)
130
+ const url = await urlProp(draft, perspective)
114
131
  return typeof url === 'string' ? new URL(url, location.origin) : url
115
132
  }
116
133
  if (typeof urlProp === 'object') {
117
134
  const preview =
118
- typeof urlProp.preview === 'function' ? await urlProp.preview(draft) : urlProp.preview
135
+ typeof urlProp.preview === 'function'
136
+ ? await urlProp.preview(draft, perspective)
137
+ : urlProp.preview
119
138
  if (typeof preview !== 'string') {
120
139
  return preview
121
140
  }
@@ -148,7 +167,7 @@ export function Iframe(props: IframeProps) {
148
167
  }
149
168
  return undefined
150
169
  },
151
- [client, currentUser?.id],
170
+ [client, currentUser?.id, perspective],
152
171
  )
153
172
  useEffect(() => {
154
173
  if (expiresAt) {
@@ -167,12 +186,13 @@ export function Iframe(props: IframeProps) {
167
186
  return (
168
187
  <Suspense fallback={<Loading iframeSize="desktop" />}>
169
188
  <IframeInner
170
- key={draftSnapshot.key}
189
+ key={`${draftSnapshot.key}-${selectedPerspectiveName || 'draft'}`}
171
190
  _key={draftSnapshot.key}
172
191
  draftSnapshot={draftSnapshot.draft}
173
192
  url={url}
174
193
  isResolvingUrl={isResolvingUrl}
175
194
  attributes={attributes}
195
+ perspective={perspective}
176
196
  defaultSize={defaultSize}
177
197
  reload={reload}
178
198
  showDisplayUrl={showDisplayUrl}
@@ -186,6 +206,7 @@ export interface IframeInnerProps extends Omit<IframeOptions, 'url'> {
186
206
  url: (draftSnapshot: SanityDocument | null) => Promise<URL | Error | undefined>
187
207
  isResolvingUrl: boolean
188
208
  draftSnapshot: SanityDocument | null
209
+ perspective: Parameters<UrlResolver>[1]
189
210
  userId?: string
190
211
  expiresAt?: number
191
212
  _key?: string
@@ -200,6 +221,7 @@ const IframeInner = memo(function IframeInner(props: IframeInnerProps) {
200
221
  draftSnapshot,
201
222
  userId,
202
223
  expiresAt,
224
+ perspective: {selectedPerspectiveName, perspectiveStack},
203
225
  _key,
204
226
  } = props
205
227
  const [iframeSize, setIframeSize] = useState(sizes?.[defaultSize] ? defaultSize : DEFAULT_SIZE)
@@ -212,6 +234,8 @@ const IframeInner = memo(function IframeInner(props: IframeInnerProps) {
212
234
  // Cache based on a few specific conditions
213
235
  'sanity-plugin-iframe-pane',
214
236
  draftSnapshot,
237
+ selectedPerspectiveName,
238
+ perspectiveStack,
215
239
  userId,
216
240
  expiresAt,
217
241
  _key,
@@ -239,7 +263,7 @@ const IframeInner = memo(function IframeInner(props: IframeInnerProps) {
239
263
 
240
264
  return (
241
265
  <MotionConfig transition={prefersReducedMotion ? {duration: 0} : undefined}>
242
- <Flex direction="column" style={{height: `100%`}}>
266
+ <Flex direction="column" style={{height: '100%'}}>
243
267
  <Toolbar
244
268
  url={url}
245
269
  iframeSize={iframeSize}
@@ -252,7 +276,7 @@ const IframeInner = memo(function IframeInner(props: IframeInnerProps) {
252
276
  {url instanceof Error ? (
253
277
  <ErrorCard error={url} />
254
278
  ) : (
255
- <Card tone="transparent" style={{height: `100%`}}>
279
+ <Card tone="transparent" style={{height: '100%'}}>
256
280
  <Frame
257
281
  ref={iframe}
258
282
  loading={loading}
@@ -294,7 +318,7 @@ const Frame = forwardRef(function Frame(
294
318
  }
295
319
 
296
320
  return (
297
- <Flex align="center" justify="center" style={{height: `100%`, position: `relative`}}>
321
+ <Flex align="center" justify="center" style={{height: '100%', position: 'relative'}}>
298
322
  <AnimatePresence>
299
323
  {!url ||
300
324
  (loading && (
@@ -305,14 +329,14 @@ const Frame = forwardRef(function Frame(
305
329
  variants={spinnerVariants}
306
330
  justify="center"
307
331
  align="center"
308
- style={{inset: `0`, position: `absolute`}}
332
+ style={{inset: '0', position: 'absolute'}}
309
333
  >
310
334
  <Loading iframeSize={iframeSize} />
311
335
  </MotionFlex>
312
336
  ))}
313
337
  </AnimatePresence>
314
338
  {url && (
315
- <motion.iframe
339
+ <MotionIFrame
316
340
  ref={iframe}
317
341
  title="preview"
318
342
  frameBorder="0"
package/src/Toolbar.tsx CHANGED
@@ -1,10 +1,10 @@
1
1
  import {CopyIcon, LaunchIcon, MobileDeviceIcon, RefreshIcon} from '@sanity/icons'
2
2
  import {Box, Button, Card, Flex, Text, Tooltip, useToast} from '@sanity/ui'
3
- import React, {useRef} from 'react'
3
+ import {useRef} from 'react'
4
4
  import {useCopyToClipboard} from 'usehooks-ts'
5
5
 
6
6
  import {DisplayUrl} from './DisplayUrl'
7
- import {IframeSizeKey, type SizeProps} from './types'
7
+ import type {IframeSizeKey, SizeProps} from './types'
8
8
 
9
9
  export const sizes: SizeProps = {
10
10
  desktop: {
@@ -17,7 +17,7 @@ export const sizes: SizeProps = {
17
17
  },
18
18
  }
19
19
 
20
- export const DEFAULT_SIZE = `desktop`
20
+ export const DEFAULT_SIZE = 'desktop'
21
21
 
22
22
  export interface ToolbarProps {
23
23
  url: URL | Error | undefined
@@ -39,7 +39,7 @@ export function Toolbar(props: ToolbarProps) {
39
39
  return (
40
40
  <>
41
41
  <textarea
42
- style={{position: `absolute`, pointerEvents: `none`, opacity: 0}}
42
+ style={{position: 'absolute', pointerEvents: 'none', opacity: 0}}
43
43
  ref={input}
44
44
  value={validUrl ? url.toString() : ''}
45
45
  readOnly
@@ -111,11 +111,20 @@ export function Toolbar(props: ToolbarProps) {
111
111
  onClick={() => {
112
112
  if (!input?.current?.value) return
113
113
 
114
- copy(input.current.value)
115
- pushToast({
116
- closable: true,
117
- status: 'success',
118
- title: 'The URL is copied to the clipboard',
114
+ copy(input.current.value).then((copied) => {
115
+ if (copied) {
116
+ pushToast({
117
+ closable: true,
118
+ status: 'success',
119
+ title: 'The URL is copied to the clipboard',
120
+ })
121
+ } else {
122
+ pushToast({
123
+ closable: true,
124
+ status: 'error',
125
+ title: 'Failed to copy the URL to the clipboard',
126
+ })
127
+ }
119
128
  })
120
129
  }}
121
130
  />