next-sanity 12.2.2 → 12.3.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.
Files changed (52) hide show
  1. package/dist/NextStudio.js +17 -0
  2. package/dist/NextStudio.js.map +1 -1
  3. package/dist/NextStudioNoScript.js +2 -0
  4. package/dist/NextStudioNoScript.js.map +1 -1
  5. package/dist/PresentationComlink.js +1 -1
  6. package/dist/RefreshOnFocus.js +1 -1
  7. package/dist/RefreshOnMount.js +7 -1
  8. package/dist/RefreshOnMount.js.map +1 -1
  9. package/dist/RefreshOnReconnect.js +1 -1
  10. package/dist/SanityLiveStream.js +4 -0
  11. package/dist/SanityLiveStream.js.map +1 -1
  12. package/dist/VisualEditing.js +42 -1
  13. package/dist/VisualEditing.js.map +1 -1
  14. package/dist/context.js +12 -0
  15. package/dist/context.js.map +1 -1
  16. package/dist/defineLive.d.ts +5 -0
  17. package/dist/defineLive.d.ts.map +1 -1
  18. package/dist/draft-mode/index.js +19 -2
  19. package/dist/draft-mode/index.js.map +1 -1
  20. package/dist/hooks/index.js +53 -0
  21. package/dist/hooks/index.js.map +1 -1
  22. package/dist/image/index.d.ts +1 -1
  23. package/dist/image/index.js +7 -1
  24. package/dist/image/index.js.map +1 -1
  25. package/dist/isCorsOriginError.js +1 -0
  26. package/dist/isCorsOriginError.js.map +1 -1
  27. package/dist/live/client-components/live/index.d.ts +8 -0
  28. package/dist/live/client-components/live/index.d.ts.map +1 -1
  29. package/dist/live/client-components/live/index.js +31 -6
  30. package/dist/live/client-components/live/index.js.map +1 -1
  31. package/dist/live/client-components/live-stream/index.js +6 -1
  32. package/dist/live/client-components/live-stream/index.js.map +1 -1
  33. package/dist/live/server-actions/index.js +2 -2
  34. package/dist/live.js +12 -4
  35. package/dist/live.js.map +1 -1
  36. package/dist/live.server-only.js +3 -0
  37. package/dist/live.server-only.js.map +1 -1
  38. package/dist/studio/client-component/index.js +7 -0
  39. package/dist/studio/client-component/index.js.map +1 -1
  40. package/dist/studio/index.js +40 -0
  41. package/dist/studio/index.js.map +1 -1
  42. package/dist/utils.js +1 -0
  43. package/dist/utils.js.map +1 -1
  44. package/dist/visual-editing/client-component/index.js +7 -0
  45. package/dist/visual-editing/client-component/index.js.map +1 -1
  46. package/dist/visual-editing/index.js +3 -0
  47. package/dist/visual-editing/index.js.map +1 -1
  48. package/dist/visual-editing/server-actions/index.js +2 -2
  49. package/dist/webhook/index.d.ts +1 -1
  50. package/dist/webhook/index.js +5 -0
  51. package/dist/webhook/index.js.map +1 -1
  52. package/package.json +17 -16
@@ -3,6 +3,7 @@ import { useMemo, useSyncExternalStore } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { Studio } from "sanity";
5
5
  import { createHashHistory } from "history";
6
+ /** @internal */
6
7
  function createHashHistoryForStudio() {
7
8
  const history = createHashHistory();
8
9
  return {
@@ -40,10 +41,26 @@ function createHashHistoryForStudio() {
40
41
  }
41
42
  };
42
43
  }
44
+ /** @internal */
43
45
  function useIsMounted() {
44
46
  return useSyncExternalStore(emptySubscribe, () => true, () => false);
45
47
  }
46
48
  const emptySubscribe = () => () => {};
49
+ /**
50
+ * Override how the Studio renders by passing children.
51
+ * This is useful for advanced use cases where you're using StudioProvider and StudioLayout instead of Studio:
52
+ * ```
53
+ * import {StudioProvider, StudioLayout} from 'sanity'
54
+ * import {NextStudio} from 'next-sanity/studio'
55
+ * <NextStudio config={config}>
56
+ * <StudioProvider config={config}>
57
+ * <CustomComponentThatUsesContextFromStudioProvider />
58
+ * <StudioLayout />
59
+ * </StudioProvider>
60
+ * </NextStudio>
61
+ * ```
62
+ * @public
63
+ */
47
64
  function NextStudioComponent({ children, config, unstable__noScript = true, scheme, history, ...props }) {
48
65
  const isMounted = useIsMounted();
49
66
  const unstableHistory = useMemo(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"NextStudio.js","names":[],"sources":["../src/studio/client-component/createHashHistoryForStudio.ts","../src/studio/client-component/useIsMounted.ts","../src/studio/client-component/NextStudio.tsx"],"sourcesContent":["// oxlint-disable unbound-method\nimport {createHashHistory, type History, type Listener} from 'history'\n\n/** @internal */\nexport function createHashHistoryForStudio(): History {\n const history = createHashHistory()\n return {\n get action() {\n return history.action\n },\n get location() {\n return history.location\n },\n get createHref() {\n return history.createHref\n },\n get push() {\n return history.push\n },\n get replace() {\n return history.replace\n },\n get go() {\n return history.go\n },\n get back() {\n return history.back\n },\n get forward() {\n return history.forward\n },\n get block() {\n return history.block\n },\n // Overriding listen to workaround a problem where native history provides history.listen(location => void), but the npm package is history.listen(({action, location}) => void)\n listen(listener: Listener) {\n // return history.listen(({ action, location }) => {\n return history.listen(({location}) => {\n // console.debug('history.listen', action, location)\n // @ts-expect-error -- working around a bug? in studio\n listener(location)\n })\n },\n }\n}\n","import {useSyncExternalStore} from 'react'\n\n/** @internal */\nexport function useIsMounted(): boolean {\n return useSyncExternalStore(\n emptySubscribe,\n () => true,\n () => false,\n )\n}\nconst emptySubscribe = () => () => {}\n","import {useMemo} from 'react'\nimport {Studio, type StudioProps} from 'sanity'\n\nimport {NextStudioLayout} from '../NextStudioLayout'\nimport {NextStudioNoScript} from '../NextStudioNoScript'\nimport {createHashHistoryForStudio} from './createHashHistoryForStudio'\nimport {useIsMounted} from './useIsMounted'\n\n/** @public */\nexport interface NextStudioProps extends StudioProps {\n children?: React.ReactNode\n /**\n * Render the <noscript> tag\n * @defaultValue true\n * @alpha\n */\n unstable__noScript?: boolean\n /**\n * The 'hash' option is new feature that is not yet stable for production, but is available for testing and its API won't change in a breaking way.\n * If 'hash' doesn't work for you, or if you want to use a memory based history, you can use the `unstable_history` prop instead.\n * @alpha\n * @defaultValue 'browser'\n */\n history?: 'browser' | 'hash'\n}\n/**\n * Override how the Studio renders by passing children.\n * This is useful for advanced use cases where you're using StudioProvider and StudioLayout instead of Studio:\n * ```\n * import {StudioProvider, StudioLayout} from 'sanity'\n * import {NextStudio} from 'next-sanity/studio'\n * <NextStudio config={config}>\n * <StudioProvider config={config}>\n * <CustomComponentThatUsesContextFromStudioProvider />\n * <StudioLayout />\n * </StudioProvider>\n * </NextStudio>\n * ```\n * @public\n */\nexport default function NextStudioComponent({\n children,\n config,\n unstable__noScript = true,\n scheme,\n history,\n ...props\n}: NextStudioProps): React.JSX.Element {\n const isMounted = useIsMounted()\n const unstableHistory = useMemo<typeof props.unstable_history>(() => {\n if (props.unstable_history && history) {\n throw new Error('Cannot use both `unstable_history` and `history` props at the same time')\n }\n\n if (isMounted && history === 'hash') {\n return createHashHistoryForStudio()\n }\n return props.unstable_history\n }, [history, isMounted, props.unstable_history])\n\n return (\n <>\n {unstable__noScript && <NextStudioNoScript />}\n <NextStudioLayout>\n {history === 'hash' && !isMounted\n ? null\n : children || (\n <Studio\n config={config}\n scheme={scheme}\n unstable_globalStyles\n {...props}\n unstable_history={unstableHistory}\n />\n )}\n </NextStudioLayout>\n </>\n )\n}\n"],"mappings":";;;;;AAIA,SAAgB,6BAAsC;CACpD,MAAM,UAAU,mBAAmB;AACnC,QAAO;EACL,IAAI,SAAS;AACX,UAAO,QAAQ;;EAEjB,IAAI,WAAW;AACb,UAAO,QAAQ;;EAEjB,IAAI,aAAa;AACf,UAAO,QAAQ;;EAEjB,IAAI,OAAO;AACT,UAAO,QAAQ;;EAEjB,IAAI,UAAU;AACZ,UAAO,QAAQ;;EAEjB,IAAI,KAAK;AACP,UAAO,QAAQ;;EAEjB,IAAI,OAAO;AACT,UAAO,QAAQ;;EAEjB,IAAI,UAAU;AACZ,UAAO,QAAQ;;EAEjB,IAAI,QAAQ;AACV,UAAO,QAAQ;;EAGjB,OAAO,UAAoB;AAEzB,UAAO,QAAQ,QAAQ,EAAC,eAAc;AAGpC,aAAS,SAAS;KAClB;;EAEL;;ACxCH,SAAgB,eAAwB;AACtC,QAAO,qBACL,sBACM,YACA,MACP;;AAEH,MAAM,6BAA6B;AC8BnC,SAAwB,oBAAoB,EAC1C,UACA,QACA,qBAAqB,MACrB,QACA,SACA,GAAG,SACkC;CACrC,MAAM,YAAY,cAAc;CAChC,MAAM,kBAAkB,cAA6C;AACnE,MAAI,MAAM,oBAAoB,QAC5B,OAAM,IAAI,MAAM,0EAA0E;AAG5F,MAAI,aAAa,YAAY,OAC3B,QAAO,4BAA4B;AAErC,SAAO,MAAM;IACZ;EAAC;EAAS;EAAW,MAAM;EAAiB,CAAC;AAEhD,QACE,qBAAA,UAAA,EAAA,UAAA,CACG,sBAAsB,oBAAC,oBAAA,EAAA,CAAqB,EAC7C,oBAAC,kBAAA,EAAA,UACE,YAAY,UAAU,CAAC,YACpB,OACA,YACE,oBAAC,QAAA;EACS;EACA;EACR,uBAAA;EACA,GAAI;EACJ,kBAAkB;GAClB,EAAA,CAES,CAAA,EAAA,CAClB"}
1
+ {"version":3,"file":"NextStudio.js","names":[],"sources":["../src/studio/client-component/createHashHistoryForStudio.ts","../src/studio/client-component/useIsMounted.ts","../src/studio/client-component/NextStudio.tsx"],"sourcesContent":["// oxlint-disable unbound-method\nimport {createHashHistory, type History, type Listener} from 'history'\n\n/** @internal */\nexport function createHashHistoryForStudio(): History {\n const history = createHashHistory()\n return {\n get action() {\n return history.action\n },\n get location() {\n return history.location\n },\n get createHref() {\n return history.createHref\n },\n get push() {\n return history.push\n },\n get replace() {\n return history.replace\n },\n get go() {\n return history.go\n },\n get back() {\n return history.back\n },\n get forward() {\n return history.forward\n },\n get block() {\n return history.block\n },\n // Overriding listen to workaround a problem where native history provides history.listen(location => void), but the npm package is history.listen(({action, location}) => void)\n listen(listener: Listener) {\n // return history.listen(({ action, location }) => {\n return history.listen(({location}) => {\n // console.debug('history.listen', action, location)\n // @ts-expect-error -- working around a bug? in studio\n listener(location)\n })\n },\n }\n}\n","import {useSyncExternalStore} from 'react'\n\n/** @internal */\nexport function useIsMounted(): boolean {\n return useSyncExternalStore(\n emptySubscribe,\n () => true,\n () => false,\n )\n}\nconst emptySubscribe = () => () => {}\n","import {useMemo} from 'react'\nimport {Studio, type StudioProps} from 'sanity'\n\nimport {NextStudioLayout} from '../NextStudioLayout'\nimport {NextStudioNoScript} from '../NextStudioNoScript'\nimport {createHashHistoryForStudio} from './createHashHistoryForStudio'\nimport {useIsMounted} from './useIsMounted'\n\n/** @public */\nexport interface NextStudioProps extends StudioProps {\n children?: React.ReactNode\n /**\n * Render the <noscript> tag\n * @defaultValue true\n * @alpha\n */\n unstable__noScript?: boolean\n /**\n * The 'hash' option is new feature that is not yet stable for production, but is available for testing and its API won't change in a breaking way.\n * If 'hash' doesn't work for you, or if you want to use a memory based history, you can use the `unstable_history` prop instead.\n * @alpha\n * @defaultValue 'browser'\n */\n history?: 'browser' | 'hash'\n}\n/**\n * Override how the Studio renders by passing children.\n * This is useful for advanced use cases where you're using StudioProvider and StudioLayout instead of Studio:\n * ```\n * import {StudioProvider, StudioLayout} from 'sanity'\n * import {NextStudio} from 'next-sanity/studio'\n * <NextStudio config={config}>\n * <StudioProvider config={config}>\n * <CustomComponentThatUsesContextFromStudioProvider />\n * <StudioLayout />\n * </StudioProvider>\n * </NextStudio>\n * ```\n * @public\n */\nexport default function NextStudioComponent({\n children,\n config,\n unstable__noScript = true,\n scheme,\n history,\n ...props\n}: NextStudioProps): React.JSX.Element {\n const isMounted = useIsMounted()\n const unstableHistory = useMemo<typeof props.unstable_history>(() => {\n if (props.unstable_history && history) {\n throw new Error('Cannot use both `unstable_history` and `history` props at the same time')\n }\n\n if (isMounted && history === 'hash') {\n return createHashHistoryForStudio()\n }\n return props.unstable_history\n }, [history, isMounted, props.unstable_history])\n\n return (\n <>\n {unstable__noScript && <NextStudioNoScript />}\n <NextStudioLayout>\n {history === 'hash' && !isMounted\n ? null\n : children || (\n <Studio\n config={config}\n scheme={scheme}\n unstable_globalStyles\n {...props}\n unstable_history={unstableHistory}\n />\n )}\n </NextStudioLayout>\n </>\n )\n}\n"],"mappings":";;;;;;AAIA,SAAgB,6BAAsC;CACpD,MAAM,UAAU,mBAAmB;AACnC,QAAO;EACL,IAAI,SAAS;AACX,UAAO,QAAQ;;EAEjB,IAAI,WAAW;AACb,UAAO,QAAQ;;EAEjB,IAAI,aAAa;AACf,UAAO,QAAQ;;EAEjB,IAAI,OAAO;AACT,UAAO,QAAQ;;EAEjB,IAAI,UAAU;AACZ,UAAO,QAAQ;;EAEjB,IAAI,KAAK;AACP,UAAO,QAAQ;;EAEjB,IAAI,OAAO;AACT,UAAO,QAAQ;;EAEjB,IAAI,UAAU;AACZ,UAAO,QAAQ;;EAEjB,IAAI,QAAQ;AACV,UAAO,QAAQ;;EAGjB,OAAO,UAAoB;AAEzB,UAAO,QAAQ,QAAQ,EAAC,eAAc;AAGpC,aAAS,SAAS;KAClB;;EAEL;;;ACxCH,SAAgB,eAAwB;AACtC,QAAO,qBACL,sBACM,YACA,MACP;;AAEH,MAAM,6BAA6B;;;;;;;;;;;;;;;;AC8BnC,SAAwB,oBAAoB,EAC1C,UACA,QACA,qBAAqB,MACrB,QACA,SACA,GAAG,SACkC;CACrC,MAAM,YAAY,cAAc;CAChC,MAAM,kBAAkB,cAA6C;AACnE,MAAI,MAAM,oBAAoB,QAC5B,OAAM,IAAI,MAAM,0EAA0E;AAG5F,MAAI,aAAa,YAAY,OAC3B,QAAO,4BAA4B;AAErC,SAAO,MAAM;IACZ;EAAC;EAAS;EAAW,MAAM;EAAiB,CAAC;AAEhD,QACE,qBAAA,UAAA,EAAA,UAAA,CACG,sBAAsB,oBAAC,oBAAD,EAAsB,CAAA,EAC7C,oBAAC,kBAAD,EAAA,UACG,YAAY,UAAU,CAAC,YACpB,OACA,YACE,oBAAC,QAAD;EACU;EACA;EACR,uBAAA;EACA,GAAI;EACJ,kBAAkB;EAClB,CAAA,EAES,CAAA,CAClB,EAAA,CAAA"}
@@ -6,6 +6,7 @@ const style = {
6
6
  WebkitFontSmoothing: "antialiased",
7
7
  overflow: "auto"
8
8
  };
9
+ /** @public */
9
10
  const NextStudioLayout = ({ children }) => {
10
11
  return /* @__PURE__ */ jsx("div", {
11
12
  id: "sanity",
@@ -33,6 +34,7 @@ const styles = {
33
34
  fontFamily: "helvetica, arial, sans-serif"
34
35
  }
35
36
  };
37
+ /** @internal */
36
38
  const NextStudioNoScript = () => /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("div", {
37
39
  style: styles.outer,
38
40
  children: /* @__PURE__ */ jsxs("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"NextStudioNoScript.js","names":[],"sources":["../src/studio/NextStudioLayout.tsx","../src/studio/NextStudioNoScript.tsx"],"sourcesContent":["/** @public */\nexport interface NextStudioLayoutProps {\n children: React.ReactNode\n}\n\nconst style = {\n height: '100vh',\n maxHeight: '100dvh',\n overscrollBehavior: 'none',\n WebkitFontSmoothing: 'antialiased',\n overflow: 'auto',\n} satisfies React.CSSProperties\n\n/** @public */\nexport const NextStudioLayout = ({children}: NextStudioLayoutProps): React.JSX.Element => {\n return (\n <div id=\"sanity\" data-ui=\"NextStudioLayout\" style={style}>\n {children}\n </div>\n )\n}\n","const styles: Record<'outer' | 'inner', React.CSSProperties> = {\n outer: {\n position: 'absolute',\n top: 0,\n right: 0,\n left: 0,\n bottom: 0,\n background: '#fff',\n zIndex: 1,\n },\n inner: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n textAlign: 'center',\n fontFamily: 'helvetica, arial, sans-serif',\n },\n}\n\n/** @internal */\nexport const NextStudioNoScript = (): React.JSX.Element => (\n <noscript>\n <div style={styles.outer}>\n <div style={styles.inner}>\n <h1>JavaScript disabled</h1>\n <p>\n Please <a href=\"https://www.enable-javascript.com/\">enable JavaScript</a> in your browser\n and reload the page to proceed.\n </p>\n </div>\n </div>\n </noscript>\n)\n"],"mappings":";AAKA,MAAM,QAAQ;CACZ,QAAQ;CACR,WAAW;CACX,oBAAoB;CACpB,qBAAqB;CACrB,UAAU;CACX;AAGD,MAAa,oBAAoB,EAAC,eAAwD;AACxF,QACE,oBAAC,OAAA;EAAI,IAAG;EAAS,WAAQ;EAA0B;EAChD;GACG;;AClBV,MAAM,SAAyD;CAC7D,OAAO;EACL,UAAU;EACV,KAAK;EACL,OAAO;EACP,MAAM;EACN,QAAQ;EACR,YAAY;EACZ,QAAQ;EACT;CACD,OAAO;EACL,UAAU;EACV,KAAK;EACL,MAAM;EACN,WAAW;EACX,WAAW;EACX,YAAY;EACb;CACF;AAGD,MAAa,2BACX,oBAAC,YAAA,EAAA,UACC,oBAAC,OAAA;CAAI,OAAO,OAAO;WACjB,qBAAC,OAAA;EAAI,OAAO,OAAO;aACjB,oBAAC,MAAA,EAAA,UAAG,uBAAA,CAAwB,EAC5B,qBAAC,KAAA,EAAA,UAAA;GAAE;GACM,oBAAC,KAAA;IAAE,MAAK;cAAqC;KAAqB;;MAEvE,CAAA;GACA;EACF,EAAA,CACG"}
1
+ {"version":3,"file":"NextStudioNoScript.js","names":[],"sources":["../src/studio/NextStudioLayout.tsx","../src/studio/NextStudioNoScript.tsx"],"sourcesContent":["/** @public */\nexport interface NextStudioLayoutProps {\n children: React.ReactNode\n}\n\nconst style = {\n height: '100vh',\n maxHeight: '100dvh',\n overscrollBehavior: 'none',\n WebkitFontSmoothing: 'antialiased',\n overflow: 'auto',\n} satisfies React.CSSProperties\n\n/** @public */\nexport const NextStudioLayout = ({children}: NextStudioLayoutProps): React.JSX.Element => {\n return (\n <div id=\"sanity\" data-ui=\"NextStudioLayout\" style={style}>\n {children}\n </div>\n )\n}\n","const styles: Record<'outer' | 'inner', React.CSSProperties> = {\n outer: {\n position: 'absolute',\n top: 0,\n right: 0,\n left: 0,\n bottom: 0,\n background: '#fff',\n zIndex: 1,\n },\n inner: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n textAlign: 'center',\n fontFamily: 'helvetica, arial, sans-serif',\n },\n}\n\n/** @internal */\nexport const NextStudioNoScript = (): React.JSX.Element => (\n <noscript>\n <div style={styles.outer}>\n <div style={styles.inner}>\n <h1>JavaScript disabled</h1>\n <p>\n Please <a href=\"https://www.enable-javascript.com/\">enable JavaScript</a> in your browser\n and reload the page to proceed.\n </p>\n </div>\n </div>\n </noscript>\n)\n"],"mappings":";AAKA,MAAM,QAAQ;CACZ,QAAQ;CACR,WAAW;CACX,oBAAoB;CACpB,qBAAqB;CACrB,UAAU;CACX;;AAGD,MAAa,oBAAoB,EAAC,eAAwD;AACxF,QACE,oBAAC,OAAD;EAAK,IAAG;EAAS,WAAQ;EAA0B;EAChD;EACG,CAAA;;AClBV,MAAM,SAAyD;CAC7D,OAAO;EACL,UAAU;EACV,KAAK;EACL,OAAO;EACP,MAAM;EACN,QAAQ;EACR,YAAY;EACZ,QAAQ;EACT;CACD,OAAO;EACL,UAAU;EACV,KAAK;EACL,MAAM;EACN,WAAW;EACX,WAAW;EACX,YAAY;EACb;CACF;;AAGD,MAAa,2BACX,oBAAC,YAAD,EAAA,UACE,oBAAC,OAAD;CAAK,OAAO,OAAO;WACjB,qBAAC,OAAD;EAAK,OAAO,OAAO;YAAnB,CACE,oBAAC,MAAD,EAAA,UAAI,uBAAwB,CAAA,EAC5B,qBAAC,KAAD,EAAA,UAAA;GAAG;GACM,oBAAC,KAAD;IAAG,MAAK;cAAqC;IAAqB,CAAA;;GAEvE,EAAA,CAAA,CACA;;CACF,CAAA,EACG,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { l as setComlink, u as setComlinkClientConfig } from "./context.js";
2
- import { useRouter } from "next/navigation";
2
+ import { useRouter } from "next/navigation.js";
3
3
  import { useEffect, useEffectEvent } from "react";
4
4
  import { createNode, createNodeMachine } from "@sanity/comlink";
5
5
  import { createCompatibilityActors } from "@sanity/presentation-comlink";
@@ -1,4 +1,4 @@
1
- import { useRouter } from "next/navigation";
1
+ import { useRouter } from "next/navigation.js";
2
2
  import { useEffect } from "react";
3
3
  const focusThrottleInterval = 5e3;
4
4
  function RefreshOnFocus() {
@@ -1,5 +1,11 @@
1
- import { useRouter } from "next/navigation";
1
+ import { useRouter } from "next/navigation.js";
2
2
  import { useEffect, useReducer } from "react";
3
+ /**
4
+ * Handles refreshing the page when the page is mounted,
5
+ * in case the content changes at a high enough frequency that by
6
+ * the time the page started streaming, and the <SanityLive> component sets
7
+ * up the EventSource connection, content might have changed.
8
+ */
3
9
  function RefreshOnMount() {
4
10
  const router = useRouter();
5
11
  const [mounted, mount] = useReducer(() => true, false);
@@ -1 +1 @@
1
- {"version":3,"file":"RefreshOnMount.js","names":[],"sources":["../src/live/client-components/live/RefreshOnMount.tsx"],"sourcesContent":["/**\n * Handles refreshing the page when the page is mounted,\n * in case the content changes at a high enough frequency that by\n * the time the page started streaming, and the <SanityLive> component sets\n * up the EventSource connection, content might have changed.\n */\n\nimport {useRouter} from 'next/navigation'\nimport {useEffect, useReducer} from 'react'\n\nexport default function RefreshOnMount(): null {\n const router = useRouter()\n const [mounted, mount] = useReducer(() => true, false)\n\n useEffect(() => {\n if (!mounted) {\n mount()\n router.refresh()\n }\n }, [mounted, router])\n\n return null\n}\nRefreshOnMount.displayName = 'RefreshOnMount'\n"],"mappings":";;AAUA,SAAwB,iBAAuB;CAC7C,MAAM,SAAS,WAAW;CAC1B,MAAM,CAAC,SAAS,SAAS,iBAAiB,MAAM,MAAM;AAEtD,iBAAgB;AACd,MAAI,CAAC,SAAS;AACZ,UAAO;AACP,UAAO,SAAS;;IAEjB,CAAC,SAAS,OAAO,CAAC;AAErB,QAAO;;AAET,eAAe,cAAc"}
1
+ {"version":3,"file":"RefreshOnMount.js","names":[],"sources":["../src/live/client-components/live/RefreshOnMount.tsx"],"sourcesContent":["/**\n * Handles refreshing the page when the page is mounted,\n * in case the content changes at a high enough frequency that by\n * the time the page started streaming, and the <SanityLive> component sets\n * up the EventSource connection, content might have changed.\n */\n\nimport {useRouter} from 'next/navigation'\nimport {useEffect, useReducer} from 'react'\n\nexport default function RefreshOnMount(): null {\n const router = useRouter()\n const [mounted, mount] = useReducer(() => true, false)\n\n useEffect(() => {\n if (!mounted) {\n mount()\n router.refresh()\n }\n }, [mounted, router])\n\n return null\n}\nRefreshOnMount.displayName = 'RefreshOnMount'\n"],"mappings":";;;;;;;;AAUA,SAAwB,iBAAuB;CAC7C,MAAM,SAAS,WAAW;CAC1B,MAAM,CAAC,SAAS,SAAS,iBAAiB,MAAM,MAAM;AAEtD,iBAAgB;AACd,MAAI,CAAC,SAAS;AACZ,UAAO;AACP,UAAO,SAAS;;IAEjB,CAAC,SAAS,OAAO,CAAC;AAErB,QAAO;;AAET,eAAe,cAAc"}
@@ -1,4 +1,4 @@
1
- import { useRouter } from "next/navigation";
1
+ import { useRouter } from "next/navigation.js";
2
2
  import { useEffect } from "react";
3
3
  function RefreshOnReconnect() {
4
4
  const router = useRouter();
@@ -3,7 +3,11 @@ import { use, useCallback, useEffect, useEffectEvent, useState, useSyncExternalS
3
3
  import { stegaEncodeSourceMap } from "@sanity/client/stega";
4
4
  import { dequal } from "dequal/lite";
5
5
  import { Fragment, jsx } from "react/jsx-runtime";
6
+ import "@sanity/client";
6
7
  const LISTEN_HEARTBEAT_INTERVAL = 1e4;
8
+ /**
9
+ * @public
10
+ */
7
11
  function SanityLiveStream(props) {
8
12
  const { query, dataset, params = {}, perspective, projectId, stega } = props;
9
13
  const comlink$1 = useSyncExternalStore(useCallback((listener) => {
@@ -1 +1 @@
1
- {"version":3,"file":"SanityLiveStream.js","names":["comlink","comlinkSnapshot"],"sources":["../src/live/client-components/live-stream/SanityLiveStream.tsx"],"sourcesContent":["// oxlint-disable no-unsafe-type-assertion\nimport {\n type ClientPerspective,\n type ContentSourceMap,\n type InitializedClientConfig,\n type QueryParams,\n} from '@sanity/client'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport type {LoaderControllerMsg} from '@sanity/presentation-comlink'\nimport {dequal} from 'dequal/lite'\nimport {use, useCallback, useEffect, useState, useSyncExternalStore, useEffectEvent} from 'react'\n\nimport {comlinkListeners, comlink as comlinkSnapshot} from '../../hooks/context'\n\n/**\n * @public\n */\nexport interface SanityLiveStreamProps extends Pick<\n InitializedClientConfig,\n 'projectId' | 'dataset'\n> {\n query: string\n params?: QueryParams\n perspective?: Exclude<ClientPerspective, 'raw'>\n stega?: boolean\n initial: Promise<React.ReactNode>\n children: (result: {\n data: unknown\n sourceMap: ContentSourceMap | null\n tags: string[]\n }) => Promise<React.ReactNode>\n}\n\nconst LISTEN_HEARTBEAT_INTERVAL = 10_000\n\n/**\n * @public\n */\nexport default function SanityLiveStream(props: SanityLiveStreamProps): React.JSX.Element | null {\n const {query, dataset, params = {}, perspective, projectId, stega} = props\n\n const subscribe = useCallback((listener: () => void) => {\n comlinkListeners.add(listener)\n return () => comlinkListeners.delete(listener)\n }, [])\n\n const comlink = useSyncExternalStore(\n subscribe,\n () => comlinkSnapshot,\n () => null,\n )\n const [children, setChildren] = useState<React.ReactNode | undefined>(undefined)\n\n const handleQueryHeartbeat = useEffectEvent((comlink: NonNullable<typeof comlinkSnapshot>) => {\n comlink.post('loader/query-listen', {\n projectId: projectId!,\n dataset: dataset!,\n perspective: perspective! as ClientPerspective,\n query,\n params: params,\n heartbeat: LISTEN_HEARTBEAT_INTERVAL,\n })\n })\n const handleQueryChange = useEffectEvent(\n (event: Extract<LoaderControllerMsg, {type: 'loader/query-change'}>['data']) => {\n if (\n dequal(\n {\n projectId,\n dataset,\n query,\n params,\n },\n {\n projectId: event.projectId,\n dataset: event.dataset,\n query: event.query,\n params: event.params,\n },\n )\n ) {\n const {result, resultSourceMap, tags} = event\n const data = stega\n ? stegaEncodeSourceMap(result, resultSourceMap, {enabled: true, studioUrl: '/'})\n : result\n // console.log('server function streaming is disabled', {\n // startTransition,\n // setPromise,\n // data,\n // resultSourceMap,\n // tags,\n // })\n // console.log('rendering with server action')\n // startTransition(() =>\n // setPromise(\n // props.children({\n // data,\n // sourceMap: resultSourceMap!,\n // tags: tags || [],\n // }) as Promise<React.JSX.Element>,\n // ),\n // )\n // oxlint-disable-next-line no-console\n console.groupCollapsed('rendering with server action')\n ;(\n props.children({\n data,\n sourceMap: resultSourceMap!,\n tags: tags || [],\n }) as Promise<React.JSX.Element>\n )\n .then(\n (children) => {\n // oxlint-disable-next-line no-console\n console.log('setChildren(children)')\n // startTransition(() => setChildren(children))\n setChildren(children)\n },\n (reason: unknown) => {\n console.error('rendering with server action: render children error', reason)\n },\n )\n // oxlint-disable-next-line no-console\n .finally(() => console.groupEnd())\n }\n },\n )\n useEffect(() => {\n if (!comlink) return\n\n const unsubscribe = comlink.on('loader/query-change', handleQueryChange)\n const interval = setInterval(() => handleQueryHeartbeat(comlink), LISTEN_HEARTBEAT_INTERVAL)\n return () => {\n clearInterval(interval)\n unsubscribe()\n }\n }, [comlink])\n\n if (!comlink || children === undefined) {\n return use(props.initial) as React.JSX.Element\n }\n\n return <>{children}</>\n}\n"],"mappings":";;;;;AAiCA,MAAM,4BAA4B;AAKlC,SAAwB,iBAAiB,OAAwD;CAC/F,MAAM,EAAC,OAAO,SAAS,SAAS,EAAE,EAAE,aAAa,WAAW,UAAS;CAOrE,MAAMA,YAAU,qBALE,aAAa,aAAyB;AACtD,mBAAiB,IAAI,SAAS;AAC9B,eAAa,iBAAiB,OAAO,SAAS;IAC7C,EAAE,CAAC,QAIEC,eACA,KACP;CACD,MAAM,CAAC,UAAU,eAAe,SAAsC,KAAA,EAAU;CAEhF,MAAM,uBAAuB,gBAAgB,YAAiD;AAC5F,UAAQ,KAAK,uBAAuB;GACvB;GACF;GACI;GACb;GACQ;GACR,WAAW;GACZ,CAAC;GACF;CACF,MAAM,oBAAoB,gBACvB,UAA+E;AAC9E,MACE,OACE;GACE;GACA;GACA;GACA;GACD,EACD;GACE,WAAW,MAAM;GACjB,SAAS,MAAM;GACf,OAAO,MAAM;GACb,QAAQ,MAAM;GACf,CACF,EACD;GACA,MAAM,EAAC,QAAQ,iBAAiB,SAAQ;GACxC,MAAM,OAAO,QACT,qBAAqB,QAAQ,iBAAiB;IAAC,SAAS;IAAM,WAAW;IAAI,CAAC,GAC9E;AAmBJ,WAAQ,eAAe,+BAA+B;AAEpD,SAAM,SAAS;IACb;IACA,WAAW;IACX,MAAM,QAAQ,EAAE;IACjB,CAAC,CAED,MACE,aAAa;AAEZ,YAAQ,IAAI,wBAAwB;AAEpC,gBAAY,SAAS;OAEtB,WAAoB;AACnB,YAAQ,MAAM,uDAAuD,OAAO;KAE/E,CAEA,cAAc,QAAQ,UAAU,CAAC;;GAGzC;AACD,iBAAgB;AACd,MAAI,CAACD,UAAS;EAEd,MAAM,cAAcA,UAAQ,GAAG,uBAAuB,kBAAkB;EACxE,MAAM,WAAW,kBAAkB,qBAAqBA,UAAQ,EAAE,0BAA0B;AAC5F,eAAa;AACX,iBAAc,SAAS;AACvB,gBAAa;;IAEd,CAACA,UAAQ,CAAC;AAEb,KAAI,CAACA,aAAW,aAAa,KAAA,EAC3B,QAAO,IAAI,MAAM,QAAQ;AAG3B,QAAO,oBAAA,UAAA,EAAG,UAAA,CAAY"}
1
+ {"version":3,"file":"SanityLiveStream.js","names":["comlink","comlinkSnapshot"],"sources":["../src/live/client-components/live-stream/SanityLiveStream.tsx"],"sourcesContent":["// oxlint-disable no-unsafe-type-assertion\nimport {\n type ClientPerspective,\n type ContentSourceMap,\n type InitializedClientConfig,\n type QueryParams,\n} from '@sanity/client'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport type {LoaderControllerMsg} from '@sanity/presentation-comlink'\nimport {dequal} from 'dequal/lite'\nimport {use, useCallback, useEffect, useState, useSyncExternalStore, useEffectEvent} from 'react'\n\nimport {comlinkListeners, comlink as comlinkSnapshot} from '../../hooks/context'\n\n/**\n * @public\n */\nexport interface SanityLiveStreamProps extends Pick<\n InitializedClientConfig,\n 'projectId' | 'dataset'\n> {\n query: string\n params?: QueryParams\n perspective?: Exclude<ClientPerspective, 'raw'>\n stega?: boolean\n initial: Promise<React.ReactNode>\n children: (result: {\n data: unknown\n sourceMap: ContentSourceMap | null\n tags: string[]\n }) => Promise<React.ReactNode>\n}\n\nconst LISTEN_HEARTBEAT_INTERVAL = 10_000\n\n/**\n * @public\n */\nexport default function SanityLiveStream(props: SanityLiveStreamProps): React.JSX.Element | null {\n const {query, dataset, params = {}, perspective, projectId, stega} = props\n\n const subscribe = useCallback((listener: () => void) => {\n comlinkListeners.add(listener)\n return () => comlinkListeners.delete(listener)\n }, [])\n\n const comlink = useSyncExternalStore(\n subscribe,\n () => comlinkSnapshot,\n () => null,\n )\n const [children, setChildren] = useState<React.ReactNode | undefined>(undefined)\n\n const handleQueryHeartbeat = useEffectEvent((comlink: NonNullable<typeof comlinkSnapshot>) => {\n comlink.post('loader/query-listen', {\n projectId: projectId!,\n dataset: dataset!,\n perspective: perspective! as ClientPerspective,\n query,\n params: params,\n heartbeat: LISTEN_HEARTBEAT_INTERVAL,\n })\n })\n const handleQueryChange = useEffectEvent(\n (event: Extract<LoaderControllerMsg, {type: 'loader/query-change'}>['data']) => {\n if (\n dequal(\n {\n projectId,\n dataset,\n query,\n params,\n },\n {\n projectId: event.projectId,\n dataset: event.dataset,\n query: event.query,\n params: event.params,\n },\n )\n ) {\n const {result, resultSourceMap, tags} = event\n const data = stega\n ? stegaEncodeSourceMap(result, resultSourceMap, {enabled: true, studioUrl: '/'})\n : result\n // console.log('server function streaming is disabled', {\n // startTransition,\n // setPromise,\n // data,\n // resultSourceMap,\n // tags,\n // })\n // console.log('rendering with server action')\n // startTransition(() =>\n // setPromise(\n // props.children({\n // data,\n // sourceMap: resultSourceMap!,\n // tags: tags || [],\n // }) as Promise<React.JSX.Element>,\n // ),\n // )\n // oxlint-disable-next-line no-console\n console.groupCollapsed('rendering with server action')\n ;(\n props.children({\n data,\n sourceMap: resultSourceMap!,\n tags: tags || [],\n }) as Promise<React.JSX.Element>\n )\n .then(\n (children) => {\n // oxlint-disable-next-line no-console\n console.log('setChildren(children)')\n // startTransition(() => setChildren(children))\n setChildren(children)\n },\n (reason: unknown) => {\n console.error('rendering with server action: render children error', reason)\n },\n )\n // oxlint-disable-next-line no-console\n .finally(() => console.groupEnd())\n }\n },\n )\n useEffect(() => {\n if (!comlink) return\n\n const unsubscribe = comlink.on('loader/query-change', handleQueryChange)\n const interval = setInterval(() => handleQueryHeartbeat(comlink), LISTEN_HEARTBEAT_INTERVAL)\n return () => {\n clearInterval(interval)\n unsubscribe()\n }\n }, [comlink])\n\n if (!comlink || children === undefined) {\n return use(props.initial) as React.JSX.Element\n }\n\n return <>{children}</>\n}\n"],"mappings":";;;;;;AAiCA,MAAM,4BAA4B;;;;AAKlC,SAAwB,iBAAiB,OAAwD;CAC/F,MAAM,EAAC,OAAO,SAAS,SAAS,EAAE,EAAE,aAAa,WAAW,UAAS;CAOrE,MAAMA,YAAU,qBALE,aAAa,aAAyB;AACtD,mBAAiB,IAAI,SAAS;AAC9B,eAAa,iBAAiB,OAAO,SAAS;IAC7C,EAAE,CAGM,QACHC,eACA,KACP;CACD,MAAM,CAAC,UAAU,eAAe,SAAsC,KAAA,EAAU;CAEhF,MAAM,uBAAuB,gBAAgB,YAAiD;AAC5F,UAAQ,KAAK,uBAAuB;GACvB;GACF;GACI;GACb;GACQ;GACR,WAAW;GACZ,CAAC;GACF;CACF,MAAM,oBAAoB,gBACvB,UAA+E;AAC9E,MACE,OACE;GACE;GACA;GACA;GACA;GACD,EACD;GACE,WAAW,MAAM;GACjB,SAAS,MAAM;GACf,OAAO,MAAM;GACb,QAAQ,MAAM;GACf,CACF,EACD;GACA,MAAM,EAAC,QAAQ,iBAAiB,SAAQ;GACxC,MAAM,OAAO,QACT,qBAAqB,QAAQ,iBAAiB;IAAC,SAAS;IAAM,WAAW;IAAI,CAAC,GAC9E;AAmBJ,WAAQ,eAAe,+BAA+B;AAEpD,SAAM,SAAS;IACb;IACA,WAAW;IACX,MAAM,QAAQ,EAAE;IACjB,CAAC,CAED,MACE,aAAa;AAEZ,YAAQ,IAAI,wBAAwB;AAEpC,gBAAY,SAAS;OAEtB,WAAoB;AACnB,YAAQ,MAAM,uDAAuD,OAAO;KAE/E,CAEA,cAAc,QAAQ,UAAU,CAAC;;GAGzC;AACD,iBAAgB;AACd,MAAI,CAACD,UAAS;EAEd,MAAM,cAAcA,UAAQ,GAAG,uBAAuB,kBAAkB;EACxE,MAAM,WAAW,kBAAkB,qBAAqBA,UAAQ,EAAE,0BAA0B;AAC5F,eAAa;AACX,iBAAc,SAAS;AACvB,gBAAa;;IAEd,CAACA,UAAQ,CAAC;AAEb,KAAI,CAACA,aAAW,aAAa,KAAA,EAC3B,QAAO,IAAI,MAAM,QAAQ;AAG3B,QAAO,oBAAA,UAAA,EAAG,UAAY,CAAA"}
@@ -1,13 +1,27 @@
1
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
1
+ import { usePathname, useRouter, useSearchParams } from "next/navigation.js";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { VisualEditing as VisualEditing$1 } from "@sanity/visual-editing/react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { revalidateRootLayout } from "next-sanity/visual-editing/server-actions";
6
+ /**
7
+ * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/path-has-prefix.ts#L10-L17
8
+ * Checks if a given path starts with a given prefix. It ensures it matches
9
+ * exactly without containing extra chars. e.g. prefix /docs should replace
10
+ * for /docs, /docs/, /docs/a but not /docsss
11
+ * @param path The path to check.
12
+ * @param prefix The prefix to check against.
13
+ */
6
14
  function pathHasPrefix(path, prefix) {
7
15
  if (typeof path !== "string") return false;
8
16
  const { pathname } = parsePath(path);
9
17
  return pathname === prefix || pathname.startsWith(`${prefix}/`);
10
18
  }
19
+ /**
20
+ * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/parse-path.ts#L6-L22
21
+ * Given a path this function will find the pathname, query and hash and return
22
+ * them. This is useful to parse full paths on the client side.
23
+ * @param path A path to parse e.g. /foo/bar?id=1#hash
24
+ */
11
25
  function parsePath(path) {
12
26
  const hashIndex = path.indexOf("#");
13
27
  const queryIndex = path.indexOf("?");
@@ -23,18 +37,37 @@ function parsePath(path) {
23
37
  hash: ""
24
38
  };
25
39
  }
40
+ /**
41
+ * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/add-path-prefix.ts#L3C1-L14C2
42
+ * Adds the provided prefix to the given path. It first ensures that the path
43
+ * is indeed starting with a slash.
44
+ */
26
45
  function addPathPrefix(path, prefix) {
27
46
  if (!path.startsWith("/") || !prefix) return path;
28
47
  if (path === "/" && prefix) return prefix;
29
48
  const { pathname, query, hash } = parsePath(path);
30
49
  return `${prefix}${pathname}${query}${hash}`;
31
50
  }
51
+ /**
52
+ * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/remove-path-prefix.ts#L3-L39
53
+ * Given a path and a prefix it will remove the prefix when it exists in the
54
+ * given path. It ensures it matches exactly without containing extra chars
55
+ * and if the prefix is not there it will be noop.
56
+ *
57
+ * @param path The path to remove the prefix from.
58
+ * @param prefix The prefix to be removed.
59
+ */
32
60
  function removePathPrefix(path, prefix) {
33
61
  if (!pathHasPrefix(path, prefix)) return path;
34
62
  const withoutPrefix = path.slice(prefix.length);
35
63
  if (withoutPrefix.startsWith("/")) return withoutPrefix;
36
64
  return `/${withoutPrefix}`;
37
65
  }
66
+ /**
67
+ * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/client/normalize-trailing-slash.ts#L16
68
+ * Normalizes the trailing slash of a path according to the `trailingSlash` option
69
+ * in `next.config.js`.
70
+ */
38
71
  const normalizePathTrailingSlash = (path, trailingSlash) => {
39
72
  const { pathname, query, hash } = parsePath(path);
40
73
  if (trailingSlash) {
@@ -43,6 +76,14 @@ const normalizePathTrailingSlash = (path, trailingSlash) => {
43
76
  }
44
77
  return `${removeTrailingSlash(pathname)}${query}${hash}`;
45
78
  };
79
+ /**
80
+ * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts#L8
81
+ * Removes the trailing slash for a given route or page path. Preserves the
82
+ * root page. Examples:
83
+ * - `/foo/bar/` -> `/foo/bar`
84
+ * - `/foo/bar` -> `/foo/bar`
85
+ * - `/` -> `/`
86
+ */
46
87
  function removeTrailingSlash(route) {
47
88
  return route.replace(/\/$/, "") || "/";
48
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VisualEditing.js","names":["VisualEditingComponent"],"sources":["../src/visual-editing/client-component/utils.ts","../src/visual-editing/client-component/VisualEditing.tsx"],"sourcesContent":["/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/path-has-prefix.ts#L10-L17\n * Checks if a given path starts with a given prefix. It ensures it matches\n * exactly without containing extra chars. e.g. prefix /docs should replace\n * for /docs, /docs/, /docs/a but not /docsss\n * @param path The path to check.\n * @param prefix The prefix to check against.\n */\nfunction pathHasPrefix(path: string, prefix: string): boolean {\n if (typeof path !== 'string') {\n return false\n }\n\n const {pathname} = parsePath(path)\n return pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/parse-path.ts#L6-L22\n * Given a path this function will find the pathname, query and hash and return\n * them. This is useful to parse full paths on the client side.\n * @param path A path to parse e.g. /foo/bar?id=1#hash\n */\nfunction parsePath(path: string): {\n pathname: string\n query: string\n hash: string\n} {\n const hashIndex = path.indexOf('#')\n const queryIndex = path.indexOf('?')\n const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex)\n\n if (hasQuery || hashIndex > -1) {\n return {\n pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),\n query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined) : '',\n hash: hashIndex > -1 ? path.slice(hashIndex) : '',\n }\n }\n\n return {pathname: path, query: '', hash: ''}\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/add-path-prefix.ts#L3C1-L14C2\n * Adds the provided prefix to the given path. It first ensures that the path\n * is indeed starting with a slash.\n */\nexport function addPathPrefix(path: string, prefix?: string): string {\n if (!path.startsWith('/') || !prefix) {\n return path\n }\n // If the path is exactly '/' then return just the prefix\n if (path === '/' && prefix) {\n return prefix\n }\n\n const {pathname, query, hash} = parsePath(path)\n return `${prefix}${pathname}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/remove-path-prefix.ts#L3-L39\n * Given a path and a prefix it will remove the prefix when it exists in the\n * given path. It ensures it matches exactly without containing extra chars\n * and if the prefix is not there it will be noop.\n *\n * @param path The path to remove the prefix from.\n * @param prefix The prefix to be removed.\n */\nexport function removePathPrefix(path: string, prefix: string): string {\n // If the path doesn't start with the prefix we can return it as is. This\n // protects us from situations where the prefix is a substring of the path\n // prefix such as:\n //\n // For prefix: /blog\n //\n // /blog -> true\n // /blog/ -> true\n // /blog/1 -> true\n // /blogging -> false\n // /blogging/ -> false\n // /blogging/1 -> false\n if (!pathHasPrefix(path, prefix)) {\n return path\n }\n\n // Remove the prefix from the path via slicing.\n const withoutPrefix = path.slice(prefix.length)\n\n // If the path without the prefix starts with a `/` we can return it as is.\n if (withoutPrefix.startsWith('/')) {\n return withoutPrefix\n }\n\n // If the path without the prefix doesn't start with a `/` we need to add it\n // back to the path to make sure it's a valid path.\n return `/${withoutPrefix}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/client/normalize-trailing-slash.ts#L16\n * Normalizes the trailing slash of a path according to the `trailingSlash` option\n * in `next.config.js`.\n */\nexport const normalizePathTrailingSlash = (path: string, trailingSlash: boolean): string => {\n const {pathname, query, hash} = parsePath(path)\n if (trailingSlash) {\n if (pathname.endsWith('/')) {\n return `${pathname}${query}${hash}`\n }\n return `${pathname}/${query}${hash}`\n }\n\n return `${removeTrailingSlash(pathname)}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts#L8\n * Removes the trailing slash for a given route or page path. Preserves the\n * root page. Examples:\n * - `/foo/bar/` -> `/foo/bar`\n * - `/foo/bar` -> `/foo/bar`\n * - `/` -> `/`\n */\nfunction removeTrailingSlash(route: string) {\n return route.replace(/\\/$/, '') || '/'\n}\n","import {\n type HistoryAdapter,\n type HistoryAdapterNavigate,\n type HistoryRefresh,\n VisualEditing as VisualEditingComponent,\n type VisualEditingOptions,\n} from '@sanity/visual-editing/react'\nimport {revalidateRootLayout} from 'next-sanity/visual-editing/server-actions'\nimport {usePathname, useRouter, useSearchParams} from 'next/navigation'\nimport {useCallback, useEffect, useMemo, useRef, useState} from 'react'\n\nimport {addPathPrefix, normalizePathTrailingSlash, removePathPrefix} from './utils'\n\n/**\n * @public\n */\nexport interface VisualEditingProps extends Omit<VisualEditingOptions, 'history'> {\n /**\n * @deprecated The histoy adapter is already implemented\n */\n history?: never\n /**\n * If next.config.ts is configured with a basePath we try to configure it automatically,\n * you can disable this by setting basePath to ''.\n * @example basePath=\"/my-custom-base-path\"\n * @alpha experimental and may change without notice\n * @defaultValue process.env.__NEXT_ROUTER_BASEPATH || ''\n */\n basePath?: string\n /**\n * If next.config.ts is configured with a `trailingSlash` we try to detect it automatically,\n * it can be controlled manually by passing a boolean.\n * @example trailingSlash={true}\n * @alpha experimental and may change without notice\n * @defaultValue Boolean(process.env.__NEXT_TRAILING_SLASH)\n */\n trailingSlash?: boolean\n}\n\nexport default function VisualEditing(props: VisualEditingProps): React.JSX.Element | null {\n const {basePath = '', plugins, components, refresh, trailingSlash = false, zIndex} = props\n\n const router = useRouter()\n const routerRef = useRef(router)\n const [navigate, setNavigate] = useState<HistoryAdapterNavigate | undefined>()\n\n useEffect(() => {\n routerRef.current = router\n }, [router])\n\n const history = useMemo<HistoryAdapter>(\n () => ({\n subscribe: (_navigate) => {\n setNavigate(() => _navigate)\n return () => setNavigate(undefined)\n },\n update: (update) => {\n switch (update.type) {\n case 'push':\n return routerRef.current.push(removePathPrefix(update.url, basePath))\n case 'pop':\n return routerRef.current.back()\n case 'replace':\n return routerRef.current.replace(removePathPrefix(update.url, basePath))\n default:\n throw new Error(`Unknown update type`, {cause: update})\n }\n },\n }),\n [basePath],\n )\n\n const pathname = usePathname()\n const searchParams = useSearchParams()\n useEffect(() => {\n if (navigate) {\n navigate({\n type: 'push',\n url: normalizePathTrailingSlash(\n addPathPrefix(\n `${pathname}${searchParams?.size ? `?${searchParams.toString()}` : ''}`,\n basePath,\n ),\n trailingSlash,\n ),\n })\n }\n }, [basePath, navigate, pathname, searchParams, trailingSlash])\n\n const handleRefresh = useCallback(\n (payload: HistoryRefresh) => {\n if (refresh) return refresh(payload)\n\n const manualFastRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, calling router.refresh() to refresh the server components without refetching cached data',\n )\n routerRef.current.refresh()\n return Promise.resolve()\n }\n const manualFallbackRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n\n const mutationFallbackRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n\n switch (payload.source) {\n case 'manual':\n return payload.livePreviewEnabled ? manualFastRefresh() : manualFallbackRefresh()\n case 'mutation':\n return payload.livePreviewEnabled ? mutationFastRefresh() : mutationFallbackRefresh()\n default:\n throw new Error('Unknown refresh source', {cause: payload})\n }\n },\n [refresh],\n )\n\n return (\n <VisualEditingComponent\n plugins={plugins}\n components={components}\n history={history}\n portal\n refresh={handleRefresh}\n zIndex={zIndex}\n />\n )\n}\n\nfunction mutationFastRefresh(): false {\n // oxlint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, mutation is skipped assuming its handled by the live preview',\n )\n return false\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VisualEditing.js","names":["VisualEditingComponent"],"sources":["../src/visual-editing/client-component/utils.ts","../src/visual-editing/client-component/VisualEditing.tsx"],"sourcesContent":["/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/path-has-prefix.ts#L10-L17\n * Checks if a given path starts with a given prefix. It ensures it matches\n * exactly without containing extra chars. e.g. prefix /docs should replace\n * for /docs, /docs/, /docs/a but not /docsss\n * @param path The path to check.\n * @param prefix The prefix to check against.\n */\nfunction pathHasPrefix(path: string, prefix: string): boolean {\n if (typeof path !== 'string') {\n return false\n }\n\n const {pathname} = parsePath(path)\n return pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/parse-path.ts#L6-L22\n * Given a path this function will find the pathname, query and hash and return\n * them. This is useful to parse full paths on the client side.\n * @param path A path to parse e.g. /foo/bar?id=1#hash\n */\nfunction parsePath(path: string): {\n pathname: string\n query: string\n hash: string\n} {\n const hashIndex = path.indexOf('#')\n const queryIndex = path.indexOf('?')\n const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex)\n\n if (hasQuery || hashIndex > -1) {\n return {\n pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),\n query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined) : '',\n hash: hashIndex > -1 ? path.slice(hashIndex) : '',\n }\n }\n\n return {pathname: path, query: '', hash: ''}\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/add-path-prefix.ts#L3C1-L14C2\n * Adds the provided prefix to the given path. It first ensures that the path\n * is indeed starting with a slash.\n */\nexport function addPathPrefix(path: string, prefix?: string): string {\n if (!path.startsWith('/') || !prefix) {\n return path\n }\n // If the path is exactly '/' then return just the prefix\n if (path === '/' && prefix) {\n return prefix\n }\n\n const {pathname, query, hash} = parsePath(path)\n return `${prefix}${pathname}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/remove-path-prefix.ts#L3-L39\n * Given a path and a prefix it will remove the prefix when it exists in the\n * given path. It ensures it matches exactly without containing extra chars\n * and if the prefix is not there it will be noop.\n *\n * @param path The path to remove the prefix from.\n * @param prefix The prefix to be removed.\n */\nexport function removePathPrefix(path: string, prefix: string): string {\n // If the path doesn't start with the prefix we can return it as is. This\n // protects us from situations where the prefix is a substring of the path\n // prefix such as:\n //\n // For prefix: /blog\n //\n // /blog -> true\n // /blog/ -> true\n // /blog/1 -> true\n // /blogging -> false\n // /blogging/ -> false\n // /blogging/1 -> false\n if (!pathHasPrefix(path, prefix)) {\n return path\n }\n\n // Remove the prefix from the path via slicing.\n const withoutPrefix = path.slice(prefix.length)\n\n // If the path without the prefix starts with a `/` we can return it as is.\n if (withoutPrefix.startsWith('/')) {\n return withoutPrefix\n }\n\n // If the path without the prefix doesn't start with a `/` we need to add it\n // back to the path to make sure it's a valid path.\n return `/${withoutPrefix}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/client/normalize-trailing-slash.ts#L16\n * Normalizes the trailing slash of a path according to the `trailingSlash` option\n * in `next.config.js`.\n */\nexport const normalizePathTrailingSlash = (path: string, trailingSlash: boolean): string => {\n const {pathname, query, hash} = parsePath(path)\n if (trailingSlash) {\n if (pathname.endsWith('/')) {\n return `${pathname}${query}${hash}`\n }\n return `${pathname}/${query}${hash}`\n }\n\n return `${removeTrailingSlash(pathname)}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts#L8\n * Removes the trailing slash for a given route or page path. Preserves the\n * root page. Examples:\n * - `/foo/bar/` -> `/foo/bar`\n * - `/foo/bar` -> `/foo/bar`\n * - `/` -> `/`\n */\nfunction removeTrailingSlash(route: string) {\n return route.replace(/\\/$/, '') || '/'\n}\n","import {\n type HistoryAdapter,\n type HistoryAdapterNavigate,\n type HistoryRefresh,\n VisualEditing as VisualEditingComponent,\n type VisualEditingOptions,\n} from '@sanity/visual-editing/react'\nimport {revalidateRootLayout} from 'next-sanity/visual-editing/server-actions'\nimport {usePathname, useRouter, useSearchParams} from 'next/navigation'\nimport {useCallback, useEffect, useMemo, useRef, useState} from 'react'\n\nimport {addPathPrefix, normalizePathTrailingSlash, removePathPrefix} from './utils'\n\n/**\n * @public\n */\nexport interface VisualEditingProps extends Omit<VisualEditingOptions, 'history'> {\n /**\n * @deprecated The histoy adapter is already implemented\n */\n history?: never\n /**\n * If next.config.ts is configured with a basePath we try to configure it automatically,\n * you can disable this by setting basePath to ''.\n * @example basePath=\"/my-custom-base-path\"\n * @alpha experimental and may change without notice\n * @defaultValue process.env.__NEXT_ROUTER_BASEPATH || ''\n */\n basePath?: string\n /**\n * If next.config.ts is configured with a `trailingSlash` we try to detect it automatically,\n * it can be controlled manually by passing a boolean.\n * @example trailingSlash={true}\n * @alpha experimental and may change without notice\n * @defaultValue Boolean(process.env.__NEXT_TRAILING_SLASH)\n */\n trailingSlash?: boolean\n}\n\nexport default function VisualEditing(props: VisualEditingProps): React.JSX.Element | null {\n const {basePath = '', plugins, components, refresh, trailingSlash = false, zIndex} = props\n\n const router = useRouter()\n const routerRef = useRef(router)\n const [navigate, setNavigate] = useState<HistoryAdapterNavigate | undefined>()\n\n useEffect(() => {\n routerRef.current = router\n }, [router])\n\n const history = useMemo<HistoryAdapter>(\n () => ({\n subscribe: (_navigate) => {\n setNavigate(() => _navigate)\n return () => setNavigate(undefined)\n },\n update: (update) => {\n switch (update.type) {\n case 'push':\n return routerRef.current.push(removePathPrefix(update.url, basePath))\n case 'pop':\n return routerRef.current.back()\n case 'replace':\n return routerRef.current.replace(removePathPrefix(update.url, basePath))\n default:\n throw new Error(`Unknown update type`, {cause: update})\n }\n },\n }),\n [basePath],\n )\n\n const pathname = usePathname()\n const searchParams = useSearchParams()\n useEffect(() => {\n if (navigate) {\n navigate({\n type: 'push',\n url: normalizePathTrailingSlash(\n addPathPrefix(\n `${pathname}${searchParams?.size ? `?${searchParams.toString()}` : ''}`,\n basePath,\n ),\n trailingSlash,\n ),\n })\n }\n }, [basePath, navigate, pathname, searchParams, trailingSlash])\n\n const handleRefresh = useCallback(\n (payload: HistoryRefresh) => {\n if (refresh) return refresh(payload)\n\n const manualFastRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, calling router.refresh() to refresh the server components without refetching cached data',\n )\n routerRef.current.refresh()\n return Promise.resolve()\n }\n const manualFallbackRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n\n const mutationFallbackRefresh = () => {\n // oxlint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n\n switch (payload.source) {\n case 'manual':\n return payload.livePreviewEnabled ? manualFastRefresh() : manualFallbackRefresh()\n case 'mutation':\n return payload.livePreviewEnabled ? mutationFastRefresh() : mutationFallbackRefresh()\n default:\n throw new Error('Unknown refresh source', {cause: payload})\n }\n },\n [refresh],\n )\n\n return (\n <VisualEditingComponent\n plugins={plugins}\n components={components}\n history={history}\n portal\n refresh={handleRefresh}\n zIndex={zIndex}\n />\n )\n}\n\nfunction mutationFastRefresh(): false {\n // oxlint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, mutation is skipped assuming its handled by the live preview',\n )\n return false\n}\n"],"mappings":"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"}
package/dist/context.js CHANGED
@@ -1,26 +1,38 @@
1
1
  import "@sanity/comlink";
2
2
  import "@sanity/presentation-comlink";
3
+ /** @internal */
3
4
  const perspectiveListeners = /* @__PURE__ */ new Set();
5
+ /** @internal */
4
6
  let perspective = "checking";
7
+ /** @internal */
5
8
  function setPerspective(nextPerspective) {
6
9
  if (perspective.toString() === nextPerspective.toString()) return;
7
10
  perspective = nextPerspective;
8
11
  for (const onPerspectiveChange of perspectiveListeners) onPerspectiveChange();
9
12
  }
13
+ /** @internal */
10
14
  const environmentListeners = /* @__PURE__ */ new Set();
15
+ /** @internal */
11
16
  let environment = "checking";
17
+ /** @internal */
12
18
  function setEnvironment(nextEnvironment) {
13
19
  environment = nextEnvironment;
14
20
  for (const onEnvironmentChange of environmentListeners) onEnvironmentChange();
15
21
  }
22
+ /** @internal */
16
23
  const comlinkListeners = /* @__PURE__ */ new Set();
24
+ /** @internal */
17
25
  let comlink = null;
26
+ /** @internal */
18
27
  let comlinkProjectId = null;
28
+ /** @internal */
19
29
  let comlinkDataset = null;
30
+ /** @internal */
20
31
  function setComlink(nextComlink) {
21
32
  comlink = nextComlink;
22
33
  for (const onComlinkChange of comlinkListeners) onComlinkChange();
23
34
  }
35
+ /** @internal */
24
36
  function setComlinkClientConfig(nextComlinkProjectId, nextComlinkDataset) {
25
37
  comlinkProjectId = nextComlinkProjectId;
26
38
  comlinkDataset = nextComlinkDataset;
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../src/live/hooks/context.ts"],"sourcesContent":["import type {ClientPerspective} from '@sanity/client'\nimport {type Node} from '@sanity/comlink'\nimport {type LoaderControllerMsg, type LoaderNodeMsg} from '@sanity/presentation-comlink'\n\n/**\n * The Sanity Client perspective used when fetching data in Draft Mode, in the `sanityFetch` calls\n * used by React Server Components on the page. Note that some of them might set the `perspective` to a different value.\n * This value is what's used by default.\n * @public\n */\nexport type DraftPerspective = 'checking' | 'unknown' | ClientPerspective\n\n/** @internal */\nexport const perspectiveListeners: Set<() => void> = new Set()\n/** @internal */\nexport let perspective: DraftPerspective = 'checking'\n/** @internal */\nexport function setPerspective(nextPerspective: DraftPerspective): void {\n if (perspective.toString() === nextPerspective.toString()) return\n perspective = nextPerspective\n for (const onPerspectiveChange of perspectiveListeners) {\n onPerspectiveChange()\n }\n}\n\n/**\n *\n * @public\n */\nexport type DraftEnvironment =\n | 'checking'\n | 'presentation-iframe'\n | 'presentation-window'\n | 'live'\n | 'static'\n | 'unknown'\n\n/** @internal */\nexport const environmentListeners: Set<() => void> = new Set()\n/** @internal */\nexport let environment: DraftEnvironment = 'checking'\n/** @internal */\nexport function setEnvironment(nextEnvironment: DraftEnvironment): void {\n environment = nextEnvironment\n for (const onEnvironmentChange of environmentListeners) {\n onEnvironmentChange()\n }\n}\n\n/** @internal */\nexport const comlinkListeners: Set<() => void> = new Set()\n/** @internal */\nexport let comlink: Node<LoaderNodeMsg, LoaderControllerMsg> | null = null\n/** @internal */\nexport let comlinkProjectId: string | null = null\n/** @internal */\nexport let comlinkDataset: string | null = null\n/** @internal */\nexport function setComlink(nextComlink: Node<LoaderNodeMsg, LoaderControllerMsg> | null): void {\n comlink = nextComlink\n for (const onComlinkChange of comlinkListeners) {\n onComlinkChange()\n }\n}\n/** @internal */\nexport function setComlinkClientConfig(\n nextComlinkProjectId: string | null,\n nextComlinkDataset: string | null,\n): void {\n comlinkProjectId = nextComlinkProjectId\n comlinkDataset = nextComlinkDataset\n for (const onComlinkChange of comlinkListeners) {\n onComlinkChange()\n }\n}\n"],"mappings":";;AAaA,MAAa,uCAAwC,IAAI,KAAK;AAE9D,IAAW,cAAgC;AAE3C,SAAgB,eAAe,iBAAyC;AACtE,KAAI,YAAY,UAAU,KAAK,gBAAgB,UAAU,CAAE;AAC3D,eAAc;AACd,MAAK,MAAM,uBAAuB,qBAChC,sBAAqB;;AAiBzB,MAAa,uCAAwC,IAAI,KAAK;AAE9D,IAAW,cAAgC;AAE3C,SAAgB,eAAe,iBAAyC;AACtE,eAAc;AACd,MAAK,MAAM,uBAAuB,qBAChC,sBAAqB;;AAKzB,MAAa,mCAAoC,IAAI,KAAK;AAE1D,IAAW,UAA2D;AAEtE,IAAW,mBAAkC;AAE7C,IAAW,iBAAgC;AAE3C,SAAgB,WAAW,aAAoE;AAC7F,WAAU;AACV,MAAK,MAAM,mBAAmB,iBAC5B,kBAAiB;;AAIrB,SAAgB,uBACd,sBACA,oBACM;AACN,oBAAmB;AACnB,kBAAiB;AACjB,MAAK,MAAM,mBAAmB,iBAC5B,kBAAiB"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../src/live/hooks/context.ts"],"sourcesContent":["import type {ClientPerspective} from '@sanity/client'\nimport {type Node} from '@sanity/comlink'\nimport {type LoaderControllerMsg, type LoaderNodeMsg} from '@sanity/presentation-comlink'\n\n/**\n * The Sanity Client perspective used when fetching data in Draft Mode, in the `sanityFetch` calls\n * used by React Server Components on the page. Note that some of them might set the `perspective` to a different value.\n * This value is what's used by default.\n * @public\n */\nexport type DraftPerspective = 'checking' | 'unknown' | ClientPerspective\n\n/** @internal */\nexport const perspectiveListeners: Set<() => void> = new Set()\n/** @internal */\nexport let perspective: DraftPerspective = 'checking'\n/** @internal */\nexport function setPerspective(nextPerspective: DraftPerspective): void {\n if (perspective.toString() === nextPerspective.toString()) return\n perspective = nextPerspective\n for (const onPerspectiveChange of perspectiveListeners) {\n onPerspectiveChange()\n }\n}\n\n/**\n *\n * @public\n */\nexport type DraftEnvironment =\n | 'checking'\n | 'presentation-iframe'\n | 'presentation-window'\n | 'live'\n | 'static'\n | 'unknown'\n\n/** @internal */\nexport const environmentListeners: Set<() => void> = new Set()\n/** @internal */\nexport let environment: DraftEnvironment = 'checking'\n/** @internal */\nexport function setEnvironment(nextEnvironment: DraftEnvironment): void {\n environment = nextEnvironment\n for (const onEnvironmentChange of environmentListeners) {\n onEnvironmentChange()\n }\n}\n\n/** @internal */\nexport const comlinkListeners: Set<() => void> = new Set()\n/** @internal */\nexport let comlink: Node<LoaderNodeMsg, LoaderControllerMsg> | null = null\n/** @internal */\nexport let comlinkProjectId: string | null = null\n/** @internal */\nexport let comlinkDataset: string | null = null\n/** @internal */\nexport function setComlink(nextComlink: Node<LoaderNodeMsg, LoaderControllerMsg> | null): void {\n comlink = nextComlink\n for (const onComlinkChange of comlinkListeners) {\n onComlinkChange()\n }\n}\n/** @internal */\nexport function setComlinkClientConfig(\n nextComlinkProjectId: string | null,\n nextComlinkDataset: string | null,\n): void {\n comlinkProjectId = nextComlinkProjectId\n comlinkDataset = nextComlinkDataset\n for (const onComlinkChange of comlinkListeners) {\n onComlinkChange()\n }\n}\n"],"mappings":";;;AAaA,MAAa,uCAAwC,IAAI,KAAK;;AAE9D,IAAW,cAAgC;;AAE3C,SAAgB,eAAe,iBAAyC;AACtE,KAAI,YAAY,UAAU,KAAK,gBAAgB,UAAU,CAAE;AAC3D,eAAc;AACd,MAAK,MAAM,uBAAuB,qBAChC,sBAAqB;;;AAiBzB,MAAa,uCAAwC,IAAI,KAAK;;AAE9D,IAAW,cAAgC;;AAE3C,SAAgB,eAAe,iBAAyC;AACtE,eAAc;AACd,MAAK,MAAM,uBAAuB,qBAChC,sBAAqB;;;AAKzB,MAAa,mCAAoC,IAAI,KAAK;;AAE1D,IAAW,UAA2D;;AAEtE,IAAW,mBAAkC;;AAE7C,IAAW,iBAAgC;;AAE3C,SAAgB,WAAW,aAAoE;AAC7F,WAAU;AACV,MAAK,MAAM,mBAAmB,iBAC5B,kBAAiB;;;AAIrB,SAAgB,uBACd,sBACA,oBACM;AACN,oBAAmB;AACnB,kBAAiB;AACjB,MAAK,MAAM,mBAAmB,iBAC5B,kBAAiB"}
@@ -91,6 +91,11 @@ interface DefinedSanityLiveProps {
91
91
  */
92
92
  intervalOnGoAway?: number | false;
93
93
  /**
94
+ * Delays events until after a Sanity Function has processed them and called the callback endpoint.
95
+ * When omitted, events are delivered immediately.
96
+ */
97
+ waitFor?: "function";
98
+ /**
94
99
  * @deprecated use `requestTag` instead
95
100
  */
96
101
  tag?: never;
@@ -1 +1 @@
1
- {"version":3,"file":"defineLive.d.ts","names":[],"sources":["../src/live/defineLive.tsx"],"mappings":";;AAoBA;;KAAY,sBAAA,sCAA4D,OAAA;EACtE,KAAA,EAAO,WAAA;EACP,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,WAAA;;;;;;;EAO/B,IAAA;EACA,WAAA,GAAc,OAAA,CAAQ,iBAAA;EACtB,KAAA;EAWI;;;EAPJ,GAAA;;;;;;EAMA,UAAA;AAAA,MACI,OAAA;EACJ,IAAA,EAAM,YAAA,CAAa,WAAA;EACnB,SAAA,EAAW,gBAAA;EACX,IAAA;AAAA;;;;KAMU,2BAAA,sCAAiE,KAAA;EAC3E,KAAA,EAAO,WAAA;EACP,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,WAAA;;;;;AAFjC;;EASE,IAAA;EACA,WAAA,GAAc,OAAA,CAAQ,iBAAA;EACtB,KAAA;;;;EAIA,GAAA;;;;;;EAMA,UAAA;EACA,QAAA,GAAW,MAAA;IACT,IAAA,EAAM,YAAA,CAAa,WAAA;IACnB,SAAA,EAAW,gBAAA;IACX,IAAA;EAAA,MACI,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,SAAA;AAAA,MACxB,KAAA,CAAM,SAAA;;;;UAKK,sBAAA;;;;;;EAMf,cAAA;;;;;;EAMA,cAAA;;;;;;EAMA,kBAAA;;;;;;;AAlBF;EA0BE,gBAAA;;;;EAKA,GAAA;;;;;;EAOA,UAAA;;;;;EAMA,OAAA,IAAW,KAAA;;;;;;;EAQX,QAAA,IAAY,KAAA,EAAO,eAAA,EAAiB,gBAAA;;;AAYtC;;EANE,kBAAA,IAAsB,IAAA,EAAM,OAAA,OAAc,OAAA;AAAA;;;;UAM3B,uBAAA;;;;EAIf,MAAA,EAAQ,YAAA;EA0BR;AAMF;;;EA3BE,WAAA;;;;;EAKA,YAAA;EAoCkB;;;;EA/BlB,YAAA;IA2BA;;;;IAtBE,UAAA;EAAA;;;;;EAMF,KAAA;AAAA;;;;iBAMc,UAAA,CAAW,MAAA,EAAQ,uBAAA;;;;;EAKjC,WAAA,EAAa,sBAAA;;;;;EAKb,UAAA,EAAY,KAAA,CAAM,aAAA,CAAc,sBAAA;;;;EAIhC,gBAAA,EAAkB,2BAAA;AAAA"}
1
+ {"version":3,"file":"defineLive.d.ts","names":[],"sources":["../src/live/defineLive.tsx"],"mappings":";;AAoBA;;KAAY,sBAAA,sCAA4D,OAAA;EACtE,KAAA,EAAO,WAAA;EACP,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,WAAA;;;;;;;EAO/B,IAAA;EACA,WAAA,GAAc,OAAA,CAAQ,iBAAA;EACtB,KAAA;EAWI;;;EAPJ,GAAA;;;;;;EAMA,UAAA;AAAA,MACI,OAAA;EACJ,IAAA,EAAM,YAAA,CAAa,WAAA;EACnB,SAAA,EAAW,gBAAA;EACX,IAAA;AAAA;;;;KAMU,2BAAA,sCAAiE,KAAA;EAC3E,KAAA,EAAO,WAAA;EACP,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,WAAA;;;;;AAFjC;;EASE,IAAA;EACA,WAAA,GAAc,OAAA,CAAQ,iBAAA;EACtB,KAAA;;;;EAIA,GAAA;;;;;;EAMA,UAAA;EACA,QAAA,GAAW,MAAA;IACT,IAAA,EAAM,YAAA,CAAa,WAAA;IACnB,SAAA,EAAW,gBAAA;IACX,IAAA;EAAA,MACI,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,SAAA;AAAA,MACxB,KAAA,CAAM,SAAA;;;;UAKK,sBAAA;;;;;;EAMf,cAAA;;;;;;EAMA,cAAA;;;;;;EAMA,kBAAA;;;;;;;AAlBF;EA0BE,gBAAA;;;;;EAMA,OAAA;EAgC0C;;;EA3B1C,GAAA;;;;;;EAOA,UAAA;;;;;EAMA,OAAA,IAAW,KAAA;;;;;;AAoBb;EAZE,QAAA,IAAY,KAAA,EAAO,eAAA,EAAiB,gBAAA;;;;;EAMpC,kBAAA,IAAsB,IAAA,EAAM,OAAA,OAAc,OAAA;AAAA;;;;UAM3B,uBAAA;EA8Bf;AAMF;;EAhCE,MAAA,EAAQ,YAAA;;;;;EAKR,WAAA;;;;;EAKA,YAAA;;;;;EAKA,YAAA;IA+BA;;;;IA1BE,UAAA;EAAA;;;;;EAMF,KAAA;AAAA;;;;iBAMc,UAAA,CAAW,MAAA,EAAQ,uBAAA;;;;;EAKjC,WAAA,EAAa,sBAAA;;;;;EAKb,UAAA,EAAY,KAAA,CAAM,aAAA,CAAc,sBAAA;;;;EAIhC,gBAAA,EAAkB,2BAAA;AAAA"}
@@ -1,7 +1,24 @@
1
1
  import { validatePreviewUrl } from "@sanity/preview-url-secret";
2
2
  import { perspectiveCookieName } from "@sanity/preview-url-secret/constants";
3
- import { cookies, draftMode } from "next/headers";
4
- import { redirect } from "next/navigation";
3
+ import { cookies, draftMode } from "next/headers.js";
4
+ import { redirect } from "next/navigation.js";
5
+ /**
6
+ * Sets up an API route for enabling draft mode, can be paired with the `previewUrl.previewMode.enable` in `sanity/presentation`.
7
+ * Can also be used with `sanity-plugin-iframe-pane`.
8
+ * @example
9
+ * ```ts
10
+ * // src/app/api/draft-mode/enable/route.ts
11
+ *
12
+ * import { defineEnableDraftMode } from "next-sanity/draft-mode";
13
+ * import { client } from "@/sanity/lib/client";
14
+ *
15
+ * export const { GET } = defineEnableDraftMode({
16
+ * client: client.withConfig({ token: process.env.SANITY_API_READ_TOKEN }),
17
+ * });
18
+ * ```
19
+ *
20
+ * @public
21
+ */
5
22
  function defineEnableDraftMode(options) {
6
23
  const { client } = options;
7
24
  return { GET: async (request) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/draft-mode/define-enable-draft-mode.ts"],"sourcesContent":["import type {SanityClient} from '@sanity/client'\nimport {validatePreviewUrl} from '@sanity/preview-url-secret'\nimport {perspectiveCookieName} from '@sanity/preview-url-secret/constants'\nimport {cookies, draftMode} from 'next/headers'\nimport {redirect} from 'next/navigation'\n\n/**\n * @public\n */\nexport interface DefineEnableDraftModeOptions {\n client: SanityClient\n /**\n * Force secure cookies in development mode.\n * Enable this when using Next.js --experimental-https flag.\n * This option has no effect in production (cookies are always secure).\n * @defaultValue false\n */\n secureDevMode?: boolean\n}\n\n/**\n * @public\n */\nexport interface EnableDraftMode {\n GET: (request: Request) => Promise<Response>\n}\n\n/**\n * Sets up an API route for enabling draft mode, can be paired with the `previewUrl.previewMode.enable` in `sanity/presentation`.\n * Can also be used with `sanity-plugin-iframe-pane`.\n * @example\n * ```ts\n * // src/app/api/draft-mode/enable/route.ts\n *\n * import { defineEnableDraftMode } from \"next-sanity/draft-mode\";\n * import { client } from \"@/sanity/lib/client\";\n *\n * export const { GET } = defineEnableDraftMode({\n * client: client.withConfig({ token: process.env.SANITY_API_READ_TOKEN }),\n * });\n * ```\n *\n * @public\n */\nexport function defineEnableDraftMode(options: DefineEnableDraftModeOptions): EnableDraftMode {\n const {client} = options\n return {\n GET: async (request: Request) => {\n // @TODO check if already in draft mode at a much earlier stage, and skip validation\n\n const {\n isValid,\n redirectTo = '/',\n studioPreviewPerspective,\n } = await validatePreviewUrl(client, request.url)\n if (!isValid) {\n return new Response('Invalid secret', {status: 401})\n }\n\n const draftModeStore = await draftMode()\n\n // Let's enable draft mode if it's not already enabled\n if (!draftModeStore.isEnabled) {\n draftModeStore.enable()\n }\n\n const isProduction = process.env.NODE_ENV === 'production'\n\n // We can't auto-detect HTTPS in dev due to Next.js limitations,\n // so we need an explicit option\n const isSecure = isProduction || (options.secureDevMode ?? false)\n\n // Override cookie header for draft mode for usage in live-preview\n // https://github.com/vercel/next.js/issues/49927\n const cookieStore = await cookies()\n const cookie = cookieStore.get('__prerender_bypass')!\n cookieStore.set({\n name: '__prerender_bypass',\n value: cookie?.value,\n httpOnly: true,\n path: '/',\n secure: isSecure,\n sameSite: isSecure ? 'none' : 'lax',\n })\n\n if (studioPreviewPerspective) {\n cookieStore.set({\n name: perspectiveCookieName,\n value: studioPreviewPerspective,\n httpOnly: true,\n path: '/',\n secure: isSecure,\n sameSite: isSecure ? 'none' : 'lax',\n })\n }\n\n // the `redirect` function throws, and eventually returns a Promise<Response>. TSC doesn't \"see\" that so we have to tell it\n return redirect(redirectTo) as Promise<Response>\n },\n }\n}\n"],"mappings":";;;;AA4CA,SAAgB,sBAAsB,SAAwD;CAC5F,MAAM,EAAC,WAAU;AACjB,QAAO,EACL,KAAK,OAAO,YAAqB;EAG/B,MAAM,EACJ,SACA,aAAa,KACb,6BACE,MAAM,mBAAmB,QAAQ,QAAQ,IAAI;AACjD,MAAI,CAAC,QACH,QAAO,IAAI,SAAS,kBAAkB,EAAC,QAAQ,KAAI,CAAC;EAGtD,MAAM,iBAAiB,MAAM,WAAW;AAGxC,MAAI,CAAC,eAAe,UAClB,gBAAe,QAAQ;EAOzB,MAAM,WAJe,QAAQ,IAAI,aAAa,iBAIZ,QAAQ,iBAAiB;EAI3D,MAAM,cAAc,MAAM,SAAS;EACnC,MAAM,SAAS,YAAY,IAAI,qBAAqB;AACpD,cAAY,IAAI;GACd,MAAM;GACN,OAAO,QAAQ;GACf,UAAU;GACV,MAAM;GACN,QAAQ;GACR,UAAU,WAAW,SAAS;GAC/B,CAAC;AAEF,MAAI,yBACF,aAAY,IAAI;GACd,MAAM;GACN,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,UAAU,WAAW,SAAS;GAC/B,CAAC;AAIJ,SAAO,SAAS,WAAW;IAE9B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/draft-mode/define-enable-draft-mode.ts"],"sourcesContent":["import type {SanityClient} from '@sanity/client'\nimport {validatePreviewUrl} from '@sanity/preview-url-secret'\nimport {perspectiveCookieName} from '@sanity/preview-url-secret/constants'\nimport {cookies, draftMode} from 'next/headers'\nimport {redirect} from 'next/navigation'\n\n/**\n * @public\n */\nexport interface DefineEnableDraftModeOptions {\n client: SanityClient\n /**\n * Force secure cookies in development mode.\n * Enable this when using Next.js --experimental-https flag.\n * This option has no effect in production (cookies are always secure).\n * @defaultValue false\n */\n secureDevMode?: boolean\n}\n\n/**\n * @public\n */\nexport interface EnableDraftMode {\n GET: (request: Request) => Promise<Response>\n}\n\n/**\n * Sets up an API route for enabling draft mode, can be paired with the `previewUrl.previewMode.enable` in `sanity/presentation`.\n * Can also be used with `sanity-plugin-iframe-pane`.\n * @example\n * ```ts\n * // src/app/api/draft-mode/enable/route.ts\n *\n * import { defineEnableDraftMode } from \"next-sanity/draft-mode\";\n * import { client } from \"@/sanity/lib/client\";\n *\n * export const { GET } = defineEnableDraftMode({\n * client: client.withConfig({ token: process.env.SANITY_API_READ_TOKEN }),\n * });\n * ```\n *\n * @public\n */\nexport function defineEnableDraftMode(options: DefineEnableDraftModeOptions): EnableDraftMode {\n const {client} = options\n return {\n GET: async (request: Request) => {\n // @TODO check if already in draft mode at a much earlier stage, and skip validation\n\n const {\n isValid,\n redirectTo = '/',\n studioPreviewPerspective,\n } = await validatePreviewUrl(client, request.url)\n if (!isValid) {\n return new Response('Invalid secret', {status: 401})\n }\n\n const draftModeStore = await draftMode()\n\n // Let's enable draft mode if it's not already enabled\n if (!draftModeStore.isEnabled) {\n draftModeStore.enable()\n }\n\n const isProduction = process.env.NODE_ENV === 'production'\n\n // We can't auto-detect HTTPS in dev due to Next.js limitations,\n // so we need an explicit option\n const isSecure = isProduction || (options.secureDevMode ?? false)\n\n // Override cookie header for draft mode for usage in live-preview\n // https://github.com/vercel/next.js/issues/49927\n const cookieStore = await cookies()\n const cookie = cookieStore.get('__prerender_bypass')!\n cookieStore.set({\n name: '__prerender_bypass',\n value: cookie?.value,\n httpOnly: true,\n path: '/',\n secure: isSecure,\n sameSite: isSecure ? 'none' : 'lax',\n })\n\n if (studioPreviewPerspective) {\n cookieStore.set({\n name: perspectiveCookieName,\n value: studioPreviewPerspective,\n httpOnly: true,\n path: '/',\n secure: isSecure,\n sameSite: isSecure ? 'none' : 'lax',\n })\n }\n\n // the `redirect` function throws, and eventually returns a Promise<Response>. TSC doesn't \"see\" that so we have to tell it\n return redirect(redirectTo) as Promise<Response>\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,sBAAsB,SAAwD;CAC5F,MAAM,EAAC,WAAU;AACjB,QAAO,EACL,KAAK,OAAO,YAAqB;EAG/B,MAAM,EACJ,SACA,aAAa,KACb,6BACE,MAAM,mBAAmB,QAAQ,QAAQ,IAAI;AACjD,MAAI,CAAC,QACH,QAAO,IAAI,SAAS,kBAAkB,EAAC,QAAQ,KAAI,CAAC;EAGtD,MAAM,iBAAiB,MAAM,WAAW;AAGxC,MAAI,CAAC,eAAe,UAClB,gBAAe,QAAQ;EAOzB,MAAM,WAJe,QAAQ,IAAI,aAAa,iBAIZ,QAAQ,iBAAiB;EAI3D,MAAM,cAAc,MAAM,SAAS;EACnC,MAAM,SAAS,YAAY,IAAI,qBAAqB;AACpD,cAAY,IAAI;GACd,MAAM;GACN,OAAO,QAAQ;GACf,UAAU;GACV,MAAM;GACN,QAAQ;GACR,UAAU,WAAW,SAAS;GAC/B,CAAC;AAEF,MAAI,yBACF,aAAY,IAAI;GACd,MAAM;GACN,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,UAAU,WAAW,SAAS;GAC/B,CAAC;AAIJ,SAAO,SAAS,WAAW;IAE9B"}
@@ -4,18 +4,52 @@ import { useCallback, useEffect, useEffectEvent, useMemo, useReducer, useSyncExt
4
4
  import { stegaEncodeSourceMap } from "@sanity/client/stega";
5
5
  import { dequal } from "dequal/lite";
6
6
  import { useIsPresentationTool, useOptimistic, useVisualEditingEnvironment } from "@sanity/visual-editing/react";
7
+ /**
8
+ * Reports the current draft mode environment.
9
+ * Use it to determine how to adapt the UI based on wether:
10
+ * - Your app is previewed in a iframe, inside Presentation Tool in a Sanity Studio.
11
+ * - Your app is previewed in a new window, spawned from Presentation Tool in a Sanity Studio.
12
+ * - Your app is live previewing drafts in a standalone context.
13
+ * - Your app is previewing drafts, but not live.
14
+ * - Your app is not previewing anything (that could be detected).
15
+ * @public
16
+ * @deprecated this hook will be removed in the next major version
17
+ */
7
18
  function useDraftModeEnvironment() {
8
19
  return useSyncExternalStore(useCallback((listener) => {
9
20
  environmentListeners.add(listener);
10
21
  return () => environmentListeners.delete(listener);
11
22
  }, []), () => environment, () => "checking");
12
23
  }
24
+ /**
25
+ * Reports the Sanity Client perspective used to fetch data in `sanityFetch` used on the page.
26
+ * If the hook is used outside Draft Mode it will resolve to `'unknown'`.
27
+ * If the hook is used but the `<SanityLive />` component is not present then it'll stay in `'checking'` and console warn after a timeout that it seems like you're missing the component.
28
+ * @public
29
+ * @deprecated this hook will be removed in the next major version
30
+ */
13
31
  function useDraftModePerspective() {
14
32
  return useSyncExternalStore(useCallback((listener) => {
15
33
  perspectiveListeners.add(listener);
16
34
  return () => perspectiveListeners.delete(listener);
17
35
  }, []), () => perspective, () => "checking");
18
36
  }
37
+ /**
38
+ * Detects if the application is considered to be in a "Live Preview" mode.
39
+ * Live Preview means that the application is either:
40
+ * - being previewed inside Sanity Presentation Tool
41
+ * - being previewed in Draft Mode, with a `browserToken` given to `defineLive`, also known as "Standalone Live Preview'"
42
+ * When in Live Preview mode, you typically want UI to update as new content comes in, without any manual intervention.
43
+ * This is very different from Live Production mode, where you usually want to delay updates that might cause layout shifts,
44
+ * to avoid interrupting the user that is consuming your content.
45
+ * This hook lets you adapt to this difference, making sure production doesn't cause layout shifts that worsen the UX,
46
+ * while in Live Preview mode layout shift is less of an issue and it's better for the editorial experience to auto refresh in real time.
47
+ *
48
+ * The hook returns `null` initially, to signal it doesn't yet know if it's live previewing or not.
49
+ * Then `true` if it is, and `false` otherwise.
50
+ * @public
51
+ * @deprecated this hook will be removed in the next major version
52
+ */
19
53
  function useIsLivePreview() {
20
54
  const environment = useDraftModeEnvironment();
21
55
  return environment === "checking" ? null : environment === "presentation-iframe" || environment === "presentation-window" || environment === "live";
@@ -42,12 +76,31 @@ function subscribe(listener) {
42
76
  }
43
77
  const EMPTY_QUERY_PARAMS = {};
44
78
  const LISTEN_HEARTBEAT_INTERVAL = 1e4;
79
+ /**
80
+ * Experimental hook that can run queries in Presentation Tool.
81
+ * Query results are sent back over postMessage whenever the query results change.
82
+ * It also works with optimistic updates in the studio itself, offering low latency updates.
83
+ * It's not as low latency as the `useOptimistic` hook, but it's a good compromise for some use cases.
84
+ * Especially until `useOptimistic` propagates edits in the Studio parent window back into the iframe.
85
+ * @alpha
86
+ */
45
87
  function usePresentationQuery(props) {
46
88
  const [state, dispatch] = useReducer(reducer, initialState);
47
89
  const { query, params = EMPTY_QUERY_PARAMS, stega = true } = props;
90
+ /**
91
+ * Comlink forwards queries we want to run to the parent window where Presentation Tool handles it for us
92
+ */
48
93
  const comlink$1 = useSyncExternalStore(subscribe, () => comlink, () => null);
94
+ /**
95
+ * The comlink events requires projectId and dataset, Presentation Tool uses it to protect against project and dataset mismatch errors.
96
+ * We don't want to force the consumers of the `usePresentationQuery` hook to provide these,
97
+ * so we set them in the component that establishes the comlink connection and propagates it to all the subscribes.
98
+ */
49
99
  const projectId = useSyncExternalStore(subscribe, () => comlinkProjectId, () => null);
50
100
  const dataset = useSyncExternalStore(subscribe, () => comlinkDataset, () => null);
101
+ /**
102
+ * The perspective is kept in sync with Presentation Tool's perspective, and even knows what perspective the page loaded with initially and can forward it to the Sanity Studio.
103
+ */
51
104
  const perspective = useDraftModePerspective();
52
105
  const handleQueryHeartbeat = useEffectEvent((comlink) => {
53
106
  if (!projectId || !dataset || !perspective) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["comlink","comlinkSnapshot"],"sources":["../../src/live/hooks/useDraftMode.ts","../../src/live/hooks/useIsLivePreview.ts","../../src/live/hooks/usePresentationQuery.ts"],"sourcesContent":["import {useCallback, useSyncExternalStore} from 'react'\n\nimport {\n environment,\n environmentListeners,\n perspective,\n perspectiveListeners,\n type DraftEnvironment,\n type DraftPerspective,\n} from './context'\n\n/**\n * Reports the current draft mode environment.\n * Use it to determine how to adapt the UI based on wether:\n * - Your app is previewed in a iframe, inside Presentation Tool in a Sanity Studio.\n * - Your app is previewed in a new window, spawned from Presentation Tool in a Sanity Studio.\n * - Your app is live previewing drafts in a standalone context.\n * - Your app is previewing drafts, but not live.\n * - Your app is not previewing anything (that could be detected).\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useDraftModeEnvironment(): DraftEnvironment {\n const subscribe = useCallback((listener: () => void) => {\n environmentListeners.add(listener)\n return () => environmentListeners.delete(listener)\n }, [])\n\n return useSyncExternalStore(\n subscribe,\n () => environment,\n () => 'checking',\n )\n}\n\n/**\n * Reports the Sanity Client perspective used to fetch data in `sanityFetch` used on the page.\n * If the hook is used outside Draft Mode it will resolve to `'unknown'`.\n * If the hook is used but the `<SanityLive />` component is not present then it'll stay in `'checking'` and console warn after a timeout that it seems like you're missing the component.\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useDraftModePerspective(): DraftPerspective {\n const subscribe = useCallback((listener: () => void) => {\n perspectiveListeners.add(listener)\n return () => perspectiveListeners.delete(listener)\n }, [])\n\n return useSyncExternalStore(\n subscribe,\n () => perspective,\n () => 'checking',\n )\n}\n","import {useDraftModeEnvironment} from './useDraftMode'\n\n/**\n * Detects if the application is considered to be in a \"Live Preview\" mode.\n * Live Preview means that the application is either:\n * - being previewed inside Sanity Presentation Tool\n * - being previewed in Draft Mode, with a `browserToken` given to `defineLive`, also known as \"Standalone Live Preview'\"\n * When in Live Preview mode, you typically want UI to update as new content comes in, without any manual intervention.\n * This is very different from Live Production mode, where you usually want to delay updates that might cause layout shifts,\n * to avoid interrupting the user that is consuming your content.\n * This hook lets you adapt to this difference, making sure production doesn't cause layout shifts that worsen the UX,\n * while in Live Preview mode layout shift is less of an issue and it's better for the editorial experience to auto refresh in real time.\n *\n * The hook returns `null` initially, to signal it doesn't yet know if it's live previewing or not.\n * Then `true` if it is, and `false` otherwise.\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useIsLivePreview(): boolean | null {\n const environment = useDraftModeEnvironment()\n return environment === 'checking'\n ? null\n : environment === 'presentation-iframe' ||\n environment === 'presentation-window' ||\n environment === 'live'\n}\n","import type {ClientPerspective, ClientReturn, ContentSourceMap, QueryParams} from '@sanity/client'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport type {LoaderControllerMsg} from '@sanity/presentation-comlink'\nimport {dequal} from 'dequal/lite'\nimport {useEffect, useMemo, useReducer, useSyncExternalStore, useEffectEvent} from 'react'\n\nimport {\n comlinkDataset,\n comlinkListeners,\n comlinkProjectId,\n comlink as comlinkSnapshot,\n} from './context'\nimport {useDraftModePerspective} from './useDraftMode'\n\n/** @alpha */\nexport type UsePresentationQueryReturnsInactive = {\n data: null\n sourceMap: null\n perspective: null\n}\n\n/** @alpha */\nexport type UsePresentationQueryReturnsActive<QueryString extends string> = {\n data: ClientReturn<QueryString>\n sourceMap: ContentSourceMap | null\n perspective: ClientPerspective\n}\n\nexport type UsePresentationQueryReturns<QueryString extends string> =\n | UsePresentationQueryReturnsInactive\n | UsePresentationQueryReturnsActive<QueryString>\n\ntype Action<QueryString extends string> = {\n type: 'query-change'\n payload: UsePresentationQueryReturnsActive<QueryString>\n}\n\nfunction reducer<QueryString extends string>(\n state: UsePresentationQueryReturns<QueryString>,\n {type, payload}: Action<QueryString>,\n): UsePresentationQueryReturns<QueryString> {\n switch (type) {\n case 'query-change':\n return dequal(state, payload)\n ? state\n : {\n ...state,\n data: dequal(state.data, payload.data)\n ? // oxlint-disable-next-line no-unsafe-type-assertion\n (state.data as ClientReturn<QueryString>)\n : payload.data,\n sourceMap: dequal(state.sourceMap, payload.sourceMap)\n ? state.sourceMap\n : payload.sourceMap,\n perspective: dequal(state.perspective, payload.perspective)\n ? // oxlint-disable-next-line no-unsafe-type-assertion\n (state.perspective as Exclude<ClientPerspective, 'raw'>)\n : payload.perspective,\n }\n default:\n return state\n }\n}\nconst initialState: UsePresentationQueryReturnsInactive = {\n data: null,\n sourceMap: null,\n perspective: null,\n}\n\nfunction subscribe(listener: () => void) {\n comlinkListeners.add(listener)\n return () => comlinkListeners.delete(listener)\n}\n\nconst EMPTY_QUERY_PARAMS: QueryParams = {}\nconst LISTEN_HEARTBEAT_INTERVAL = 10_000\n\n/**\n * Experimental hook that can run queries in Presentation Tool.\n * Query results are sent back over postMessage whenever the query results change.\n * It also works with optimistic updates in the studio itself, offering low latency updates.\n * It's not as low latency as the `useOptimistic` hook, but it's a good compromise for some use cases.\n * Especially until `useOptimistic` propagates edits in the Studio parent window back into the iframe.\n * @alpha\n */\nexport function usePresentationQuery<const QueryString extends string>(props: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n stega?: boolean\n}): UsePresentationQueryReturns<QueryString> {\n const [state, dispatch] = useReducer(reducer, initialState)\n const {query, params = EMPTY_QUERY_PARAMS, stega = true} = props\n\n /**\n * Comlink forwards queries we want to run to the parent window where Presentation Tool handles it for us\n */\n const comlink = useSyncExternalStore(\n subscribe,\n () => comlinkSnapshot,\n () => null,\n )\n /**\n * The comlink events requires projectId and dataset, Presentation Tool uses it to protect against project and dataset mismatch errors.\n * We don't want to force the consumers of the `usePresentationQuery` hook to provide these,\n * so we set them in the component that establishes the comlink connection and propagates it to all the subscribes.\n */\n const projectId = useSyncExternalStore(\n subscribe,\n () => comlinkProjectId,\n () => null,\n )\n const dataset = useSyncExternalStore(\n subscribe,\n () => comlinkDataset,\n () => null,\n )\n /**\n * The perspective is kept in sync with Presentation Tool's perspective, and even knows what perspective the page loaded with initially and can forward it to the Sanity Studio.\n */\n const perspective = useDraftModePerspective()\n const handleQueryHeartbeat = useEffectEvent((comlink: NonNullable<typeof comlinkSnapshot>) => {\n // Handle odd case where the comlink can take events but some data is missing\n if (!projectId || !dataset || !perspective) {\n console.warn('usePresentationQuery: projectId, dataset and perspective must be set', {\n projectId,\n dataset,\n perspective,\n })\n return\n }\n // Another odd case where the initial perspective states haven't resolved to the actual perspective state\n if (perspective === 'checking' || perspective === 'unknown') {\n return\n }\n comlink.post('loader/query-listen', {\n projectId,\n dataset,\n perspective,\n query,\n params,\n heartbeat: LISTEN_HEARTBEAT_INTERVAL,\n })\n })\n const handleQueryChange = useEffectEvent(\n (event: Extract<LoaderControllerMsg, {type: 'loader/query-change'}>['data']) => {\n if (\n dequal(\n {\n projectId,\n dataset,\n query,\n params,\n },\n {\n projectId: event.projectId,\n dataset: event.dataset,\n query: event.query,\n params: event.params,\n },\n )\n ) {\n dispatch({\n type: 'query-change',\n payload: {\n data: event.result,\n sourceMap: event.resultSourceMap || null,\n perspective: event.perspective,\n },\n })\n }\n },\n )\n useEffect(() => {\n if (!comlink) return\n\n const unsubscribe = comlink.on('loader/query-change', handleQueryChange)\n const interval = setInterval(() => handleQueryHeartbeat(comlink), LISTEN_HEARTBEAT_INTERVAL)\n return () => {\n clearInterval(interval)\n unsubscribe()\n }\n }, [comlink])\n\n return useMemo(() => {\n if (stega && state.sourceMap) {\n return {\n ...state,\n data: stegaEncodeSourceMap(state.data, state.sourceMap, {enabled: true, studioUrl: '/'}),\n }\n }\n return state\n }, [state, stega])\n}\n"],"mappings":";;;;;;AAsBA,SAAgB,0BAA4C;AAM1D,QAAO,qBALW,aAAa,aAAyB;AACtD,uBAAqB,IAAI,SAAS;AAClC,eAAa,qBAAqB,OAAO,SAAS;IACjD,EAAE,CAAC,QAIE,mBACA,WACP;;AAUH,SAAgB,0BAA4C;AAM1D,QAAO,qBALW,aAAa,aAAyB;AACtD,uBAAqB,IAAI,SAAS;AAClC,eAAa,qBAAqB,OAAO,SAAS;IACjD,EAAE,CAAC,QAIE,mBACA,WACP;;AClCH,SAAgB,mBAAmC;CACjD,MAAM,cAAc,yBAAyB;AAC7C,QAAO,gBAAgB,aACnB,OACA,gBAAgB,yBACd,gBAAgB,yBAChB,gBAAgB;;ACaxB,SAAS,QACP,OACA,EAAC,MAAM,WACmC;AAC1C,SAAQ,MAAR;EACE,KAAK,eACH,QAAO,OAAO,OAAO,QAAQ,GACzB,QACA;GACE,GAAG;GACH,MAAM,OAAO,MAAM,MAAM,QAAQ,KAAK,GAEjC,MAAM,OACP,QAAQ;GACZ,WAAW,OAAO,MAAM,WAAW,QAAQ,UAAU,GACjD,MAAM,YACN,QAAQ;GACZ,aAAa,OAAO,MAAM,aAAa,QAAQ,YAAY,GAEtD,MAAM,cACP,QAAQ;GACb;EACP,QACE,QAAO;;;AAGb,MAAM,eAAoD;CACxD,MAAM;CACN,WAAW;CACX,aAAa;CACd;AAED,SAAS,UAAU,UAAsB;AACvC,kBAAiB,IAAI,SAAS;AAC9B,cAAa,iBAAiB,OAAO,SAAS;;AAGhD,MAAM,qBAAkC,EAAE;AAC1C,MAAM,4BAA4B;AAUlC,SAAgB,qBAAuD,OAI1B;CAC3C,MAAM,CAAC,OAAO,YAAY,WAAW,SAAS,aAAa;CAC3D,MAAM,EAAC,OAAO,SAAS,oBAAoB,QAAQ,SAAQ;CAK3D,MAAMA,YAAU,qBACd,iBACMC,eACA,KACP;CAMD,MAAM,YAAY,qBAChB,iBACM,wBACA,KACP;CACD,MAAM,UAAU,qBACd,iBACM,sBACA,KACP;CAID,MAAM,cAAc,yBAAyB;CAC7C,MAAM,uBAAuB,gBAAgB,YAAiD;AAE5F,MAAI,CAAC,aAAa,CAAC,WAAW,CAAC,aAAa;AAC1C,WAAQ,KAAK,wEAAwE;IACnF;IACA;IACA;IACD,CAAC;AACF;;AAGF,MAAI,gBAAgB,cAAc,gBAAgB,UAChD;AAEF,UAAQ,KAAK,uBAAuB;GAClC;GACA;GACA;GACA;GACA;GACA,WAAW;GACZ,CAAC;GACF;CACF,MAAM,oBAAoB,gBACvB,UAA+E;AAC9E,MACE,OACE;GACE;GACA;GACA;GACA;GACD,EACD;GACE,WAAW,MAAM;GACjB,SAAS,MAAM;GACf,OAAO,MAAM;GACb,QAAQ,MAAM;GACf,CACF,CAED,UAAS;GACP,MAAM;GACN,SAAS;IACP,MAAM,MAAM;IACZ,WAAW,MAAM,mBAAmB;IACpC,aAAa,MAAM;IACpB;GACF,CAAC;GAGP;AACD,iBAAgB;AACd,MAAI,CAACD,UAAS;EAEd,MAAM,cAAcA,UAAQ,GAAG,uBAAuB,kBAAkB;EACxE,MAAM,WAAW,kBAAkB,qBAAqBA,UAAQ,EAAE,0BAA0B;AAC5F,eAAa;AACX,iBAAc,SAAS;AACvB,gBAAa;;IAEd,CAACA,UAAQ,CAAC;AAEb,QAAO,cAAc;AACnB,MAAI,SAAS,MAAM,UACjB,QAAO;GACL,GAAG;GACH,MAAM,qBAAqB,MAAM,MAAM,MAAM,WAAW;IAAC,SAAS;IAAM,WAAW;IAAI,CAAC;GACzF;AAEH,SAAO;IACN,CAAC,OAAO,MAAM,CAAC"}
1
+ {"version":3,"file":"index.js","names":["comlink","comlinkSnapshot"],"sources":["../../src/live/hooks/useDraftMode.ts","../../src/live/hooks/useIsLivePreview.ts","../../src/live/hooks/usePresentationQuery.ts"],"sourcesContent":["import {useCallback, useSyncExternalStore} from 'react'\n\nimport {\n environment,\n environmentListeners,\n perspective,\n perspectiveListeners,\n type DraftEnvironment,\n type DraftPerspective,\n} from './context'\n\n/**\n * Reports the current draft mode environment.\n * Use it to determine how to adapt the UI based on wether:\n * - Your app is previewed in a iframe, inside Presentation Tool in a Sanity Studio.\n * - Your app is previewed in a new window, spawned from Presentation Tool in a Sanity Studio.\n * - Your app is live previewing drafts in a standalone context.\n * - Your app is previewing drafts, but not live.\n * - Your app is not previewing anything (that could be detected).\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useDraftModeEnvironment(): DraftEnvironment {\n const subscribe = useCallback((listener: () => void) => {\n environmentListeners.add(listener)\n return () => environmentListeners.delete(listener)\n }, [])\n\n return useSyncExternalStore(\n subscribe,\n () => environment,\n () => 'checking',\n )\n}\n\n/**\n * Reports the Sanity Client perspective used to fetch data in `sanityFetch` used on the page.\n * If the hook is used outside Draft Mode it will resolve to `'unknown'`.\n * If the hook is used but the `<SanityLive />` component is not present then it'll stay in `'checking'` and console warn after a timeout that it seems like you're missing the component.\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useDraftModePerspective(): DraftPerspective {\n const subscribe = useCallback((listener: () => void) => {\n perspectiveListeners.add(listener)\n return () => perspectiveListeners.delete(listener)\n }, [])\n\n return useSyncExternalStore(\n subscribe,\n () => perspective,\n () => 'checking',\n )\n}\n","import {useDraftModeEnvironment} from './useDraftMode'\n\n/**\n * Detects if the application is considered to be in a \"Live Preview\" mode.\n * Live Preview means that the application is either:\n * - being previewed inside Sanity Presentation Tool\n * - being previewed in Draft Mode, with a `browserToken` given to `defineLive`, also known as \"Standalone Live Preview'\"\n * When in Live Preview mode, you typically want UI to update as new content comes in, without any manual intervention.\n * This is very different from Live Production mode, where you usually want to delay updates that might cause layout shifts,\n * to avoid interrupting the user that is consuming your content.\n * This hook lets you adapt to this difference, making sure production doesn't cause layout shifts that worsen the UX,\n * while in Live Preview mode layout shift is less of an issue and it's better for the editorial experience to auto refresh in real time.\n *\n * The hook returns `null` initially, to signal it doesn't yet know if it's live previewing or not.\n * Then `true` if it is, and `false` otherwise.\n * @public\n * @deprecated this hook will be removed in the next major version\n */\nexport function useIsLivePreview(): boolean | null {\n const environment = useDraftModeEnvironment()\n return environment === 'checking'\n ? null\n : environment === 'presentation-iframe' ||\n environment === 'presentation-window' ||\n environment === 'live'\n}\n","import type {ClientPerspective, ClientReturn, ContentSourceMap, QueryParams} from '@sanity/client'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport type {LoaderControllerMsg} from '@sanity/presentation-comlink'\nimport {dequal} from 'dequal/lite'\nimport {useEffect, useMemo, useReducer, useSyncExternalStore, useEffectEvent} from 'react'\n\nimport {\n comlinkDataset,\n comlinkListeners,\n comlinkProjectId,\n comlink as comlinkSnapshot,\n} from './context'\nimport {useDraftModePerspective} from './useDraftMode'\n\n/** @alpha */\nexport type UsePresentationQueryReturnsInactive = {\n data: null\n sourceMap: null\n perspective: null\n}\n\n/** @alpha */\nexport type UsePresentationQueryReturnsActive<QueryString extends string> = {\n data: ClientReturn<QueryString>\n sourceMap: ContentSourceMap | null\n perspective: ClientPerspective\n}\n\nexport type UsePresentationQueryReturns<QueryString extends string> =\n | UsePresentationQueryReturnsInactive\n | UsePresentationQueryReturnsActive<QueryString>\n\ntype Action<QueryString extends string> = {\n type: 'query-change'\n payload: UsePresentationQueryReturnsActive<QueryString>\n}\n\nfunction reducer<QueryString extends string>(\n state: UsePresentationQueryReturns<QueryString>,\n {type, payload}: Action<QueryString>,\n): UsePresentationQueryReturns<QueryString> {\n switch (type) {\n case 'query-change':\n return dequal(state, payload)\n ? state\n : {\n ...state,\n data: dequal(state.data, payload.data)\n ? // oxlint-disable-next-line no-unsafe-type-assertion\n (state.data as ClientReturn<QueryString>)\n : payload.data,\n sourceMap: dequal(state.sourceMap, payload.sourceMap)\n ? state.sourceMap\n : payload.sourceMap,\n perspective: dequal(state.perspective, payload.perspective)\n ? // oxlint-disable-next-line no-unsafe-type-assertion\n (state.perspective as Exclude<ClientPerspective, 'raw'>)\n : payload.perspective,\n }\n default:\n return state\n }\n}\nconst initialState: UsePresentationQueryReturnsInactive = {\n data: null,\n sourceMap: null,\n perspective: null,\n}\n\nfunction subscribe(listener: () => void) {\n comlinkListeners.add(listener)\n return () => comlinkListeners.delete(listener)\n}\n\nconst EMPTY_QUERY_PARAMS: QueryParams = {}\nconst LISTEN_HEARTBEAT_INTERVAL = 10_000\n\n/**\n * Experimental hook that can run queries in Presentation Tool.\n * Query results are sent back over postMessage whenever the query results change.\n * It also works with optimistic updates in the studio itself, offering low latency updates.\n * It's not as low latency as the `useOptimistic` hook, but it's a good compromise for some use cases.\n * Especially until `useOptimistic` propagates edits in the Studio parent window back into the iframe.\n * @alpha\n */\nexport function usePresentationQuery<const QueryString extends string>(props: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n stega?: boolean\n}): UsePresentationQueryReturns<QueryString> {\n const [state, dispatch] = useReducer(reducer, initialState)\n const {query, params = EMPTY_QUERY_PARAMS, stega = true} = props\n\n /**\n * Comlink forwards queries we want to run to the parent window where Presentation Tool handles it for us\n */\n const comlink = useSyncExternalStore(\n subscribe,\n () => comlinkSnapshot,\n () => null,\n )\n /**\n * The comlink events requires projectId and dataset, Presentation Tool uses it to protect against project and dataset mismatch errors.\n * We don't want to force the consumers of the `usePresentationQuery` hook to provide these,\n * so we set them in the component that establishes the comlink connection and propagates it to all the subscribes.\n */\n const projectId = useSyncExternalStore(\n subscribe,\n () => comlinkProjectId,\n () => null,\n )\n const dataset = useSyncExternalStore(\n subscribe,\n () => comlinkDataset,\n () => null,\n )\n /**\n * The perspective is kept in sync with Presentation Tool's perspective, and even knows what perspective the page loaded with initially and can forward it to the Sanity Studio.\n */\n const perspective = useDraftModePerspective()\n const handleQueryHeartbeat = useEffectEvent((comlink: NonNullable<typeof comlinkSnapshot>) => {\n // Handle odd case where the comlink can take events but some data is missing\n if (!projectId || !dataset || !perspective) {\n console.warn('usePresentationQuery: projectId, dataset and perspective must be set', {\n projectId,\n dataset,\n perspective,\n })\n return\n }\n // Another odd case where the initial perspective states haven't resolved to the actual perspective state\n if (perspective === 'checking' || perspective === 'unknown') {\n return\n }\n comlink.post('loader/query-listen', {\n projectId,\n dataset,\n perspective,\n query,\n params,\n heartbeat: LISTEN_HEARTBEAT_INTERVAL,\n })\n })\n const handleQueryChange = useEffectEvent(\n (event: Extract<LoaderControllerMsg, {type: 'loader/query-change'}>['data']) => {\n if (\n dequal(\n {\n projectId,\n dataset,\n query,\n params,\n },\n {\n projectId: event.projectId,\n dataset: event.dataset,\n query: event.query,\n params: event.params,\n },\n )\n ) {\n dispatch({\n type: 'query-change',\n payload: {\n data: event.result,\n sourceMap: event.resultSourceMap || null,\n perspective: event.perspective,\n },\n })\n }\n },\n )\n useEffect(() => {\n if (!comlink) return\n\n const unsubscribe = comlink.on('loader/query-change', handleQueryChange)\n const interval = setInterval(() => handleQueryHeartbeat(comlink), LISTEN_HEARTBEAT_INTERVAL)\n return () => {\n clearInterval(interval)\n unsubscribe()\n }\n }, [comlink])\n\n return useMemo(() => {\n if (stega && state.sourceMap) {\n return {\n ...state,\n data: stegaEncodeSourceMap(state.data, state.sourceMap, {enabled: true, studioUrl: '/'}),\n }\n }\n return state\n }, [state, stega])\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,SAAgB,0BAA4C;AAM1D,QAAO,qBALW,aAAa,aAAyB;AACtD,uBAAqB,IAAI,SAAS;AAClC,eAAa,qBAAqB,OAAO,SAAS;IACjD,EAAE,CAGM,QACH,mBACA,WACP;;;;;;;;;AAUH,SAAgB,0BAA4C;AAM1D,QAAO,qBALW,aAAa,aAAyB;AACtD,uBAAqB,IAAI,SAAS;AAClC,eAAa,qBAAqB,OAAO,SAAS;IACjD,EAAE,CAGM,QACH,mBACA,WACP;;;;;;;;;;;;;;;;;;AClCH,SAAgB,mBAAmC;CACjD,MAAM,cAAc,yBAAyB;AAC7C,QAAO,gBAAgB,aACnB,OACA,gBAAgB,yBACd,gBAAgB,yBAChB,gBAAgB;;ACaxB,SAAS,QACP,OACA,EAAC,MAAM,WACmC;AAC1C,SAAQ,MAAR;EACE,KAAK,eACH,QAAO,OAAO,OAAO,QAAQ,GACzB,QACA;GACE,GAAG;GACH,MAAM,OAAO,MAAM,MAAM,QAAQ,KAAK,GAEjC,MAAM,OACP,QAAQ;GACZ,WAAW,OAAO,MAAM,WAAW,QAAQ,UAAU,GACjD,MAAM,YACN,QAAQ;GACZ,aAAa,OAAO,MAAM,aAAa,QAAQ,YAAY,GAEtD,MAAM,cACP,QAAQ;GACb;EACP,QACE,QAAO;;;AAGb,MAAM,eAAoD;CACxD,MAAM;CACN,WAAW;CACX,aAAa;CACd;AAED,SAAS,UAAU,UAAsB;AACvC,kBAAiB,IAAI,SAAS;AAC9B,cAAa,iBAAiB,OAAO,SAAS;;AAGhD,MAAM,qBAAkC,EAAE;AAC1C,MAAM,4BAA4B;;;;;;;;;AAUlC,SAAgB,qBAAuD,OAI1B;CAC3C,MAAM,CAAC,OAAO,YAAY,WAAW,SAAS,aAAa;CAC3D,MAAM,EAAC,OAAO,SAAS,oBAAoB,QAAQ,SAAQ;;;;CAK3D,MAAMA,YAAU,qBACd,iBACMC,eACA,KACP;;;;;;CAMD,MAAM,YAAY,qBAChB,iBACM,wBACA,KACP;CACD,MAAM,UAAU,qBACd,iBACM,sBACA,KACP;;;;CAID,MAAM,cAAc,yBAAyB;CAC7C,MAAM,uBAAuB,gBAAgB,YAAiD;AAE5F,MAAI,CAAC,aAAa,CAAC,WAAW,CAAC,aAAa;AAC1C,WAAQ,KAAK,wEAAwE;IACnF;IACA;IACA;IACD,CAAC;AACF;;AAGF,MAAI,gBAAgB,cAAc,gBAAgB,UAChD;AAEF,UAAQ,KAAK,uBAAuB;GAClC;GACA;GACA;GACA;GACA;GACA,WAAW;GACZ,CAAC;GACF;CACF,MAAM,oBAAoB,gBACvB,UAA+E;AAC9E,MACE,OACE;GACE;GACA;GACA;GACA;GACD,EACD;GACE,WAAW,MAAM;GACjB,SAAS,MAAM;GACf,OAAO,MAAM;GACb,QAAQ,MAAM;GACf,CACF,CAED,UAAS;GACP,MAAM;GACN,SAAS;IACP,MAAM,MAAM;IACZ,WAAW,MAAM,mBAAmB;IACpC,aAAa,MAAM;IACpB;GACF,CAAC;GAGP;AACD,iBAAgB;AACd,MAAI,CAACD,UAAS;EAEd,MAAM,cAAcA,UAAQ,GAAG,uBAAuB,kBAAkB;EACxE,MAAM,WAAW,kBAAkB,qBAAqBA,UAAQ,EAAE,0BAA0B;AAC5F,eAAa;AACX,iBAAc,SAAS;AACvB,gBAAa;;IAEd,CAACA,UAAQ,CAAC;AAEb,QAAO,cAAc;AACnB,MAAI,SAAS,MAAM,UACjB,QAAO;GACL,GAAG;GACH,MAAM,qBAAqB,MAAM,MAAM,MAAM,WAAW;IAAC,SAAS;IAAM,WAAW;IAAI,CAAC;GACzF;AAEH,SAAO;IACN,CAAC,OAAO,MAAM,CAAC"}