@sanity/themer
0.10.10.10.2
dist/tool.js.map~
dist/tool.js.mapModified+1−1
Index: package/dist/tool.js.map
===================================================================
--- package/dist/tool.js.map
+++ package/dist/tool.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"tool.js","names":["() =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:open')\n addThemerTransitionType('open')\n dispatch({type: 'open'})\n })","() =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:close')\n addThemerTransitionType('close')\n dispatch({type: 'close'})\n })","() =>\n startTransition(() => {\n // We want to trigger rerender in a transition, so that clicking the open button\n // can interrupt a prerender, and hovering over the button won't abort an active view transition.\n // But we also don't want the transition to trigger a view transition,\n // so we intentionally do not set a transition type here.\n dispatch({type: 'prerender'})\n // Do a little fun spin\n if (!open) iconRef.current?.spin()\n })","(root: HTMLButtonElement | null) => {\n const navbar = root?.closest(NAVBAR_SELECTOR)\n\n if (!navbar) return undefined\n\n const resizeObserver = new ResizeObserver((entries) => {\n const height = entries[0]?.borderBoxSize[0]?.blockSize\n\n if (height) {\n startTransition(() => dispatch({type: 'set-navbar-height', height}))\n }\n })\n\n resizeObserver.observe(navbar)\n\n return () => resizeObserver.disconnect()\n }","<Text size={1}>{title}</Text>","prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )","<Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>","shouldDetectNavbarHeight ? observeNavbarHeight : undefined","open ? handleClose : handleOpen","open || busy","<ViewTransition default=\"none\" update={viewTransitionClasses.navbarButtonTooltip.update}>\n <Tooltip animate content={<Text size={1}>{title}</Text>} portal>\n <Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>\n </Tooltip>\n </ViewTransition>","{\n location: 'topbar',\n name: 'themer-topbar',\n // The component itself rather than a `() => <ThemerNavbarButton />`\n // wrapper: the Studio renders `render` as a component, and a wrapper\n // made anew on every render would remount the button each time —\n // resetting its animation\n render: ThemerNavbarButton,\n }","props.__internal_actions ?? []","__internal_actions","props.renderDefault({\n ...props,\n __internal_actions: [...(props.__internal_actions ?? []), button],\n })","{\n children,\n config,\n dispatch,\n open,\n shouldDetectNavbarHeight,\n split,\n}: {children: React.ReactNode; shouldDetectNavbarHeight?: boolean} & Pick<\n ThemerProps,\n 'dispatch' | 'config' | 'open' | 'split'\n>","<ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>","shouldDetectNavbarHeight ?? false","<ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>","<ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>","<ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>","<PluginConfigContext value={config}>\n <ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>\n </PluginConfigContext>","_c","unknown","{actorRef}: Pick<ThemerProps, 'actorRef'>","() => syncThemer(actorRef, {persist: writePersistedSnapshot})","[actorRef]","_c","{\n children,\n backgroundColor,\n config,\n dispatch,\n navbarHeight,\n open,\n persistedSnapshot,\n prerender,\n prerenderSplitScreen,\n scheme,\n split,\n}: ThemerLayoutProps & {backgroundColor: string | undefined; scheme: ThemeColorSchemeKey}","{snapshot: persistedSnapshot}","{backgroundColor}","prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )","<ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>","<Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>","split && open ? 'visible' : 'hidden'","<ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>","prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )","<ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>","<Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>","open ? 'visible' : 'hidden'","<Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>","<ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />","<RevokeImageUrls actorRef={actorRef} />","<PersistActorSnapshot actorRef={actorRef} />","<>\n <Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>\n\n <ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />\n <RevokeImageUrls actorRef={actorRef} />\n <PersistActorSnapshot actorRef={actorRef} />\n </>","{\n borderRight,\n children,\n config,\n dispatch,\n open,\n scheme,\n shouldDetectNavbarHeight,\n split,\n}: {\n borderRight?: boolean\n scheme?: ThemeColorSchemeKey\n} & React.ComponentProps<typeof ThemerNavbarProvider>","(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }","boundaryElement","() => {}","<ViewTransition update=\"none\">{children}</ViewTransition>","<ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>","<BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>","<Card\n borderRight={borderRight}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n scheme={scheme}\n ref={(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }}\n >\n <BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>\n </Card>","{actorRef, dispatch}: Pick<ThemerProps, 'actorRef' | 'dispatch'>","() => {\n const subscription = actorRef.subscribe((snapshot) => {\n const switching = snapshot.hasTag('switching')\n const theme = resolveActiveThemeOptions(snapshot.context)\n\n startTransition(() => {\n if (switching) {\n addThemerTransitionType('crossfade')\n }\n if (!theme) {\n dispatch({type: 'unset-theme'})\n } else {\n dispatch({type: 'set-theme', theme})\n }\n })\n })\n\n return () => subscription.unsubscribe()\n }","[actorRef, dispatch]","{actorRef}: Pick<ThemerProps, 'actorRef'>","() => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n }","[actorRef]","_c","_Fragment","{\n children,\n config,\n}: {\n children: React.ReactNode\n} & Pick<ThemerProps, 'config'>","(initialState) => {\n if (!persistedSnapshot) return initialState\n const theme = resolveActiveThemeOptions(persistedSnapshot.context)\n return theme ? {...initialState, theme} : initialState\n }","(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined","theme","<ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>","navbarHeight","prerender","prerenderSplitScreen","state.theme?.[scheme]?.background","<ThemeProvider theme={(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined}>\n <ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>\n </ThemeProvider>","_c","props.renderDefault(props)","<ThemerProvider config={config}>{props.renderDefault(props)}</ThemerProvider>","_c"],"sources":["../src/tool/context.ts","../src/tool/ThemerNavbar.tsx","../src/tool/reducer.ts","../src/tool/selectors.ts","../src/tool/schemas.ts","../src/tool/storage.ts","../src/tool/machine.ts","../src/tool/sync.ts","../src/tool/PersistActorSnapshot.tsx","../src/tool/ThemerLayout.tsx","../src/tool/ThemerProvider.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {createContext} from 'react'\n\nimport type {PluginConfig} from '#types'\n\nimport type {ToolReducerAction} from './reducer'\n\n/*\n * The tool reducer's state that the navbar toggle shows and changes. The\n * Studio renders the navbar itself, out of the layout's reach, so this is\n * where the reducer's values travel by context — the layout and the sidebar\n * get them as props. One context per value, on purpose: a consumer only\n * re-renders for the value it reads, where one object would re-render every\n * consumer for any change.\n */\n\n/** @internal */\nexport const ToolIsOpenContext = createContext<boolean>(false)\n/**\n * The global top-level useReducer dispatch() for state that persists through open/close of the tool\n * @internal */\nexport const ToolDispatchContext = createContext<React.Dispatch<ToolReducerAction>>(() => {\n throw new Error('ToolDispatchContext not initialized')\n})\n/** @internal */\nexport const ToolSplitIsOpenContext = createContext<boolean>(false)\n\n/** @internal */\nexport const ToolShouldDetectNavbarHeightContext = createContext<boolean>(false)\n\n/** @internal */\nexport const PluginConfigContext = createContext<Required<PluginConfig> | null>(null)\n\n/** @internal */\nexport type ThemerView =\n | {name: 'list'}\n | {\n name: 'edit'\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n }\n | {name: 'removed'}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {Box, Button, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {\n lazy,\n startTransition,\n Suspense,\n use,\n useRef,\n useTransition,\n ViewTransition,\n useMemo,\n} from 'react'\nimport {type NavbarProps} from 'sanity'\nimport type {NavbarAction} from 'sanity/_dangerously_use_private_internals_that_do_not_follow_semver'\n\nimport {NAVBAR_SELECTOR} from '#constants'\nimport {\n PluginConfigContext,\n ToolDispatchContext,\n ToolIsOpenContext,\n ToolShouldDetectNavbarHeightContext,\n ToolSplitIsOpenContext,\n} from '#context'\nimport type {ThemerProps} from '#types'\n\nimport {addThemerTransitionType} from './addThemerTransitionType'\n\nimport {viewTransitionClasses} from './ViewTransitions.css'\n\nconst AnimatedColorWheelIcon = lazy(() => import('./AnimatedColorWheelIcon'))\n\nfunction ThemerNavbarButton() {\n const config = use(PluginConfigContext)\n const dispatch = use(ToolDispatchContext)\n const open = use(ToolIsOpenContext)\n const shouldDetectNavbarHeight = use(ToolShouldDetectNavbarHeightContext)\n const split = use(ToolSplitIsOpenContext)\n\n if (!config) {\n throw new TypeError('PluginConfigContext.Provider is missing')\n }\n\n const [busy, startBusyTransition] = useTransition()\n const iconRef = useRef<{spin: () => void}>(null)\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const handleOpen = () =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:open')\n addThemerTransitionType('open')\n dispatch({type: 'open'})\n })\n const handleClose = () =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:close')\n addThemerTransitionType('close')\n dispatch({type: 'close'})\n })\n const handlePrerender = () =>\n startTransition(() => {\n // We want to trigger rerender in a transition, so that clicking the open button\n // can interrupt a prerender, and hovering over the button won't abort an active view transition.\n // But we also don't want the transition to trigger a view transition,\n // so we intentionally do not set a transition type here.\n dispatch({type: 'prerender'})\n // Do a little fun spin\n if (!open) iconRef.current?.spin()\n })\n\n // The sidebar's header takes the height of the Studio navbar, so the two\n // line up — whatever height the Studio, its breakpoint or a custom navbar\n // gives it. The button sits inside the navbar, so it is the one to measure\n // it, as a ref callback: observed from mount to unmount, in a transition,\n // since nothing urgent hangs on the number\n const observeNavbarHeight = (root: HTMLButtonElement | null) => {\n const navbar = root?.closest(NAVBAR_SELECTOR)\n\n if (!navbar) return undefined\n\n const resizeObserver = new ResizeObserver((entries) => {\n const height = entries[0]?.borderBoxSize[0]?.blockSize\n\n if (height) {\n startTransition(() => dispatch({type: 'set-navbar-height', height}))\n }\n })\n\n resizeObserver.observe(navbar)\n\n return () => resizeObserver.disconnect()\n }\n\n const {title} = config\n\n // The `Box` is here because a `ViewTransition` cannot be the direct child\n // of the `Tooltip`: it needs a child that renders a DOM node\n return (\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButtonTooltip.update}>\n <Tooltip animate content={<Text size={1}>{title}</Text>} portal>\n <Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>\n </Tooltip>\n </ViewTransition>\n )\n}\n\nexport function ThemerNavbar(props: NavbarProps) {\n const button = useMemo(\n () =>\n ({\n location: 'topbar',\n name: 'themer-topbar',\n // The component itself rather than a `() => <ThemerNavbarButton />`\n // wrapper: the Studio renders `render` as a component, and a wrapper\n // made anew on every render would remount the button each time —\n // resetting its animation\n render: ThemerNavbarButton,\n }) satisfies NavbarAction,\n [],\n )\n return props.renderDefault({\n ...props,\n __internal_actions: [...(props.__internal_actions ?? []), button],\n })\n}\n\n// It can be easy to forget which contexts that ThemerNavbar requires and expects to be defined,\n// especially since TypeScript is unable to detect missing contexts statically,\n// parent views uses this component to ensure it doesn't miss anything and the props interface gives us type check validation.\nexport function ThemerNavbarProvider({\n children,\n config,\n dispatch,\n open,\n shouldDetectNavbarHeight,\n split,\n}: {children: React.ReactNode; shouldDetectNavbarHeight?: boolean} & Pick<\n ThemerProps,\n 'dispatch' | 'config' | 'open' | 'split'\n>) {\n return (\n <PluginConfigContext value={config}>\n <ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>\n </PluginConfigContext>\n )\n}\n","import {dequal} from 'dequal/lite'\n\nimport type {BuildThemeOptions} from '../theme/options'\n\n/**\n * What the layout shows, owned by the `useReducer` in `ThemerProvider` and\n * never persisted — it is each tab's own — handed down to the layout and the\n * sidebar as props and to the navbar toggle through the `Tool*Context`s:\n * whether the sidebar is open and whether the Studio shows twice, whether\n * either has been prerendered yet — they mount ahead of their first showing,\n * so that the code loads and the tree is warm before the transition — the\n * options of the applied theme, as the machine publishes them, and the\n * height of the Studio navbar, as the navbar toggle measures it, for the\n * sidebar's header to match.\n */\nexport type ToolReducerState = {\n navbarHeight: number | null\n open: boolean\n prerender: boolean\n prerenderSplitScreen: boolean\n split: boolean\n theme: BuildThemeOptions | null\n}\n\nexport type ToolReducerAction =\n | {type: 'set-navbar-height'; height: number}\n | {type: 'prerender'}\n | {type: 'open'}\n | {type: 'close'}\n | {type: 'split-screen:prerender'}\n | {type: 'split-screen:open'}\n | {type: 'split-screen:close'}\n | {type: 'set-theme'; theme: BuildThemeOptions}\n | {type: 'unset-theme'}\n\nexport function toolReducer(state: ToolReducerState, action: ToolReducerAction): ToolReducerState {\n switch (action.type) {\n case 'set-navbar-height':\n return state.navbarHeight === action.height ? state : {...state, navbarHeight: action.height}\n case 'prerender':\n return state.prerender ? state : {...state, prerender: true}\n case 'open':\n return state.open ? state : {...state, open: true, prerender: true}\n case 'close':\n return state.open ? {...state, open: false} : state\n case 'split-screen:prerender':\n return state.prerenderSplitScreen ? state : {...state, prerenderSplitScreen: true}\n case 'split-screen:open':\n return state.split ? state : {...state, prerenderSplitScreen: true, split: true}\n case 'split-screen:close':\n return state.split ? {...state, split: false} : state\n case 'unset-theme':\n return state.theme === null ? state : {...state, theme: null}\n case 'set-theme':\n return dequal(state.theme, action.theme) ? state : {...state, theme: action.theme}\n default: {\n // Every action is handled above, so only a dispatch the types do not\n // know about gets here — a bug worth hearing of, not a silent no-op\n const unknown: never = action\n throw new Error(`Unknown themer tool action: ${JSON.stringify(unknown)}`)\n }\n }\n}\n\n/**\n * Where a tab starts: closed, nothing prerendered, no theme applied —\n * `ThemerProvider` seeds only the applied theme, from the machine's restored\n * snapshot\n */\nexport const initialToolState = {\n navbarHeight: null,\n open: false,\n prerender: false,\n prerenderSplitScreen: false,\n split: false,\n theme: null,\n} as const satisfies ToolReducerState\n","import type {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport type {ThemerState} from './themes'\n\n/**\n * The options of the applied theme — `null` for the configured theme, which\n * applies nothing on top of the Studio's own, and for an applied theme that\n * no longer resolves. Cheaper than resolving the whole list when only the\n * applied theme matters.\n *\n * @internal\n */\nexport function resolveActiveThemeOptions(state: ThemerState): BuildThemeOptions | null {\n if (state.active === null || state.removed.includes(state.active)) return null\n\n for (const theme of state.custom) {\n if (theme.slug === state.active) return theme.options\n }\n for (const preset of presets) {\n if (preset.slug === state.active) return preset.options\n }\n\n return null\n}\n","import * as v from 'valibot'\n\nimport {isColor} from '../lib/mix'\nimport {\n type BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n type SchemeThemeOptions,\n} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport type {ImagePalette} from './imagePalette'\nimport type {ThemerMachineContext} from './machine'\nimport {CONFIG_SLUG, type CustomTheme, displayTitle, type ThemerState} from './themes'\n\n/*\n * What the tool keeps in `localStorage` is parsed with these schemas as it is\n * read back, so that a session starts from what it can use of what an\n * earlier one left — whatever version of the tool wrote it, and whatever\n * happened to it since. The schemas are lenient where the old sanitizers\n * were: a color that is not one is dropped rather than failing the theme it\n * is part of, a theme that cannot be used is dropped rather than failing the\n * list, and the shapes of earlier versions are converted into today's.\n */\n\n/** Drops the keys whose value is `undefined`, which a dropped option leaves behind */\nfunction compact<T extends object>(value: T): T {\n // oxlint-disable-next-line no-unsafe-type-assertion -- the same keys, minus the undefined ones\n return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T\n}\n\n/** A `#rgb` or `#rrggbb` color, lowercased */\nconst color = v.pipe(v.string(), v.check(isColor), v.toLowerCase())\n\n/** A color that is dropped rather than failing what it is part of */\nconst optionalColor = v.fallback(v.optional(color), undefined)\n\n/** A contrast, clamped into range — dropped when it is not a number */\nconst optionalContrast = v.fallback(\n v.optional(\n v.pipe(v.number(), v.finite(), v.toMinValue(MINIMUM_CONTRAST), v.toMaxValue(MAXIMUM_CONTRAST)),\n ),\n undefined,\n)\n\nconst schemeOptionsSchema = v.pipe(\n v.object({\n accent: optionalColor,\n text: optionalColor,\n background: optionalColor,\n contrast: optionalContrast,\n }),\n v.transform((options): SchemeThemeOptions => compact(options)),\n)\n\n/** Options grouped by scheme, as they are today — a scheme without usable options is left out */\nconst groupedOptionsSchema = v.pipe(\n v.looseObject({\n light: v.fallback(v.optional(schemeOptionsSchema), undefined),\n dark: v.fallback(v.optional(schemeOptionsSchema), undefined),\n }),\n // The flat shape of earlier versions always had an accent at the top\n v.check((input) => !('accent' in input), 'flat options'),\n v.transform(({light, dark}): BuildThemeOptions => {\n const options: BuildThemeOptions = {}\n\n if (light && Object.keys(light).length > 0) options.light = light\n if (dark && Object.keys(dark).length > 0) options.dark = dark\n\n return options\n }),\n)\n\n/**\n * Options from before they were grouped by scheme: one accent, text color and\n * contrast for both schemes, and a background per scheme. Both schemes get\n * the shared colors, so the theme keeps looking the same.\n */\nconst flatOptionsSchema = v.pipe(\n v.object({\n accent: color,\n text: optionalColor,\n contrast: optionalContrast,\n background: v.fallback(\n v.optional(v.object({light: optionalColor, dark: optionalColor})),\n undefined,\n ),\n }),\n v.transform(({accent, text, contrast, background}): BuildThemeOptions => {\n const shared: SchemeThemeOptions = compact({accent, text, contrast})\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const schemeBackground = background?.[scheme]\n\n options[scheme] = schemeBackground ? {...shared, background: schemeBackground} : {...shared}\n }\n\n return options\n }),\n)\n\n/** The `buildTheme` options of a theme, in today's shape or an earlier version's @internal */\nexport const themeOptionsSchema = v.union([flatOptionsSchema, groupedOptionsSchema])\n\n/** A swatch of an image palette — `null` when it is missing or not a color */\nconst swatch = v.fallback(v.optional(v.nullable(color), null), null)\n\n/** The palette a theme took its colors from — `null` without a single swatch */\nconst paletteSchema = v.pipe(\n v.object({\n vibrant: swatch,\n lightVibrant: swatch,\n darkVibrant: swatch,\n muted: swatch,\n lightMuted: swatch,\n darkMuted: swatch,\n dominant: swatch,\n }),\n v.transform((palette): ImagePalette | null =>\n Object.values(palette).some((item) => item !== null) ? palette : null,\n ),\n)\n\nfunction isReservedSlug(slug: string): boolean {\n return slug === CONFIG_SLUG || presets.some((preset) => preset.slug === slug)\n}\n\nconst customThemeSchema = v.pipe(\n v.object({\n slug: v.pipe(\n v.string(),\n v.nonEmpty(),\n v.check((slug) => !isReservedSlug(slug), 'reserved slug'),\n ),\n title: v.fallback(v.optional(v.string()), undefined),\n options: themeOptionsSchema,\n palette: v.fallback(v.optional(paletteSchema), undefined),\n }),\n v.transform(({slug, title, options, palette}): CustomTheme => ({\n slug,\n title: displayTitle(title ?? ''),\n options,\n ...(palette ? {palette} : {}),\n })),\n)\n\n/** The items of a list that can be used, each parsed on its own so that one bad item does not fail the rest */\nconst lenientList = v.fallback(v.optional(v.array(v.unknown()), []), [])\n\n/**\n * The persisted themer state — the applied theme, the user's themes and\n * what was removed and reordered — as it is read back. Custom themes that\n * cannot be used, repeated slugs, and removed or ordered slugs that name no\n * theme are dropped.\n */\nconst themerStateSchema = v.pipe(\n v.object({\n active: v.fallback(v.nullable(v.string()), null),\n custom: lenientList,\n removed: lenientList,\n order: lenientList,\n }),\n v.transform((state): ThemerState => {\n const custom: CustomTheme[] = []\n\n for (const item of state.custom) {\n const result = v.safeParse(customThemeSchema, item)\n\n if (result.success && !custom.some((theme) => theme.slug === result.output.slug)) {\n custom.push(result.output)\n }\n }\n\n const isCustom = (slug: string) => custom.some((theme) => theme.slug === slug)\n const isPreset = (slug: string) => presets.some((preset) => preset.slug === slug)\n const removed: string[] = []\n const order: string[] = []\n\n for (const slug of state.removed) {\n if (typeof slug !== 'string' || slug === CONFIG_SLUG || removed.includes(slug)) continue\n if (isPreset(slug) || isCustom(slug)) removed.push(slug)\n }\n for (const slug of state.order) {\n if (typeof slug !== 'string' || order.includes(slug)) continue\n if (slug === CONFIG_SLUG || isPreset(slug) || isCustom(slug)) order.push(slug)\n }\n\n return {active: state.active, custom, removed, order}\n }),\n)\n\n/** The persisted themer state, or `null` for anything else @internal */\nexport function parseThemerState(input: unknown): ThemerState | null {\n const result = v.safeParse(themerStateSchema, input)\n\n return result.success ? result.output : null\n}\n\n/** The flows a session restores to */\nconst flowSchema = v.picklist(['list', 'edit', 'removed'])\n\n/**\n * The machine's state value, as persisted — only what the machine has states\n * for restores. The list's dialogs are states of the list, so a snapshot\n * written while one was open names it; the dialog does not reopen\n */\nconst snapshotValueSchema = v.object({\n flow: v.union([flowSchema, v.object({list: v.picklist(['idle', 'pasting', 'snippet'])})]),\n theme: v.picklist(['applied', 'switching']),\n})\n\n/**\n * The machine's persisted snapshot, as `getPersistedSnapshot()` writes it and\n * `createActor` restores it — `status`, `value`, `context`, `historyValue`\n * and `children`, with `output` and `error` left out as `undefined`.\n *\n * @internal\n */\nexport interface PersistedThemerSnapshot {\n status: 'active'\n output: undefined\n error: undefined\n value: {flow: v.InferOutput<typeof flowSchema>; theme: 'applied' | 'switching'}\n context: ThemerMachineContext\n historyValue: Record<string, never>\n children: Record<string, never>\n}\n\n/**\n * A snapshot for the machine to start a session from: the given themes, with\n * nothing being switched to, in the list unless a flow and its subject are\n * given. It is how the themes alone — all that earlier versions persisted —\n * are handed to a machine that only restores snapshots, and what a restored\n * snapshot is made fit for this session as.\n *\n * @internal\n */\nexport function snapshotFromState(\n state: ThemerState,\n editing: ThemerMachineContext['editing'] = null,\n flow: PersistedThemerSnapshot['value']['flow'] = 'list',\n): PersistedThemerSnapshot {\n return {\n status: 'active',\n output: undefined,\n error: undefined,\n value: {flow, theme: 'applied'},\n context: {...state, editing, images: {}},\n historyValue: {},\n children: {},\n }\n}\n\n/**\n * The machine's snapshot as the last session persisted it, made fit for this\n * one: the themes are parsed like any persisted state, the flow keeps its\n * subject or falls back to the list, a dialog that was open is closed, a\n * switch that was under way is over, and the image URLs of the last session\n * are gone with it.\n *\n * @internal\n */\nexport const persistedSnapshotSchema = v.pipe(\n v.object({\n status: v.literal('active'),\n value: snapshotValueSchema,\n context: v.pipe(\n v.looseObject({\n editing: v.fallback(\n v.nullable(v.object({slug: v.string(), focusTitle: v.boolean()})),\n null,\n ),\n }),\n v.transform(({editing, ...state}) => ({editing, state: parseThemerState(state)})),\n v.check(({state}) => state !== null, 'no themes'),\n ),\n }),\n v.transform(({value, context}): PersistedThemerSnapshot => {\n // Checked above, but the type does not know\n const state = context.state ?? {active: null, custom: [], removed: [], order: []}\n const editing =\n context.editing &&\n state.custom.some((theme) => theme.slug === context.editing?.slug) &&\n !state.removed.includes(context.editing.slug)\n ? context.editing\n : null\n const flow =\n typeof value.flow !== 'string' ||\n (value.flow === 'edit' && editing === null) ||\n (value.flow === 'removed' && state.removed.length === 0)\n ? 'list'\n : value.flow\n\n return snapshotFromState(state, editing, flow)\n }),\n)\n","import * as v from 'valibot'\nimport type {Snapshot} from 'xstate'\n\nimport {\n parseThemerState,\n type PersistedThemerSnapshot,\n persistedSnapshotSchema,\n snapshotFromState,\n themeOptionsSchema,\n} from './schemas'\nimport type {CustomTheme} from './themes'\n\n/** Where the machine's snapshot is kept between sessions */\nconst SNAPSHOT_STORAGE_KEY = 'sanityStudio:themer:snapshot'\n\n/**\n * Where earlier versions kept the persisted state — the applied theme, the\n * user's themes and what was removed and reordered — before the machine's\n * whole snapshot was persisted. Read until the first snapshot is written.\n */\nconst STATE_STORAGE_KEY = 'sanityStudio:themer:state'\n\n/**\n * Where the earliest versions of the tool kept their single draft theme — it\n * is migrated into a custom theme, so a draft survives the upgrade.\n */\nconst LEGACY_STORAGE_KEY = 'sanityStudio:themer:options'\n\n/**\n * What the draft of the earliest versions is imported as — the same theme\n * whichever tab reads it first, so that tabs upgrading side by side agree\n */\nconst LEGACY_DRAFT: Omit<CustomTheme, 'options'> = {slug: 'custom-draft', title: 'Draft theme'}\n\nfunction readJson(key: string): unknown {\n const raw = localStorage.getItem(key)\n\n return raw ? JSON.parse(raw) : undefined\n}\n\n/**\n * Restores the machine's snapshot from what the last session persisted, for\n * `createActor` to start from, so the user's themes survive Studio reloads:\n * the snapshot itself where there is one that fits, and one made from the\n * themes alone where the snapshot no longer fits the machine or where\n * earlier versions of the tool stored the themes without a snapshot.\n * `undefined` when nothing usable is stored — or storage cannot be read at\n * all — leaves the machine to start from no themes.\n *\n * The parsing is the schemas' (see `schemas.ts`): nothing here or in the\n * machine looks at what is stored beyond handing it over.\n *\n * @internal\n */\nexport function readPersistedSnapshot(): PersistedThemerSnapshot | undefined {\n try {\n if (typeof localStorage === 'undefined') return undefined\n\n const persisted = readJson(SNAPSHOT_STORAGE_KEY)\n\n if (persisted !== undefined) {\n const snapshot = v.safeParse(persistedSnapshotSchema, persisted)\n\n if (snapshot.success) return snapshot.output\n\n // A snapshot from a version of the machine with other states still\n // holds the themes, which is what matters\n const state = parseThemerState(\n persisted && typeof persisted === 'object' ? Reflect.get(persisted, 'context') : undefined,\n )\n\n if (state) return snapshotFromState(state)\n }\n\n const state = parseThemerState(readJson(STATE_STORAGE_KEY))\n\n if (state) return snapshotFromState(state)\n\n return readLegacyDraft()\n } catch {\n return undefined\n }\n}\n\nfunction readLegacyDraft(): PersistedThemerSnapshot | undefined {\n const options = v.safeParse(themeOptionsSchema, readJson(LEGACY_STORAGE_KEY))\n\n if (!options.success) return undefined\n\n const theme: CustomTheme = {...LEGACY_DRAFT, options: options.output}\n\n return snapshotFromState({active: theme.slug, custom: [theme], removed: [], order: []})\n}\n\n/**\n * Persists the machine's snapshot for the next session, and lets go of what\n * earlier versions stored once it is written — which completes their\n * migration.\n *\n * @internal\n */\nexport function writePersistedSnapshot(snapshot: Snapshot<unknown>): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n localStorage.setItem(SNAPSHOT_STORAGE_KEY, JSON.stringify(snapshot))\n localStorage.removeItem(STATE_STORAGE_KEY)\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n } catch {\n // Storage can be unavailable (e.g. private browsing) — themes just won't persist\n }\n}\n","import {assign, not, setup, type SnapshotFrom} from 'xstate'\n\nimport type {BuildThemeOptions} from '../theme/options'\nimport type {ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n CONFIG_TITLE,\n createCustomTheme,\n duplicateTitle,\n initialThemerState,\n resolveThemes,\n type ThemerState,\n UNTITLED_THEME,\n} from './themes'\n\n/**\n * How long the machine stays `switching` after an event that applies another\n * theme, when no edit to the applied theme ends it sooner. The layout\n * cross-fades the Studio to the theme in the commit that follows the switch,\n * so this only bounds how long the tag lingers once that is done. Generous,\n * so that it never runs out before a slow deferred commit starts the fade.\n *\n * @internal\n */\nexport const MOTION_DURATION = 2000\n\n/** @internal */\nexport interface ThemerMachineContext extends ThemerState {\n /** The theme open in the editor, while the `flow` is `edit` */\n editing: {\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n } | null\n /**\n * Object URLs of the images that themes took their palette from, by theme\n * slug — kept for the session only, so the image can be shown right after\n * it was picked\n */\n images: Record<string, string>\n}\n\n/** @internal */\nexport type ThemerEvent =\n /** Applies a theme to the whole Studio */\n | {type: 'theme.pick'; slug: string}\n /**\n * Adds a theme and opens it in the editor — based on the applied theme, or\n * on the given title, options and image palette (e.g. from an image)\n */\n | {\n type: 'theme.add'\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Adds a copy of a theme and opens it in the editor */\n | {type: 'theme.duplicate'; slug: string}\n /** Adds a theme someone shared as a code, and applies it — staying in the list */\n | {type: 'theme.import'; title: string; options: BuildThemeOptions}\n /** Opens one of the user's own themes in the editor */\n | {type: 'theme.edit'; slug: string}\n | {\n type: 'theme.update'\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Takes a theme out of the list — it can be restored until it is deleted */\n | {type: 'theme.remove'; slug: string}\n /** Puts a removed theme back in the list */\n | {type: 'theme.restore'; slug: string}\n /** Deletes one of the user's own themes for good */\n | {type: 'theme.delete'; slug: string}\n /** Rearranges the list: the slugs of the listed themes, in their new order */\n | {type: 'theme.reorder'; order: string[]}\n /** Back to picking a theme */\n | {type: 'flow.list'}\n /** On to restoring removed themes */\n | {type: 'flow.removed'}\n /** Opens the dialog for adding a shared theme code by hand, from the list */\n | {type: 'dialog.paste'}\n /** Opens the dialog with the applied theme's `buildTheme` snippet, from the list */\n | {type: 'dialog.snippet'}\n /** Closes whichever dialog is open */\n | {type: 'dialog.close'}\n /**\n * Takes over the persisted state as another tab of the same Studio changed\n * it, so that every tab shows the same themes (see `sync.ts`)\n */\n | {type: 'themes.sync'; state: ThemerState}\n\nfunction themesOf(context: ThemerMachineContext) {\n return resolveThemes(context)\n}\n\nfunction revokeObjectUrl(url: string) {\n if (typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {\n URL.revokeObjectURL(url)\n }\n}\n\n/**\n * The themes of the themer tool and the sidebar's way through them: which\n * `flow` the sidebar is in — picking a theme from the `list`, with its\n * dialogs for pasting a shared code and for the applied theme's snippet,\n * `edit`ing one of the user's own themes, or restoring `removed` ones — and,\n * in a parallel `theme` region, whether another theme was just applied. The\n * context carries the persisted state (the applied theme, the user's themes\n * and what was removed) alongside what the flows need. Whether the sidebar is\n * open and whether the Studio shows twice is not the machine's: the tool\n * reducer in `ThemerProvider` owns that, so that the navbar toggle and the\n * sidebar can change it in transitions of their own. Nor is persisting: the\n * machine never touches storage and takes no input — it starts from no\n * themes, and a session with something to pick up from restores its\n * persisted snapshot instead (see `storage.ts` and `sync.ts`), while the\n * persisted state of other tabs reaches it as a `themes.sync` event like any\n * other.\n *\n * The `theme` region is `switching` (its tag) right after an event that\n * applies another theme — picking one, adding, duplicating, importing or\n * editing one, removing or deleting the applied one — which is when the\n * layout cross-fades the Studio to it. Edits to the applied theme's colors are\n * not switches, they follow the pointer: the first one ends the switch, and so\n * does `MOTION_DURATION` when none follows.\n *\n * Theme operations are handled in every flow, and the flows leave on their\n * own when they lose their subject: the editor when its theme is removed or\n * deleted, and the removed view when its last theme is restored or deleted.\n *\n * @internal\n */\nexport const themerMachine = setup({\n // XState reads `types` for their types only — the values are never used\n types: {\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n context: {} as ThemerMachineContext,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n events: {} as ThemerEvent,\n },\n guards: {\n isCustomTheme: ({context}, params: {slug: string}) =>\n themesOf(context).themes.some(\n (theme) => theme.slug === params.slug && theme.source === 'custom',\n ),\n isEditingListedTheme: ({context}) =>\n context.editing !== null &&\n themesOf(context).themes.some(\n (theme) => theme.slug === context.editing?.slug && theme.source === 'custom',\n ),\n hasRemovedThemes: ({context}) => themesOf(context).removed.length > 0,\n // The configured theme has no code to show — it is the Studio's own\n hasAppliedTheme: ({context}) => themesOf(context).active !== undefined,\n // Only another theme than the applied one changes anything to cross-fade to\n isAnotherTheme: ({context}, params: {slug: string}) =>\n params.slug !== (context.active ?? CONFIG_SLUG),\n // Removing or deleting the applied theme is what changes the applied theme\n isAppliedTheme: ({context}, params: {slug: string}) => params.slug === context.active,\n // Another tab applied another theme, which is as much of a switch as picking it here\n appliesAnotherTheme: ({context}, params: {state: ThemerState}) =>\n params.state.active !== context.active,\n // A theme added from given options (an image's) looks different from the applied one\n hasOptions: (_, params: {options: BuildThemeOptions | undefined}) =>\n params.options !== undefined,\n },\n actions: {\n // Another tab's persisted state replaces this one's: themes, order and\n // all. The images of this session stay, they are this tab's\n sync: assign((_, params: {state: ThemerState}) => ({\n active: params.state.active,\n custom: params.state.custom,\n removed: params.state.removed,\n order: params.state.order,\n })),\n // The image of a replaced palette or a deleted theme is released from memory\n revokeImage: (_, params: {url: string | undefined}) => {\n if (params.url) revokeObjectUrl(params.url)\n },\n pick: assign((_, params: {slug: string}) => ({\n active: params.slug === CONFIG_SLUG ? null : params.slug,\n })),\n add: assign(\n (\n {context},\n params: {\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => {\n // Based on the applied theme — the stock one when the configured\n // theme applies, which is not in the list\n const theme = createCustomTheme(\n params.title ?? UNTITLED_THEME,\n params.options ?? themesOf(context).active?.options ?? {},\n params.palette,\n )\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n // A theme named after its image needs no renaming right away\n editing: {slug: theme.slug, focusTitle: params.title === undefined},\n images: params.imageUrl\n ? {...context.images, [theme.slug]: params.imageUrl}\n : context.images,\n }\n },\n ),\n import: assign(({context}, params: {title: string; options: BuildThemeOptions}) => {\n const theme = createCustomTheme(params.title, params.options)\n\n return {active: theme.slug, custom: [...context.custom, theme]}\n }),\n duplicate: assign(({context}, params: {slug: string}) => {\n const {themes, removed} = themesOf(context)\n // The configured theme is not in the list: a copy of it starts from the\n // stock options, as the Studio shows it\n const source =\n params.slug === CONFIG_SLUG\n ? {title: CONFIG_TITLE, options: {}, palette: undefined}\n : [...themes, ...removed].find((theme) => theme.slug === params.slug)\n\n if (!source) return {}\n\n const theme = createCustomTheme(duplicateTitle(source.title), source.options, source.palette)\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n editing: {slug: theme.slug, focusTitle: true},\n }\n }),\n startEditing: assign((_, params: {slug: string}) => ({\n active: params.slug,\n editing: {slug: params.slug, focusTitle: false},\n })),\n stopEditing: assign({editing: null}),\n update: assign(\n (\n {context},\n params: {\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => ({\n custom: context.custom.map((theme) => {\n if (theme.slug !== params.slug) return theme\n\n // Keep the options identity when only the title changes, so the\n // applied theme is not rebuilt on every keystroke\n return {\n ...theme,\n title: params.title ?? theme.title,\n options: params.options ?? theme.options,\n ...(params.palette ? {palette: params.palette} : {}),\n }\n }),\n images:\n params.imageUrl && context.custom.some((theme) => theme.slug === params.slug)\n ? {...context.images, [params.slug]: params.imageUrl}\n : context.images,\n }),\n ),\n remove: assign(({context}, params: {slug: string}) => {\n if (params.slug === CONFIG_SLUG || context.removed.includes(params.slug)) return {}\n\n return {\n active: context.active === params.slug ? null : context.active,\n removed: [...context.removed, params.slug],\n }\n }),\n restore: assign(({context}, params: {slug: string}) => ({\n removed: context.removed.filter((slug) => slug !== params.slug),\n })),\n reorder: assign(({context}, params: {order: string[]}) => {\n const listed = new Set(themesOf(context).themes.map((theme) => theme.slug))\n const order = params.order.filter((slug) => listed.has(slug))\n\n // The removed themes keep their place in line for when they are restored\n return {\n order: [...order, ...context.order.filter((slug) => !order.includes(slug))],\n }\n }),\n delete: assign(({context}, params: {slug: string}) => {\n if (!context.custom.some((theme) => theme.slug === params.slug)) return {}\n\n const {[params.slug]: _deleted, ...images} = context.images\n\n return {\n active: context.active === params.slug ? null : context.active,\n custom: context.custom.filter((theme) => theme.slug !== params.slug),\n removed: context.removed.filter((slug) => slug !== params.slug),\n images,\n }\n }),\n },\n}).createMachine({\n id: 'themer',\n context: () => ({...initialThemerState, editing: null, images: {}}),\n type: 'parallel',\n states: {\n theme: {\n initial: 'applied',\n states: {\n // Only events that change what is applied count: adding a copy of the\n // applied theme, editing or duplicating it, picking it again, or\n // removing another theme leaves the Studio looking the same — with\n // nothing to cross-fade, the switch would only linger until the first\n // edit that followed, which must follow the pointer\n applied: {\n on: {\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n },\n 'theme.import': 'switching',\n 'themes.sync': {\n guard: {type: 'appliesAnotherTheme', params: ({event}) => ({state: event.state})},\n target: 'switching',\n },\n },\n },\n switching: {\n tags: ['switching'],\n after: {[MOTION_DURATION]: 'applied'},\n on: {\n // The editor edits the applied theme, and its colors must follow\n // the pointer rather than cross-fade — a switch is over at the\n // first edit, however soon it comes\n 'theme.update': 'applied',\n // Another switch starts the clock over\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n reenter: true,\n },\n 'theme.import': {target: 'switching', reenter: true},\n 'themes.sync': {\n guard: {type: 'appliesAnotherTheme', params: ({event}) => ({state: event.state})},\n target: 'switching',\n reenter: true,\n },\n },\n },\n },\n },\n flow: {\n initial: 'list',\n on: {\n 'theme.pick': {\n actions: [{type: 'pick', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.add': {\n target: '.edit',\n actions: [\n {\n type: 'add',\n params: ({event}) => ({\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n ],\n },\n 'theme.duplicate': {\n target: '.edit',\n actions: [{type: 'duplicate', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.edit': {\n target: '.edit',\n guard: {type: 'isCustomTheme', params: ({event}) => ({slug: event.slug})},\n actions: [{type: 'startEditing', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.update': {\n actions: [\n // A new image replaces the one the theme had\n {\n type: 'revokeImage',\n params: ({context, event}) => ({\n url: event.imageUrl ? context.images[event.slug] : undefined,\n }),\n },\n {\n type: 'update',\n params: ({event}) => ({\n slug: event.slug,\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n ],\n },\n 'theme.remove': {\n actions: [{type: 'remove', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.restore': {\n actions: [{type: 'restore', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.delete': {\n actions: [\n {\n type: 'revokeImage',\n params: ({context, event}) => ({url: context.images[event.slug]}),\n },\n {type: 'delete', params: ({event}) => ({slug: event.slug})},\n ],\n },\n 'theme.reorder': {\n actions: [{type: 'reorder', params: ({event}) => ({order: event.order})}],\n },\n 'theme.import': {\n actions: [\n {\n type: 'import',\n params: ({event}) => ({title: event.title, options: event.options}),\n },\n ],\n },\n 'themes.sync': {\n actions: {type: 'sync', params: ({event}) => ({state: event.state})},\n },\n 'flow.list': '.list',\n 'flow.removed': {\n target: '.removed',\n guard: 'hasRemovedThemes',\n },\n },\n states: {\n list: {\n initial: 'idle',\n // The list's dialogs — for a shared code pasted by hand, and for the\n // applied theme's snippet — are states of the list, so that leaving\n // it, as adding a theme does for the editor, closes them\n states: {\n idle: {\n on: {\n 'dialog.paste': 'pasting',\n 'dialog.snippet': {guard: 'hasAppliedTheme', target: 'snippet'},\n },\n },\n pasting: {\n on: {'dialog.close': 'idle'},\n },\n snippet: {\n on: {'dialog.close': 'idle'},\n // The applied theme can go away meanwhile — removed from another tab\n always: {guard: not('hasAppliedTheme'), target: 'idle'},\n },\n },\n },\n edit: {\n exit: 'stopEditing',\n // The theme being edited can go away — removed from the editor, or\n // duplicated from a theme that does not exist — and then there is\n // nothing left to edit\n always: {guard: not('isEditingListedTheme'), target: 'list'},\n },\n removed: {\n always: {guard: not('hasRemovedThemes'), target: 'list'},\n },\n },\n },\n },\n})\n\n/** @internal */\nexport type ThemerSnapshot = SnapshotFrom<typeof themerMachine>\n\n/** The part of the machine context that is persisted between sessions @internal */\nexport function selectStoredState(snapshot: ThemerSnapshot): ThemerState {\n const {active, custom, removed, order} = snapshot.context\n\n return {active, custom, removed, order}\n}\n","import {dequal} from 'dequal/lite'\nimport type {ActorRefFrom, Snapshot} from 'xstate'\n\nimport {selectStoredState, type themerMachine} from './machine'\nimport {parseThemerState} from './schemas'\nimport type {ThemerState} from './themes'\n\n/** The channel every tab of the same origin shares — one Studio, one set of themes */\nconst CHANNEL_NAME = 'sanity-themer'\n\n/**\n * The lock the one tab that persists holds for as long as it lives. The\n * other tabs queue for it, so when that tab goes away the next one in line\n * takes over the persisting — nothing is ever written from two tabs at once\n */\nconst LOCK_NAME = 'sanity-themer:persist'\n\n/** How long a persist waits for the browser to be idle before it goes ahead anyway */\nconst PERSIST_TIMEOUT = 1_000\n\n/**\n * What the tabs tell each other. Every change to the persisted state goes\n * around as `state`, with the `revision` it brings the state to — one more\n * than the last one the tab heard of — and the tab it is `from`: a tab takes\n * a state only when it is newer than the one it has, ties going to the\n * higher tab id, so that two tabs changing at once end up with the same\n * state rather than each other's. A tab that just started asks for the\n * current state with `hello`, answered with a `state` addressed `to` it,\n * which only it takes, and only while it has changed nothing of its own yet\n * — a change it made meanwhile goes around again instead, ahead of the\n * answer, so that a late answer never undoes it\n */\ntype SyncMessage =\n | {type: 'hello'; from: string}\n | {type: 'state'; state: ThemerState; revision: number; from: string; to?: string}\n\n/** What the sync needs of a `BroadcastChannel` @internal */\nexport interface SyncChannel {\n /** Sends a message to the other tabs — not to this one */\n post(message: unknown): void\n /** Hears the other tabs' messages; returns what stops listening */\n listen(listener: (data: unknown) => void): () => void\n close(): void\n}\n\n/** What the sync needs of `navigator.locks` @internal */\nexport interface SyncLocks {\n request(\n name: string,\n options: {signal?: AbortSignal},\n callback: () => Promise<unknown>,\n ): Promise<unknown>\n}\n\n/**\n * What the sync does with the browser, each replaceable — so that it can run\n * under test, and so that it degrades where a browser lacks an API: without\n * a channel the tabs do not hear of each other, and without locks every tab\n * persists for itself\n *\n * @internal\n */\nexport interface ThemerSyncOptions {\n /** Persists the machine's snapshot — from the one tab that holds the lock */\n persist: (snapshot: Snapshot<unknown>) => void\n /** Opens the channel to the other tabs, or `null` where there is none */\n openChannel?: () => SyncChannel | null\n /** Elects the tab that persists, or `undefined` where there is no lock manager */\n locks?: SyncLocks | undefined\n /** Defers a persist to an idle moment; returns what cancels it */\n schedule?: (callback: () => void) => () => void\n}\n\nfunction openBroadcastChannel(): SyncChannel | null {\n if (typeof BroadcastChannel !== 'function') return null\n\n const channel = new BroadcastChannel(CHANNEL_NAME)\n\n return {\n // oxlint-disable-next-line require-post-message-target-origin -- a BroadcastChannel is same-origin by nature, its postMessage takes no target\n post: (message) => channel.postMessage(message),\n listen: (listener) => {\n const onMessage = (event: MessageEvent) => listener(event.data)\n\n channel.addEventListener('message', onMessage)\n\n return () => channel.removeEventListener('message', onMessage)\n },\n close: () => channel.close(),\n }\n}\n\nfunction browserLocks(): SyncLocks | undefined {\n // Web Locks need a secure context, where `navigator.locks` is undefined otherwise\n return typeof navigator !== 'undefined' && 'locks' in navigator ? navigator.locks : undefined\n}\n\nfunction scheduleIdle(callback: () => void): () => void {\n if (typeof requestIdleCallback === 'function') {\n const id = requestIdleCallback(callback, {timeout: PERSIST_TIMEOUT})\n\n return () => cancelIdleCallback(id)\n }\n\n const id = setTimeout(callback, PERSIST_TIMEOUT)\n\n return () => clearTimeout(id)\n}\n\nfunction parseMessage(data: unknown): SyncMessage | null {\n if (!data || typeof data !== 'object') return null\n\n const type: unknown = Reflect.get(data, 'type')\n\n if (type === 'hello') {\n const from: unknown = Reflect.get(data, 'from')\n\n return typeof from === 'string' ? {type, from} : null\n }\n\n if (type === 'state') {\n const state = parseThemerState(Reflect.get(data, 'state'))\n const revision: unknown = Reflect.get(data, 'revision')\n const from: unknown = Reflect.get(data, 'from')\n const to: unknown = Reflect.get(data, 'to')\n\n if (!state || typeof revision !== 'number' || !Number.isFinite(revision)) return null\n if (typeof from !== 'string') return null\n\n return {type, state, revision, from, ...(typeof to === 'string' ? {to} : {})}\n }\n\n return null\n}\n\nfunction tabId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2)\n}\n\n/**\n * Keeps the themes of every tab of the Studio the same, and persists them\n * from one tab only.\n *\n * Every tab runs its own machine, and what the machine persists — the\n * applied theme, the user's themes, what was removed and reordered — goes\n * around to the other tabs on a `BroadcastChannel` as it changes, where it\n * reaches their machines as a `themes.sync` event: the same themes\n * everywhere, with the flow each tab is in, the tool's open and split state\n * (the reducer's, in `plugin.tsx`) and the images of this session staying\n * the tab's own. A tab that starts asks the others for the current state,\n * in case what it read from storage was not written yet.\n *\n * Persisting is the business of one tab at a time: the one holding the Web\n * Lock, which it keeps for as long as it lives. The others queue for it, so\n * the next tab takes over as the holder goes away, and a tab that takes\n * over persists right away, in case the last one left with a write pending.\n * Writes happen when the browser is idle — a run of edits is written once —\n * and as the page hides, so that nothing pending is lost.\n *\n * Returns what stops the sync: it lets go of the lock, which hands the\n * persisting on, and closes the channel.\n *\n * @internal\n */\nexport function syncThemer(\n actorRef: ActorRefFrom<typeof themerMachine>,\n options: ThemerSyncOptions,\n): () => void {\n const {persist, openChannel = openBroadcastChannel, schedule = scheduleIdle} = options\n const channel = openChannel()\n // Without a channel the tabs cannot hear of each other's changes, so each\n // persists its own — a lock would only silence the tabs not holding it.\n // Given as `undefined` means no lock manager, not the browser's: Node has\n // one too these days\n const locks = channel ? ('locks' in options ? options.locks : browserLocks()) : undefined\n const id = tabId()\n const abort = new AbortController()\n let leader = false\n let cancelPersist: (() => void) | undefined\n /** Whether another tab's state is being sent into the machine, which is nothing to pass on */\n let applying = false\n /** The persisted state as the other tabs last heard it from this one, or this one from them */\n let shared = selectStoredState(actorRef.getSnapshot())\n /** The revision `shared` is at, and the tab whose change it was */\n let revision = 0\n let origin = id\n /** Whether this tab still has what it started with, so that an answer to its `hello` is welcome */\n let unchanged = true\n /** The tabs that said hello before this one held the lock, to answer once it does */\n const unanswered = new Set<string>()\n\n const isNewer = (message: {revision: number; from: string}) =>\n message.revision > revision || (message.revision === revision && message.from > origin)\n\n const persistNow = () => {\n cancelPersist?.()\n cancelPersist = undefined\n persist(actorRef.getPersistedSnapshot())\n }\n const persistSoon = () => {\n cancelPersist?.()\n cancelPersist = schedule(persistNow)\n }\n const answer = (to: string) => {\n channel?.post({type: 'state', state: shared, revision, from: origin, to} satisfies SyncMessage)\n }\n const becomeLeader = () => {\n leader = true\n persistNow()\n // Tabs that started alongside this one asked before anyone held the lock\n for (const from of unanswered) answer(from)\n unanswered.clear()\n }\n\n if (locks) {\n locks\n .request(LOCK_NAME, {signal: abort.signal}, () => {\n becomeLeader()\n\n // Held until the sync stops, which hands the lock to the next tab in line\n return new Promise<void>((resolve) => {\n abort.signal.addEventListener('abort', () => resolve(), {once: true})\n })\n })\n .catch(() => {\n // The sync stopped before the lock was granted — nothing to let go of\n })\n } else {\n becomeLeader()\n }\n\n const subscription = actorRef.subscribe((snapshot) => {\n if (leader) persistSoon()\n if (applying) return\n\n const state = selectStoredState(snapshot)\n\n // Only changes to what is persisted concern the other tabs — the flow is this tab's\n if (dequal(state, shared)) return\n\n shared = state\n unchanged = false\n revision += 1\n origin = id\n channel?.post({type: 'state', state, revision, from: id} satisfies SyncMessage)\n })\n\n if (channel) {\n channel.listen((data) => {\n const message = parseMessage(data)\n\n if (!message) return\n\n if (message.type === 'hello') {\n // The tab that persists speaks for all, so that the newcomer hears one\n // answer — or hears it once a tab holds the lock\n if (leader) {\n answer(message.from)\n } else {\n unanswered.add(message.from)\n }\n\n return\n }\n\n if (message.to !== undefined) {\n if (message.to !== id) return\n\n if (!unchanged) {\n // Asked before this tab changed anything, answered after: the change\n // is the later one, and goes around again ahead of the answer\n if (origin === id) {\n revision = Math.max(revision, message.revision) + 1\n channel.post({type: 'state', state: shared, revision, from: id} satisfies SyncMessage)\n\n return\n }\n\n // Answered twice — without a lock manager every tab answers — the\n // answers are ordered like any other state\n if (!isNewer(message)) return\n }\n } else if (!isNewer(message)) {\n return\n }\n\n revision = message.revision\n origin = message.from\n if (dequal(message.state, shared)) return\n\n shared = message.state\n unchanged = false\n applying = true\n try {\n actorRef.send({type: 'themes.sync', state: message.state})\n } finally {\n applying = false\n }\n })\n channel.post({type: 'hello', from: id} satisfies SyncMessage)\n }\n\n const flush = () => {\n if (leader && cancelPersist) persistNow()\n }\n const page = typeof window === 'undefined' ? undefined : window\n\n page?.addEventListener('pagehide', flush)\n\n return () => {\n subscription.unsubscribe()\n page?.removeEventListener('pagehide', flush)\n flush()\n abort.abort()\n channel?.close()\n }\n}\n","import {useEffect} from 'react'\n\nimport type {ThemerProps} from '#types'\n\nimport {writePersistedSnapshot} from './storage'\nimport {syncThemer} from './sync'\n\n/**\n * Persists the machine's snapshot for the next session, and keeps the themes\n * in step with the other tabs of the Studio — from the one tab holding the\n * Web Lock, when the browser is idle; see `sync.ts`. Rendered by the layout,\n * outside the sidebar's `Activity`, so that it runs while the sidebar is\n * closed too: other tabs change the themes whether this one shows them or\n * not.\n */\nexport function PersistActorSnapshot({actorRef}: Pick<ThemerProps, 'actorRef'>) {\n useEffect(() => syncThemer(actorRef, {persist: writePersistedSnapshot}), [actorRef])\n\n return null\n}\n","import {BoundaryElementProvider, Card, Flex} from '@sanity/ui'\nimport type {ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useActorRef} from '@xstate/react'\nimport {Activity, lazy, startTransition, useEffect, useState, ViewTransition} from 'react'\n\nimport type {PluginConfig} from '#types'\nimport type {ThemerProps} from '#types'\n\nimport {addThemerTransitionType} from './addThemerTransitionType'\nimport {themerMachine} from './machine'\nimport {PersistActorSnapshot} from './PersistActorSnapshot'\nimport type {ToolReducerState} from './reducer'\nimport {resolveActiveThemeOptions} from './selectors'\nimport type {readPersistedSnapshot} from './storage'\nimport {ThemerNavbarProvider} from './ThemerNavbar'\n\nimport {layout, viewTransitionClasses} from './ViewTransitions.css'\n\n/**\n * The sidebar — everything in it, from the theme list to the snippet dialog —\n * loads the first time it opens. There is no `Suspense` boundary around it,\n * so it must only ever mount from a transition: the navbar toggle prerenders\n * it in one as the pointer comes near, which keeps the Studio as it was while\n * the code loads, where an urgent render would suspend up to the Studio's\n * own boundary and swap the whole Studio for its loading screen.\n */\nconst ResizableSidebar = lazy(() => import('./ResizableSidebar'))\n\ninterface ThemerLayoutProps extends Omit<ToolReducerState, 'theme'> {\n children: React.ReactNode\n config: PluginConfig\n dispatch: ThemerProps['dispatch']\n /** The machine's snapshot as the last session persisted it, for the machine to pick up where it left off */\n persistedSnapshot: ReturnType<typeof readPersistedSnapshot>\n}\n\n/**\n * Wraps the whole Studio to run the themer machine that the sidebar's flows\n * share — the user's themes, which one is applied and which flow the sidebar\n * is in — and renders the sidebar next to the Studio. The sidebar sits at\n * the `layout` level rather than in `activeToolLayout` because the split\n * preview renders the Studio twice, and the sidebar must not come along.\n *\n * Whether the sidebar is open and whether the Studio shows twice comes in as\n * props from the tool reducer in `ThemerProvider`, and goes on to the sidebar\n * as props along with the machine's actor, which the sidebar selects its\n * themes and flow from (see `ThemerProps`): the navbar toggle and the sidebar\n * dispatch to the reducer inside transitions tagged with a transition type\n * (see `addThemerTransitionType`), and the `ViewTransition` boundaries below\n * pick their classes by that type, so React animates the sidebar sliding in\n * and out, the split copy coming and going and a picked theme cross-fading\n * in (React 19.3), styled in `ViewTransitions.css.ts`. The sidebar and the\n * split copy stay mounted while hidden, in `Activity`, so that opening them\n * again is instant and they can prerender ahead of the transition. The\n * navbar toggle, which the Studio renders somewhere in `children`, reads the\n * same values and the plugin's options from the contexts\n * `ThemerNavbarProvider` provides in each copy of the Studio, and measures\n * the Studio navbar for the sidebar's header — in the Studio proper, not in\n * the split copy, which `ToolShouldDetectNavbarHeightContext` tells apart.\n *\n * The applied theme is the reducer's, provided above this layout in\n * `ThemerProvider`: the machine publishes it synchronously, which React does\n * not animate, so `ThemerThemeCrossfader` hands it to the reducer in a\n * transition of its own. The Studio next to the sidebar always follows the\n * appearance setting (light/dark/system) and the picked theme like any other\n * theme would; the split preview adds a second copy in the opposite scheme\n * on the far side.\n *\n * Whatever else on the page has a `view-transition-name` — an avatar the\n * Studio names so it moves as one piece — comes along in step: every\n * transition the layout starts carries `layoutTransitionType`, which the\n * stylesheet keys on to move every group in the layout's time (see\n * `ViewTransitions.css.ts`).\n *\n * @internal\n */\nexport function ThemerLayout({\n children,\n backgroundColor,\n config,\n dispatch,\n navbarHeight,\n open,\n persistedSnapshot,\n prerender,\n prerenderSplitScreen,\n scheme,\n split,\n}: ThemerLayoutProps & {backgroundColor: string | undefined; scheme: ThemeColorSchemeKey}) {\n const actorRef = useActorRef(themerMachine, {snapshot: persistedSnapshot})\n\n const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\n\n return (\n <>\n <Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>\n\n <ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />\n <RevokeImageUrls actorRef={actorRef} />\n <PersistActorSnapshot actorRef={actorRef} />\n </>\n )\n}\n\n/**\n * One copy of the Studio, in the given color scheme — or, without one, in the\n * scheme the Studio is showing. The two copies of the split preview share the\n * router, the document store and every other provider above the layout —\n * only the scheme differs — so they stay in sync while navigating. The\n * `color-scheme` of a forced scheme keeps native form controls and scrollbars\n * in step with it. Each copy provides the navbar toggle it renders with the\n * contexts it reads (see `ThemerNavbarProvider`).\n */\nfunction StudioPreview({\n borderRight,\n children,\n config,\n dispatch,\n open,\n scheme,\n shouldDetectNavbarHeight,\n split,\n}: {\n borderRight?: boolean\n scheme?: ThemeColorSchemeKey\n} & React.ComponentProps<typeof ThemerNavbarProvider>) {\n const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)\n\n return (\n <Card\n borderRight={borderRight}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n scheme={scheme}\n ref={(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }}\n >\n <BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>\n </Card>\n )\n}\n\n/**\n * Hands the applied theme to the tool reducer as the machine publishes it —\n * in a transition tagged to cross-fade while the machine says a theme is\n * being switched to, so that editing the applied theme's colors applies live\n * while picking another theme fades the Studio over to it\n */\nfunction ThemerThemeCrossfader({actorRef, dispatch}: Pick<ThemerProps, 'actorRef' | 'dispatch'>) {\n useEffect(() => {\n const subscription = actorRef.subscribe((snapshot) => {\n const switching = snapshot.hasTag('switching')\n const theme = resolveActiveThemeOptions(snapshot.context)\n\n startTransition(() => {\n if (switching) {\n addThemerTransitionType('crossfade')\n }\n if (!theme) {\n dispatch({type: 'unset-theme'})\n } else {\n dispatch({type: 'set-theme', theme})\n }\n })\n })\n\n return () => subscription.unsubscribe()\n }, [actorRef, dispatch])\n\n return null\n}\n\nfunction RevokeImageUrls({actorRef}: Pick<ThemerProps, 'actorRef'>) {\n // The machine releases the image of a palette as it is replaced or its\n // theme deleted; whatever it still holds goes with the layout — the actor\n // keeps its last snapshot once stopped\n useEffect(\n () => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n },\n [actorRef],\n )\n\n return null\n}\n","import {ThemeProvider, useMediaIndex} from '@sanity/ui'\nimport {use, useReducer, useState} from 'react'\nimport {browser} from 'react-dom'\nimport {useColorSchemeValue} from 'sanity'\n\nimport {MIN_MEDIA_INDEX_FOR_SIDEBAR, MIN_MEDIA_INDEX_FOR_SPLIT_SCREEN} from '#constants'\nimport type {ThemerProps} from '#types'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {initialToolState, toolReducer} from './reducer'\nimport {resolveActiveThemeOptions} from './selectors'\nimport {readPersistedSnapshot} from './storage'\nimport {ThemerLayout} from './ThemerLayout'\n\n/**\n * Keep this component extremely lightweight, and only include state that are needed by both\n * `studio.components.layout` and `studio.components.navbar`, where it's not possible to prop-drill into `studio.components.navbar`,\n * and that need to survive open/close of the tool\n * @TODO will mount on the future `studio.components.provider` entrypoint to ensure other plugins using `studio.components.layout` is correctly wrapped in the top-level theme regardless of plugin ordering\n */\nexport function ThemerProvider({\n children,\n config,\n}: {\n children: React.ReactNode\n} & Pick<ThemerProps, 'config'>) {\n use(browser('The current theme is stored in localStorage.'))\n\n const [persistedSnapshot] = useState(readPersistedSnapshot)\n const [state, dispatch] = useReducer(toolReducer, initialToolState, (initialState) => {\n if (!persistedSnapshot) return initialState\n const theme = resolveActiveThemeOptions(persistedSnapshot.context)\n return theme ? {...initialState, theme} : initialState\n })\n\n const scheme = useColorSchemeValue()\n const index = useMediaIndex()\n const isMobile = index <= MIN_MEDIA_INDEX_FOR_SIDEBAR\n const isTooSmallForSplitScreen = index <= MIN_MEDIA_INDEX_FOR_SPLIT_SCREEN\n\n // @TODO Themer does not yet support mobile\n const open = state.open && !isMobile\n // Split screen needs some space\n const split = state.split && !isTooSmallForSplitScreen\n\n return (\n <ThemeProvider theme={(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined}>\n <ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>\n </ThemeProvider>\n )\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport type {PluginConfig} from '#types'\n\nimport {ThemerNavbar} from './ThemerNavbar'\nimport {ThemerProvider} from './ThemerProvider'\n\n/**\n * Options for the {@link themerTool} plugin.\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport interface ThemerToolOptions {\n /**\n * What the tool goes by in the Studio navbar — the toggle's label and\n * tooltip. Defaults to `Themer`.\n */\n title?: string\n}\n\n/**\n * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a\n * navbar toggle opens the sidebar next to the Studio, with a list of themes —\n * the configured theme, the presets and your own — each previewed as a tiny\n * Studio in both color schemes. Picking one applies it live to the whole\n * Studio while you browse around; your own themes can be edited with\n * accent/text/background pickers and a contrast slider per scheme, added,\n * duplicated, given the colors of an image (its palette is read on device),\n * removed and restored, and a dialog shows the `buildTheme` snippet that\n * makes the applied theme permanent. Toggle between light and dark mode with\n * the regular appearance menu — the preview follows it — or split the\n * preview to see the whole Studio in light and dark side by side.\n *\n * ```ts\n * import {themerTool} from '@sanity/themer/tool'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * plugins: [themerTool()],\n * // ...rest of the config\n * })\n * ```\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport const themerTool = definePlugin<ThemerToolOptions | void>((options) => {\n const {title = 'Themer'} = options ?? {}\n const ThemerLayoutProvider = defineThemerLayout({title})\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {layout: ThemerLayoutProvider, navbar: ThemerNavbar},\n },\n }\n})\n\n// This wrapper creates a clean closure that allows safely using the `'use memo'` directive\n// so that the dynamically created component can be memoized by react compiler\nfunction defineThemerLayout(config: PluginConfig) {\n return function DefinedThemerLayoutProvider(props: LayoutProps) {\n 'use memo'\n\n return <ThemerProvider config={config}>{props.renderDefault(props)}</ThemerProvider>\n }\n}\n"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"tool.js","names":["() =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:open')\n addThemerTransitionType('open')\n dispatch({type: 'open'})\n })","() =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:close')\n addThemerTransitionType('close')\n dispatch({type: 'close'})\n })","() =>\n startTransition(() => {\n // We want to trigger rerender in a transition, so that clicking the open button\n // can interrupt a prerender, and hovering over the button won't abort an active view transition.\n // But we also don't want the transition to trigger a view transition,\n // so we intentionally do not set a transition type here.\n dispatch({type: 'prerender'})\n // Do a little fun spin\n if (!open) iconRef.current?.spin()\n })","(root: HTMLButtonElement | null) => {\n const navbar = root?.closest(NAVBAR_SELECTOR)\n\n if (!navbar) return undefined\n\n const resizeObserver = new ResizeObserver((entries) => {\n const height = entries[0]?.borderBoxSize[0]?.blockSize\n\n if (height) {\n startTransition(() => dispatch({type: 'set-navbar-height', height}))\n }\n })\n\n resizeObserver.observe(navbar)\n\n return () => resizeObserver.disconnect()\n }","<Text size={1}>{title}</Text>","prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )","<Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>","shouldDetectNavbarHeight ? observeNavbarHeight : undefined","open ? handleClose : handleOpen","open || busy","<ViewTransition default=\"none\" update={viewTransitionClasses.navbarButtonTooltip.update}>\n <Tooltip animate content={<Text size={1}>{title}</Text>} portal>\n <Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>\n </Tooltip>\n </ViewTransition>","{\n location: 'topbar',\n name: 'themer-topbar',\n // The component itself rather than a `() => <ThemerNavbarButton />`\n // wrapper: the Studio renders `render` as a component, and a wrapper\n // made anew on every render would remount the button each time —\n // resetting its animation\n render: ThemerNavbarButton,\n }","props.__internal_actions ?? []","__internal_actions","props.renderDefault({\n ...props,\n __internal_actions: [...(props.__internal_actions ?? []), button],\n })","{\n children,\n config,\n dispatch,\n open,\n shouldDetectNavbarHeight,\n split,\n}: {children: React.ReactNode; shouldDetectNavbarHeight?: boolean} & Pick<\n ThemerProps,\n 'dispatch' | 'config' | 'open' | 'split'\n>","<ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>","shouldDetectNavbarHeight ?? false","<ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>","<ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>","<ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>","<PluginConfigContext value={config}>\n <ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>\n </PluginConfigContext>","_c","unknown","{actorRef}: Pick<ThemerProps, 'actorRef'>","() => syncThemer(actorRef, {persist: writePersistedSnapshot})","[actorRef]","_c","{\n children,\n backgroundColor,\n config,\n dispatch,\n navbarHeight,\n open,\n persistedSnapshot,\n prerender,\n prerenderSplitScreen,\n scheme,\n split,\n}: ThemerLayoutProps & {backgroundColor: string | undefined; scheme: ThemeColorSchemeKey}","{snapshot: persistedSnapshot}","{backgroundColor}","prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )","<ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>","<Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>","split && open ? 'visible' : 'hidden'","<ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>","prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )","<ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>","<Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>","open ? 'visible' : 'hidden'","<Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>","<ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />","<RevokeImageUrls actorRef={actorRef} />","<PersistActorSnapshot actorRef={actorRef} />","<>\n <Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>\n\n <ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />\n <RevokeImageUrls actorRef={actorRef} />\n <PersistActorSnapshot actorRef={actorRef} />\n </>","{\n borderRight,\n children,\n config,\n dispatch,\n open,\n scheme,\n shouldDetectNavbarHeight,\n split,\n}: {\n borderRight?: boolean\n scheme?: ThemeColorSchemeKey\n} & React.ComponentProps<typeof ThemerNavbarProvider>","(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }","boundaryElement","() => {}","<ViewTransition update=\"none\">{children}</ViewTransition>","<ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>","<BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>","<Card\n borderRight={borderRight}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n scheme={scheme}\n ref={(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }}\n >\n <BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>\n </Card>","{actorRef, dispatch}: Pick<ThemerProps, 'actorRef' | 'dispatch'>","() => {\n const subscription = actorRef.subscribe((snapshot) => {\n const switching = snapshot.hasTag('switching')\n const theme = resolveActiveThemeOptions(snapshot.context)\n\n startTransition(() => {\n if (switching) {\n addThemerTransitionType('crossfade')\n }\n if (!theme) {\n dispatch({type: 'unset-theme'})\n } else {\n dispatch({type: 'set-theme', theme})\n }\n })\n })\n\n return () => subscription.unsubscribe()\n }","[actorRef, dispatch]","{actorRef}: Pick<ThemerProps, 'actorRef'>","() => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n }","[actorRef]","_c","_Fragment","{\n children,\n config,\n}: {\n children: React.ReactNode\n} & Pick<ThemerProps, 'config'>","(initialState) => {\n if (!persistedSnapshot) return initialState\n const theme = resolveActiveThemeOptions(persistedSnapshot.context)\n return theme ? {...initialState, theme} : initialState\n }","(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined","theme","<ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>","navbarHeight","prerender","prerenderSplitScreen","state.theme?.[scheme]?.background","<ThemeProvider theme={(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined}>\n <ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>\n </ThemeProvider>","_c","props.renderDefault(props)","<ThemerProvider config={config}>{props.renderDefault(props)}</ThemerProvider>","_c"],"sources":["../src/tool/context.ts","../src/tool/ThemerNavbar.tsx","../src/tool/reducer.ts","../src/tool/selectors.ts","../src/tool/schemas.ts","../src/tool/storage.ts","../src/tool/machine.ts","../src/tool/sync.ts","../src/tool/PersistActorSnapshot.tsx","../src/tool/ThemerLayout.tsx","../src/tool/ThemerProvider.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {createContext} from 'react'\n\nimport type {PluginConfig} from '#types'\n\nimport type {ToolReducerAction} from './reducer'\n\n/*\n * The tool reducer's state that the navbar toggle shows and changes. The\n * Studio renders the navbar itself, out of the layout's reach, so this is\n * where the reducer's values travel by context — the layout and the sidebar\n * get them as props. One context per value, on purpose: a consumer only\n * re-renders for the value it reads, where one object would re-render every\n * consumer for any change.\n */\n\n/** @internal */\nexport const ToolIsOpenContext = createContext<boolean>(false)\n/**\n * The global top-level useReducer dispatch() for state that persists through open/close of the tool\n * @internal */\nexport const ToolDispatchContext = createContext<React.Dispatch<ToolReducerAction>>(() => {\n throw new Error('ToolDispatchContext not initialized')\n})\n/** @internal */\nexport const ToolSplitIsOpenContext = createContext<boolean>(false)\n\n/** @internal */\nexport const ToolShouldDetectNavbarHeightContext = createContext<boolean>(false)\n\n/** @internal */\nexport const PluginConfigContext = createContext<Required<PluginConfig> | null>(null)\n\n/** @internal */\nexport type ThemerView =\n | {name: 'list'}\n | {\n name: 'edit'\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n }\n | {name: 'removed'}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {Box, Button, Text, usePrefersReducedMotion} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {\n lazy,\n startTransition,\n Suspense,\n use,\n useRef,\n useTransition,\n ViewTransition,\n useMemo,\n} from 'react'\nimport {type NavbarProps} from 'sanity'\nimport type {NavbarAction} from 'sanity/_dangerously_use_private_internals_that_do_not_follow_semver'\n\nimport {NAVBAR_SELECTOR} from '#constants'\nimport {\n PluginConfigContext,\n ToolDispatchContext,\n ToolIsOpenContext,\n ToolShouldDetectNavbarHeightContext,\n ToolSplitIsOpenContext,\n} from '#context'\nimport type {ThemerProps} from '#types'\n\nimport {addThemerTransitionType} from './addThemerTransitionType'\n\nimport {viewTransitionClasses} from './ViewTransitions.css'\n\nconst AnimatedColorWheelIcon = lazy(() => import('./AnimatedColorWheelIcon'))\n\nfunction ThemerNavbarButton() {\n const config = use(PluginConfigContext)\n const dispatch = use(ToolDispatchContext)\n const open = use(ToolIsOpenContext)\n const shouldDetectNavbarHeight = use(ToolShouldDetectNavbarHeightContext)\n const split = use(ToolSplitIsOpenContext)\n\n if (!config) {\n throw new TypeError('PluginConfigContext.Provider is missing')\n }\n\n const [busy, startBusyTransition] = useTransition()\n const iconRef = useRef<{spin: () => void}>(null)\n const prefersReducedMotion = usePrefersReducedMotion()\n\n const handleOpen = () =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:open')\n addThemerTransitionType('open')\n dispatch({type: 'open'})\n })\n const handleClose = () =>\n startBusyTransition(() => {\n if (split) addThemerTransitionType('split-screen:close')\n addThemerTransitionType('close')\n dispatch({type: 'close'})\n })\n const handlePrerender = () =>\n startTransition(() => {\n // We want to trigger rerender in a transition, so that clicking the open button\n // can interrupt a prerender, and hovering over the button won't abort an active view transition.\n // But we also don't want the transition to trigger a view transition,\n // so we intentionally do not set a transition type here.\n dispatch({type: 'prerender'})\n // Do a little fun spin\n if (!open) iconRef.current?.spin()\n })\n\n // The sidebar's header takes the height of the Studio navbar, so the two\n // line up — whatever height the Studio, its breakpoint or a custom navbar\n // gives it. The button sits inside the navbar, so it is the one to measure\n // it, as a ref callback: observed from mount to unmount, in a transition,\n // since nothing urgent hangs on the number\n const observeNavbarHeight = (root: HTMLButtonElement | null) => {\n const navbar = root?.closest(NAVBAR_SELECTOR)\n\n if (!navbar) return undefined\n\n const resizeObserver = new ResizeObserver((entries) => {\n const height = entries[0]?.borderBoxSize[0]?.blockSize\n\n if (height) {\n startTransition(() => dispatch({type: 'set-navbar-height', height}))\n }\n })\n\n resizeObserver.observe(navbar)\n\n return () => resizeObserver.disconnect()\n }\n\n const {title} = config\n\n // The `Box` is here because a `ViewTransition` cannot be the direct child\n // of the `Tooltip`: it needs a child that renders a DOM node\n return (\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButtonTooltip.update}>\n <Tooltip animate content={<Text size={1}>{title}</Text>} portal>\n <Box>\n <ViewTransition default=\"none\" update={viewTransitionClasses.navbarButton.update}>\n <Button\n ref={shouldDetectNavbarHeight ? observeNavbarHeight : undefined}\n aria-busy={busy}\n aria-label={title}\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n icon={\n // The animated wheel needs `motion/react`, which is heavy, so it\n // loads lazily — behind the plain icon it reimplements, which is\n // all there is to show with `prefers-reduced-motion: reduce`\n prefersReducedMotion ? (\n <ColorWheelIcon />\n ) : (\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\n )\n }\n mode=\"bleed\"\n onClick={open ? handleClose : handleOpen}\n onFocus={handlePrerender}\n onMouseEnter={handlePrerender}\n // The Studio's own navbar buttons go through a wrapper that pins them\n // to this padding, where `@sanity/ui` defaults to a roomier 3\n padding={2}\n selected={open || busy}\n />\n </ViewTransition>\n </Box>\n </Tooltip>\n </ViewTransition>\n )\n}\n\nexport function ThemerNavbar(props: NavbarProps) {\n const button = useMemo(\n () =>\n ({\n location: 'topbar',\n name: 'themer-topbar',\n // The component itself rather than a `() => <ThemerNavbarButton />`\n // wrapper: the Studio renders `render` as a component, and a wrapper\n // made anew on every render would remount the button each time —\n // resetting its animation\n render: ThemerNavbarButton,\n }) satisfies NavbarAction,\n [],\n )\n return props.renderDefault({\n ...props,\n __internal_actions: [...(props.__internal_actions ?? []), button],\n })\n}\n\n// It can be easy to forget which contexts that ThemerNavbar requires and expects to be defined,\n// especially since TypeScript is unable to detect missing contexts statically,\n// parent views uses this component to ensure it doesn't miss anything and the props interface gives us type check validation.\nexport function ThemerNavbarProvider({\n children,\n config,\n dispatch,\n open,\n shouldDetectNavbarHeight,\n split,\n}: {children: React.ReactNode; shouldDetectNavbarHeight?: boolean} & Pick<\n ThemerProps,\n 'dispatch' | 'config' | 'open' | 'split'\n>) {\n return (\n <PluginConfigContext value={config}>\n <ToolDispatchContext value={dispatch}>\n <ToolIsOpenContext value={open}>\n <ToolSplitIsOpenContext value={split}>\n <ToolShouldDetectNavbarHeightContext value={shouldDetectNavbarHeight ?? false}>\n {children}\n </ToolShouldDetectNavbarHeightContext>\n </ToolSplitIsOpenContext>\n </ToolIsOpenContext>\n </ToolDispatchContext>\n </PluginConfigContext>\n )\n}\n","import {dequal} from 'dequal/lite'\n\nimport type {BuildThemeOptions} from '../theme/options'\n\n/**\n * What the layout shows, owned by the `useReducer` in `ThemerProvider` and\n * never persisted — it is each tab's own — handed down to the layout and the\n * sidebar as props and to the navbar toggle through the `Tool*Context`s:\n * whether the sidebar is open and whether the Studio shows twice, whether\n * either has been prerendered yet — they mount ahead of their first showing,\n * so that the code loads and the tree is warm before the transition — the\n * options of the applied theme, as the machine publishes them, and the\n * height of the Studio navbar, as the navbar toggle measures it, for the\n * sidebar's header to match.\n */\nexport type ToolReducerState = {\n navbarHeight: number | null\n open: boolean\n prerender: boolean\n prerenderSplitScreen: boolean\n split: boolean\n theme: BuildThemeOptions | null\n}\n\nexport type ToolReducerAction =\n | {type: 'set-navbar-height'; height: number}\n | {type: 'prerender'}\n | {type: 'open'}\n | {type: 'close'}\n | {type: 'split-screen:prerender'}\n | {type: 'split-screen:open'}\n | {type: 'split-screen:close'}\n | {type: 'set-theme'; theme: BuildThemeOptions}\n | {type: 'unset-theme'}\n\nexport function toolReducer(state: ToolReducerState, action: ToolReducerAction): ToolReducerState {\n switch (action.type) {\n case 'set-navbar-height':\n return state.navbarHeight === action.height ? state : {...state, navbarHeight: action.height}\n case 'prerender':\n return state.prerender ? state : {...state, prerender: true}\n case 'open':\n return state.open ? state : {...state, open: true, prerender: true}\n case 'close':\n return state.open ? {...state, open: false} : state\n case 'split-screen:prerender':\n return state.prerenderSplitScreen ? state : {...state, prerenderSplitScreen: true}\n case 'split-screen:open':\n return state.split ? state : {...state, prerenderSplitScreen: true, split: true}\n case 'split-screen:close':\n return state.split ? {...state, split: false} : state\n case 'unset-theme':\n return state.theme === null ? state : {...state, theme: null}\n case 'set-theme':\n return dequal(state.theme, action.theme) ? state : {...state, theme: action.theme}\n default: {\n // Every action is handled above, so only a dispatch the types do not\n // know about gets here — a bug worth hearing of, not a silent no-op\n const unknown: never = action\n throw new Error(`Unknown themer tool action: ${JSON.stringify(unknown)}`)\n }\n }\n}\n\n/**\n * Where a tab starts: closed, nothing prerendered, no theme applied —\n * `ThemerProvider` seeds only the applied theme, from the machine's restored\n * snapshot\n */\nexport const initialToolState = {\n navbarHeight: null,\n open: false,\n prerender: false,\n prerenderSplitScreen: false,\n split: false,\n theme: null,\n} as const satisfies ToolReducerState\n","import type {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport type {ThemerState} from './themes'\n\n/**\n * The options of the applied theme — `null` for the configured theme, which\n * applies nothing on top of the Studio's own, and for an applied theme that\n * no longer resolves. Cheaper than resolving the whole list when only the\n * applied theme matters.\n *\n * @internal\n */\nexport function resolveActiveThemeOptions(state: ThemerState): BuildThemeOptions | null {\n if (state.active === null || state.removed.includes(state.active)) return null\n\n for (const theme of state.custom) {\n if (theme.slug === state.active) return theme.options\n }\n for (const preset of presets) {\n if (preset.slug === state.active) return preset.options\n }\n\n return null\n}\n","import * as v from 'valibot'\n\nimport {isColor} from '../lib/mix'\nimport {\n type BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n type SchemeThemeOptions,\n} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport type {ImagePalette} from './imagePalette'\nimport type {ThemerMachineContext} from './machine'\nimport {CONFIG_SLUG, type CustomTheme, displayTitle, type ThemerState} from './themes'\n\n/*\n * What the tool keeps in `localStorage` is parsed with these schemas as it is\n * read back, so that a session starts from what it can use of what an\n * earlier one left — whatever version of the tool wrote it, and whatever\n * happened to it since. The schemas are lenient where the old sanitizers\n * were: a color that is not one is dropped rather than failing the theme it\n * is part of, a theme that cannot be used is dropped rather than failing the\n * list, and the shapes of earlier versions are converted into today's.\n */\n\n/** Drops the keys whose value is `undefined`, which a dropped option leaves behind */\nfunction compact<T extends object>(value: T): T {\n // oxlint-disable-next-line no-unsafe-type-assertion -- the same keys, minus the undefined ones\n return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T\n}\n\n/** A `#rgb` or `#rrggbb` color, lowercased */\nconst color = v.pipe(v.string(), v.check(isColor), v.toLowerCase())\n\n/** A color that is dropped rather than failing what it is part of */\nconst optionalColor = v.fallback(v.optional(color), undefined)\n\n/** A contrast, clamped into range — dropped when it is not a number */\nconst optionalContrast = v.fallback(\n v.optional(\n v.pipe(v.number(), v.finite(), v.toMinValue(MINIMUM_CONTRAST), v.toMaxValue(MAXIMUM_CONTRAST)),\n ),\n undefined,\n)\n\nconst schemeOptionsSchema = v.pipe(\n v.object({\n accent: optionalColor,\n text: optionalColor,\n background: optionalColor,\n contrast: optionalContrast,\n }),\n v.transform((options): SchemeThemeOptions => compact(options)),\n)\n\n/** Options grouped by scheme, as they are today — a scheme without usable options is left out */\nconst groupedOptionsSchema = v.pipe(\n v.looseObject({\n light: v.fallback(v.optional(schemeOptionsSchema), undefined),\n dark: v.fallback(v.optional(schemeOptionsSchema), undefined),\n }),\n // The flat shape of earlier versions always had an accent at the top\n v.check((input) => !('accent' in input), 'flat options'),\n v.transform(({light, dark}): BuildThemeOptions => {\n const options: BuildThemeOptions = {}\n\n if (light && Object.keys(light).length > 0) options.light = light\n if (dark && Object.keys(dark).length > 0) options.dark = dark\n\n return options\n }),\n)\n\n/**\n * Options from before they were grouped by scheme: one accent, text color and\n * contrast for both schemes, and a background per scheme. Both schemes get\n * the shared colors, so the theme keeps looking the same.\n */\nconst flatOptionsSchema = v.pipe(\n v.object({\n accent: color,\n text: optionalColor,\n contrast: optionalContrast,\n background: v.fallback(\n v.optional(v.object({light: optionalColor, dark: optionalColor})),\n undefined,\n ),\n }),\n v.transform(({accent, text, contrast, background}): BuildThemeOptions => {\n const shared: SchemeThemeOptions = compact({accent, text, contrast})\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const schemeBackground = background?.[scheme]\n\n options[scheme] = schemeBackground ? {...shared, background: schemeBackground} : {...shared}\n }\n\n return options\n }),\n)\n\n/** The `buildTheme` options of a theme, in today's shape or an earlier version's @internal */\nexport const themeOptionsSchema = v.union([flatOptionsSchema, groupedOptionsSchema])\n\n/** A swatch of an image palette — `null` when it is missing or not a color */\nconst swatch = v.fallback(v.optional(v.nullable(color), null), null)\n\n/** The palette a theme took its colors from — `null` without a single swatch */\nconst paletteSchema = v.pipe(\n v.object({\n vibrant: swatch,\n lightVibrant: swatch,\n darkVibrant: swatch,\n muted: swatch,\n lightMuted: swatch,\n darkMuted: swatch,\n dominant: swatch,\n }),\n v.transform((palette): ImagePalette | null =>\n Object.values(palette).some((item) => item !== null) ? palette : null,\n ),\n)\n\nfunction isReservedSlug(slug: string): boolean {\n return slug === CONFIG_SLUG || presets.some((preset) => preset.slug === slug)\n}\n\nconst customThemeSchema = v.pipe(\n v.object({\n slug: v.pipe(\n v.string(),\n v.nonEmpty(),\n v.check((slug) => !isReservedSlug(slug), 'reserved slug'),\n ),\n title: v.fallback(v.optional(v.string()), undefined),\n options: themeOptionsSchema,\n palette: v.fallback(v.optional(paletteSchema), undefined),\n }),\n v.transform(({slug, title, options, palette}): CustomTheme => ({\n slug,\n title: displayTitle(title ?? ''),\n options,\n ...(palette ? {palette} : {}),\n })),\n)\n\n/** The items of a list that can be used, each parsed on its own so that one bad item does not fail the rest */\nconst lenientList = v.fallback(v.optional(v.array(v.unknown()), []), [])\n\n/**\n * The persisted themer state — the applied theme, the user's themes and\n * what was removed and reordered — as it is read back. Custom themes that\n * cannot be used, repeated slugs, and removed or ordered slugs that name no\n * theme are dropped.\n */\nconst themerStateSchema = v.pipe(\n v.object({\n active: v.fallback(v.nullable(v.string()), null),\n custom: lenientList,\n removed: lenientList,\n order: lenientList,\n }),\n v.transform((state): ThemerState => {\n const custom: CustomTheme[] = []\n\n for (const item of state.custom) {\n const result = v.safeParse(customThemeSchema, item)\n\n if (result.success && !custom.some((theme) => theme.slug === result.output.slug)) {\n custom.push(result.output)\n }\n }\n\n const isCustom = (slug: string) => custom.some((theme) => theme.slug === slug)\n const isPreset = (slug: string) => presets.some((preset) => preset.slug === slug)\n const removed: string[] = []\n const order: string[] = []\n\n for (const slug of state.removed) {\n if (typeof slug !== 'string' || slug === CONFIG_SLUG || removed.includes(slug)) continue\n if (isPreset(slug) || isCustom(slug)) removed.push(slug)\n }\n for (const slug of state.order) {\n if (typeof slug !== 'string' || order.includes(slug)) continue\n if (slug === CONFIG_SLUG || isPreset(slug) || isCustom(slug)) order.push(slug)\n }\n\n return {active: state.active, custom, removed, order}\n }),\n)\n\n/** The persisted themer state, or `null` for anything else @internal */\nexport function parseThemerState(input: unknown): ThemerState | null {\n const result = v.safeParse(themerStateSchema, input)\n\n return result.success ? result.output : null\n}\n\n/** The flows a session restores to */\nconst flowSchema = v.picklist(['list', 'edit', 'removed'])\n\n/**\n * The machine's state value, as persisted — only what the machine has states\n * for restores. The list's dialogs are states of the list, so a snapshot\n * written while one was open names it; the dialog does not reopen\n */\nconst snapshotValueSchema = v.object({\n flow: v.union([flowSchema, v.object({list: v.picklist(['idle', 'pasting', 'snippet'])})]),\n theme: v.picklist(['applied', 'switching']),\n})\n\n/**\n * The machine's persisted snapshot, as `getPersistedSnapshot()` writes it and\n * `createActor` restores it — `status`, `value`, `context`, `historyValue`\n * and `children`, with `output` and `error` left out as `undefined`.\n *\n * @internal\n */\nexport interface PersistedThemerSnapshot {\n status: 'active'\n output: undefined\n error: undefined\n value: {flow: v.InferOutput<typeof flowSchema>; theme: 'applied' | 'switching'}\n context: ThemerMachineContext\n historyValue: Record<string, never>\n children: Record<string, never>\n}\n\n/**\n * A snapshot for the machine to start a session from: the given themes, with\n * nothing being switched to, in the list unless a flow and its subject are\n * given. It is how the themes alone — all that earlier versions persisted —\n * are handed to a machine that only restores snapshots, and what a restored\n * snapshot is made fit for this session as.\n *\n * @internal\n */\nexport function snapshotFromState(\n state: ThemerState,\n editing: ThemerMachineContext['editing'] = null,\n flow: PersistedThemerSnapshot['value']['flow'] = 'list',\n): PersistedThemerSnapshot {\n return {\n status: 'active',\n output: undefined,\n error: undefined,\n value: {flow, theme: 'applied'},\n context: {...state, editing, images: {}},\n historyValue: {},\n children: {},\n }\n}\n\n/**\n * The machine's snapshot as the last session persisted it, made fit for this\n * one: the themes are parsed like any persisted state, the flow keeps its\n * subject or falls back to the list, a dialog that was open is closed, a\n * switch that was under way is over, and the image URLs of the last session\n * are gone with it.\n *\n * @internal\n */\nexport const persistedSnapshotSchema = v.pipe(\n v.object({\n status: v.literal('active'),\n value: snapshotValueSchema,\n context: v.pipe(\n v.looseObject({\n editing: v.fallback(\n v.nullable(v.object({slug: v.string(), focusTitle: v.boolean()})),\n null,\n ),\n }),\n v.transform(({editing, ...state}) => ({editing, state: parseThemerState(state)})),\n v.check(({state}) => state !== null, 'no themes'),\n ),\n }),\n v.transform(({value, context}): PersistedThemerSnapshot => {\n // Checked above, but the type does not know\n const state = context.state ?? {active: null, custom: [], removed: [], order: []}\n const editing =\n context.editing &&\n state.custom.some((theme) => theme.slug === context.editing?.slug) &&\n !state.removed.includes(context.editing.slug)\n ? context.editing\n : null\n const flow =\n typeof value.flow !== 'string' ||\n (value.flow === 'edit' && editing === null) ||\n (value.flow === 'removed' && state.removed.length === 0)\n ? 'list'\n : value.flow\n\n return snapshotFromState(state, editing, flow)\n }),\n)\n","import * as v from 'valibot'\nimport type {Snapshot} from 'xstate'\n\nimport {\n parseThemerState,\n type PersistedThemerSnapshot,\n persistedSnapshotSchema,\n snapshotFromState,\n themeOptionsSchema,\n} from './schemas'\nimport type {CustomTheme} from './themes'\n\n/** Where the machine's snapshot is kept between sessions */\nconst SNAPSHOT_STORAGE_KEY = 'sanityStudio:themer:snapshot'\n\n/**\n * Where earlier versions kept the persisted state — the applied theme, the\n * user's themes and what was removed and reordered — before the machine's\n * whole snapshot was persisted. Read until the first snapshot is written.\n */\nconst STATE_STORAGE_KEY = 'sanityStudio:themer:state'\n\n/**\n * Where the earliest versions of the tool kept their single draft theme — it\n * is migrated into a custom theme, so a draft survives the upgrade.\n */\nconst LEGACY_STORAGE_KEY = 'sanityStudio:themer:options'\n\n/**\n * What the draft of the earliest versions is imported as — the same theme\n * whichever tab reads it first, so that tabs upgrading side by side agree\n */\nconst LEGACY_DRAFT: Omit<CustomTheme, 'options'> = {slug: 'custom-draft', title: 'Draft theme'}\n\nfunction readJson(key: string): unknown {\n const raw = localStorage.getItem(key)\n\n return raw ? JSON.parse(raw) : undefined\n}\n\n/**\n * Restores the machine's snapshot from what the last session persisted, for\n * `createActor` to start from, so the user's themes survive Studio reloads:\n * the snapshot itself where there is one that fits, and one made from the\n * themes alone where the snapshot no longer fits the machine or where\n * earlier versions of the tool stored the themes without a snapshot.\n * `undefined` when nothing usable is stored — or storage cannot be read at\n * all — leaves the machine to start from no themes.\n *\n * The parsing is the schemas' (see `schemas.ts`): nothing here or in the\n * machine looks at what is stored beyond handing it over.\n *\n * @internal\n */\nexport function readPersistedSnapshot(): PersistedThemerSnapshot | undefined {\n try {\n if (typeof localStorage === 'undefined') return undefined\n\n const persisted = readJson(SNAPSHOT_STORAGE_KEY)\n\n if (persisted !== undefined) {\n const snapshot = v.safeParse(persistedSnapshotSchema, persisted)\n\n if (snapshot.success) return snapshot.output\n\n // A snapshot from a version of the machine with other states still\n // holds the themes, which is what matters\n const state = parseThemerState(\n persisted && typeof persisted === 'object' ? Reflect.get(persisted, 'context') : undefined,\n )\n\n if (state) return snapshotFromState(state)\n }\n\n const state = parseThemerState(readJson(STATE_STORAGE_KEY))\n\n if (state) return snapshotFromState(state)\n\n return readLegacyDraft()\n } catch {\n return undefined\n }\n}\n\nfunction readLegacyDraft(): PersistedThemerSnapshot | undefined {\n const options = v.safeParse(themeOptionsSchema, readJson(LEGACY_STORAGE_KEY))\n\n if (!options.success) return undefined\n\n const theme: CustomTheme = {...LEGACY_DRAFT, options: options.output}\n\n return snapshotFromState({active: theme.slug, custom: [theme], removed: [], order: []})\n}\n\n/**\n * Persists the machine's snapshot for the next session, and lets go of what\n * earlier versions stored once it is written — which completes their\n * migration.\n *\n * @internal\n */\nexport function writePersistedSnapshot(snapshot: Snapshot<unknown>): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n localStorage.setItem(SNAPSHOT_STORAGE_KEY, JSON.stringify(snapshot))\n localStorage.removeItem(STATE_STORAGE_KEY)\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n } catch {\n // Storage can be unavailable (e.g. private browsing) — themes just won't persist\n }\n}\n","import {assign, not, setup, type SnapshotFrom} from 'xstate'\n\nimport type {BuildThemeOptions} from '../theme/options'\nimport type {ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n CONFIG_TITLE,\n createCustomTheme,\n duplicateTitle,\n initialThemerState,\n resolveThemes,\n type ThemerState,\n UNTITLED_THEME,\n} from './themes'\n\n/**\n * How long the machine stays `switching` after an event that applies another\n * theme, when no edit to the applied theme ends it sooner. The layout\n * cross-fades the Studio to the theme in the commit that follows the switch,\n * so this only bounds how long the tag lingers once that is done. Generous,\n * so that it never runs out before a slow deferred commit starts the fade.\n *\n * @internal\n */\nexport const MOTION_DURATION = 2000\n\n/** @internal */\nexport interface ThemerMachineContext extends ThemerState {\n /** The theme open in the editor, while the `flow` is `edit` */\n editing: {\n slug: string\n /** Whether the title input should take focus, for themes that were just created */\n focusTitle: boolean\n } | null\n /**\n * Object URLs of the images that themes took their palette from, by theme\n * slug — kept for the session only, so the image can be shown right after\n * it was picked\n */\n images: Record<string, string>\n}\n\n/** @internal */\nexport type ThemerEvent =\n /** Applies a theme to the whole Studio */\n | {type: 'theme.pick'; slug: string}\n /**\n * Adds a theme and opens it in the editor — based on the applied theme, or\n * on the given title, options and image palette (e.g. from an image)\n */\n | {\n type: 'theme.add'\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Adds a copy of a theme and opens it in the editor */\n | {type: 'theme.duplicate'; slug: string}\n /** Adds a theme someone shared as a code, and applies it — staying in the list */\n | {type: 'theme.import'; title: string; options: BuildThemeOptions}\n /** Opens one of the user's own themes in the editor */\n | {type: 'theme.edit'; slug: string}\n | {\n type: 'theme.update'\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n }\n /** Takes a theme out of the list — it can be restored until it is deleted */\n | {type: 'theme.remove'; slug: string}\n /** Puts a removed theme back in the list */\n | {type: 'theme.restore'; slug: string}\n /** Deletes one of the user's own themes for good */\n | {type: 'theme.delete'; slug: string}\n /** Rearranges the list: the slugs of the listed themes, in their new order */\n | {type: 'theme.reorder'; order: string[]}\n /** Back to picking a theme */\n | {type: 'flow.list'}\n /** On to restoring removed themes */\n | {type: 'flow.removed'}\n /** Opens the dialog for adding a shared theme code by hand, from the list */\n | {type: 'dialog.paste'}\n /** Opens the dialog with the applied theme's `buildTheme` snippet, from the list */\n | {type: 'dialog.snippet'}\n /** Closes whichever dialog is open */\n | {type: 'dialog.close'}\n /**\n * Takes over the persisted state as another tab of the same Studio changed\n * it, so that every tab shows the same themes (see `sync.ts`)\n */\n | {type: 'themes.sync'; state: ThemerState}\n\nfunction themesOf(context: ThemerMachineContext) {\n return resolveThemes(context)\n}\n\nfunction revokeObjectUrl(url: string) {\n if (typeof URL !== 'undefined' && typeof URL.revokeObjectURL === 'function') {\n URL.revokeObjectURL(url)\n }\n}\n\n/**\n * The themes of the themer tool and the sidebar's way through them: which\n * `flow` the sidebar is in — picking a theme from the `list`, with its\n * dialogs for pasting a shared code and for the applied theme's snippet,\n * `edit`ing one of the user's own themes, or restoring `removed` ones — and,\n * in a parallel `theme` region, whether another theme was just applied. The\n * context carries the persisted state (the applied theme, the user's themes\n * and what was removed) alongside what the flows need. Whether the sidebar is\n * open and whether the Studio shows twice is not the machine's: the tool\n * reducer in `ThemerProvider` owns that, so that the navbar toggle and the\n * sidebar can change it in transitions of their own. Nor is persisting: the\n * machine never touches storage and takes no input — it starts from no\n * themes, and a session with something to pick up from restores its\n * persisted snapshot instead (see `storage.ts` and `sync.ts`), while the\n * persisted state of other tabs reaches it as a `themes.sync` event like any\n * other.\n *\n * The `theme` region is `switching` (its tag) right after an event that\n * applies another theme — picking one, adding, duplicating, importing or\n * editing one, removing or deleting the applied one — which is when the\n * layout cross-fades the Studio to it. Edits to the applied theme's colors are\n * not switches, they follow the pointer: the first one ends the switch, and so\n * does `MOTION_DURATION` when none follows.\n *\n * Theme operations are handled in every flow, and the flows leave on their\n * own when they lose their subject: the editor when its theme is removed or\n * deleted, and the removed view when its last theme is restored or deleted.\n *\n * @internal\n */\nexport const themerMachine = setup({\n // XState reads `types` for their types only — the values are never used\n types: {\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n context: {} as ThemerMachineContext,\n // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n events: {} as ThemerEvent,\n },\n guards: {\n isCustomTheme: ({context}, params: {slug: string}) =>\n themesOf(context).themes.some(\n (theme) => theme.slug === params.slug && theme.source === 'custom',\n ),\n isEditingListedTheme: ({context}) =>\n context.editing !== null &&\n themesOf(context).themes.some(\n (theme) => theme.slug === context.editing?.slug && theme.source === 'custom',\n ),\n hasRemovedThemes: ({context}) => themesOf(context).removed.length > 0,\n // The configured theme has no code to show — it is the Studio's own\n hasAppliedTheme: ({context}) => themesOf(context).active !== undefined,\n // Only another theme than the applied one changes anything to cross-fade to\n isAnotherTheme: ({context}, params: {slug: string}) =>\n params.slug !== (context.active ?? CONFIG_SLUG),\n // Removing or deleting the applied theme is what changes the applied theme\n isAppliedTheme: ({context}, params: {slug: string}) => params.slug === context.active,\n // Another tab applied another theme, which is as much of a switch as picking it here\n appliesAnotherTheme: ({context}, params: {state: ThemerState}) =>\n params.state.active !== context.active,\n // A theme added from given options (an image's) looks different from the applied one\n hasOptions: (_, params: {options: BuildThemeOptions | undefined}) =>\n params.options !== undefined,\n },\n actions: {\n // Another tab's persisted state replaces this one's: themes, order and\n // all. The images of this session stay, they are this tab's\n sync: assign((_, params: {state: ThemerState}) => ({\n active: params.state.active,\n custom: params.state.custom,\n removed: params.state.removed,\n order: params.state.order,\n })),\n // The image of a replaced palette or a deleted theme is released from memory\n revokeImage: (_, params: {url: string | undefined}) => {\n if (params.url) revokeObjectUrl(params.url)\n },\n pick: assign((_, params: {slug: string}) => ({\n active: params.slug === CONFIG_SLUG ? null : params.slug,\n })),\n add: assign(\n (\n {context},\n params: {\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => {\n // Based on the applied theme — the stock one when the configured\n // theme applies, which is not in the list\n const theme = createCustomTheme(\n params.title ?? UNTITLED_THEME,\n params.options ?? themesOf(context).active?.options ?? {},\n params.palette,\n )\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n // A theme named after its image needs no renaming right away\n editing: {slug: theme.slug, focusTitle: params.title === undefined},\n images: params.imageUrl\n ? {...context.images, [theme.slug]: params.imageUrl}\n : context.images,\n }\n },\n ),\n import: assign(({context}, params: {title: string; options: BuildThemeOptions}) => {\n const theme = createCustomTheme(params.title, params.options)\n\n return {active: theme.slug, custom: [...context.custom, theme]}\n }),\n duplicate: assign(({context}, params: {slug: string}) => {\n const {themes, removed} = themesOf(context)\n // The configured theme is not in the list: a copy of it starts from the\n // stock options, as the Studio shows it\n const source =\n params.slug === CONFIG_SLUG\n ? {title: CONFIG_TITLE, options: {}, palette: undefined}\n : [...themes, ...removed].find((theme) => theme.slug === params.slug)\n\n if (!source) return {}\n\n const theme = createCustomTheme(duplicateTitle(source.title), source.options, source.palette)\n\n return {\n active: theme.slug,\n custom: [...context.custom, theme],\n editing: {slug: theme.slug, focusTitle: true},\n }\n }),\n startEditing: assign((_, params: {slug: string}) => ({\n active: params.slug,\n editing: {slug: params.slug, focusTitle: false},\n })),\n stopEditing: assign({editing: null}),\n update: assign(\n (\n {context},\n params: {\n slug: string\n title?: string\n options?: BuildThemeOptions\n palette?: ImagePalette\n imageUrl?: string\n },\n ) => ({\n custom: context.custom.map((theme) => {\n if (theme.slug !== params.slug) return theme\n\n // Keep the options identity when only the title changes, so the\n // applied theme is not rebuilt on every keystroke\n return {\n ...theme,\n title: params.title ?? theme.title,\n options: params.options ?? theme.options,\n ...(params.palette ? {palette: params.palette} : {}),\n }\n }),\n images:\n params.imageUrl && context.custom.some((theme) => theme.slug === params.slug)\n ? {...context.images, [params.slug]: params.imageUrl}\n : context.images,\n }),\n ),\n remove: assign(({context}, params: {slug: string}) => {\n if (params.slug === CONFIG_SLUG || context.removed.includes(params.slug)) return {}\n\n return {\n active: context.active === params.slug ? null : context.active,\n removed: [...context.removed, params.slug],\n }\n }),\n restore: assign(({context}, params: {slug: string}) => ({\n removed: context.removed.filter((slug) => slug !== params.slug),\n })),\n reorder: assign(({context}, params: {order: string[]}) => {\n const listed = new Set(themesOf(context).themes.map((theme) => theme.slug))\n const order = params.order.filter((slug) => listed.has(slug))\n\n // The removed themes keep their place in line for when they are restored\n return {\n order: [...order, ...context.order.filter((slug) => !order.includes(slug))],\n }\n }),\n delete: assign(({context}, params: {slug: string}) => {\n if (!context.custom.some((theme) => theme.slug === params.slug)) return {}\n\n const {[params.slug]: _deleted, ...images} = context.images\n\n return {\n active: context.active === params.slug ? null : context.active,\n custom: context.custom.filter((theme) => theme.slug !== params.slug),\n removed: context.removed.filter((slug) => slug !== params.slug),\n images,\n }\n }),\n },\n}).createMachine({\n id: 'themer',\n context: () => ({...initialThemerState, editing: null, images: {}}),\n type: 'parallel',\n states: {\n theme: {\n initial: 'applied',\n states: {\n // Only events that change what is applied count: adding a copy of the\n // applied theme, editing or duplicating it, picking it again, or\n // removing another theme leaves the Studio looking the same — with\n // nothing to cross-fade, the switch would only linger until the first\n // edit that followed, which must follow the pointer\n applied: {\n on: {\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n },\n 'theme.import': 'switching',\n 'themes.sync': {\n guard: {type: 'appliesAnotherTheme', params: ({event}) => ({state: event.state})},\n target: 'switching',\n },\n },\n },\n switching: {\n tags: ['switching'],\n after: {[MOTION_DURATION]: 'applied'},\n on: {\n // The editor edits the applied theme, and its colors must follow\n // the pointer rather than cross-fade — a switch is over at the\n // first edit, however soon it comes\n 'theme.update': 'applied',\n // Another switch starts the clock over\n 'theme.pick': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.edit': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.duplicate': {\n guard: {type: 'isAnotherTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.remove': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.delete': {\n guard: {type: 'isAppliedTheme', params: ({event}) => ({slug: event.slug})},\n target: 'switching',\n reenter: true,\n },\n 'theme.add': {\n guard: {type: 'hasOptions', params: ({event}) => ({options: event.options})},\n target: 'switching',\n reenter: true,\n },\n 'theme.import': {target: 'switching', reenter: true},\n 'themes.sync': {\n guard: {type: 'appliesAnotherTheme', params: ({event}) => ({state: event.state})},\n target: 'switching',\n reenter: true,\n },\n },\n },\n },\n },\n flow: {\n initial: 'list',\n on: {\n 'theme.pick': {\n actions: [{type: 'pick', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.add': {\n target: '.edit',\n actions: [\n {\n type: 'add',\n params: ({event}) => ({\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n ],\n },\n 'theme.duplicate': {\n target: '.edit',\n actions: [{type: 'duplicate', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.edit': {\n target: '.edit',\n guard: {type: 'isCustomTheme', params: ({event}) => ({slug: event.slug})},\n actions: [{type: 'startEditing', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.update': {\n actions: [\n // A new image replaces the one the theme had\n {\n type: 'revokeImage',\n params: ({context, event}) => ({\n url: event.imageUrl ? context.images[event.slug] : undefined,\n }),\n },\n {\n type: 'update',\n params: ({event}) => ({\n slug: event.slug,\n title: event.title,\n options: event.options,\n palette: event.palette,\n imageUrl: event.imageUrl,\n }),\n },\n ],\n },\n 'theme.remove': {\n actions: [{type: 'remove', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.restore': {\n actions: [{type: 'restore', params: ({event}) => ({slug: event.slug})}],\n },\n 'theme.delete': {\n actions: [\n {\n type: 'revokeImage',\n params: ({context, event}) => ({url: context.images[event.slug]}),\n },\n {type: 'delete', params: ({event}) => ({slug: event.slug})},\n ],\n },\n 'theme.reorder': {\n actions: [{type: 'reorder', params: ({event}) => ({order: event.order})}],\n },\n 'theme.import': {\n actions: [\n {\n type: 'import',\n params: ({event}) => ({title: event.title, options: event.options}),\n },\n ],\n },\n 'themes.sync': {\n actions: {type: 'sync', params: ({event}) => ({state: event.state})},\n },\n 'flow.list': '.list',\n 'flow.removed': {\n target: '.removed',\n guard: 'hasRemovedThemes',\n },\n },\n states: {\n list: {\n initial: 'idle',\n // The list's dialogs — for a shared code pasted by hand, and for the\n // applied theme's snippet — are states of the list, so that leaving\n // it, as adding a theme does for the editor, closes them\n states: {\n idle: {\n on: {\n 'dialog.paste': 'pasting',\n 'dialog.snippet': {guard: 'hasAppliedTheme', target: 'snippet'},\n },\n },\n pasting: {\n on: {'dialog.close': 'idle'},\n },\n snippet: {\n on: {'dialog.close': 'idle'},\n // The applied theme can go away meanwhile — removed from another tab\n always: {guard: not('hasAppliedTheme'), target: 'idle'},\n },\n },\n },\n edit: {\n exit: 'stopEditing',\n // The theme being edited can go away — removed from the editor, or\n // duplicated from a theme that does not exist — and then there is\n // nothing left to edit\n always: {guard: not('isEditingListedTheme'), target: 'list'},\n },\n removed: {\n always: {guard: not('hasRemovedThemes'), target: 'list'},\n },\n },\n },\n },\n})\n\n/** @internal */\nexport type ThemerSnapshot = SnapshotFrom<typeof themerMachine>\n\n/** The part of the machine context that is persisted between sessions @internal */\nexport function selectStoredState(snapshot: ThemerSnapshot): ThemerState {\n const {active, custom, removed, order} = snapshot.context\n\n return {active, custom, removed, order}\n}\n","import {dequal} from 'dequal/lite'\nimport type {ActorRefFrom, Snapshot} from 'xstate'\n\nimport {selectStoredState, type themerMachine} from './machine'\nimport {parseThemerState} from './schemas'\nimport type {ThemerState} from './themes'\n\n/** The channel every tab of the same origin shares — one Studio, one set of themes */\nconst CHANNEL_NAME = 'sanity-themer'\n\n/**\n * The lock the one tab that persists holds for as long as it lives. The\n * other tabs queue for it, so when that tab goes away the next one in line\n * takes over the persisting — nothing is ever written from two tabs at once\n */\nconst LOCK_NAME = 'sanity-themer:persist'\n\n/** How long a persist waits for the browser to be idle before it goes ahead anyway */\nconst PERSIST_TIMEOUT = 1_000\n\n/**\n * What the tabs tell each other. Every change to the persisted state goes\n * around as `state`, with the `revision` it brings the state to — one more\n * than the last one the tab heard of — and the tab it is `from`: a tab takes\n * a state only when it is newer than the one it has, ties going to the\n * higher tab id, so that two tabs changing at once end up with the same\n * state rather than each other's. A tab that just started asks for the\n * current state with `hello`, answered with a `state` addressed `to` it,\n * which only it takes, and only while it has changed nothing of its own yet\n * — a change it made meanwhile goes around again instead, ahead of the\n * answer, so that a late answer never undoes it\n */\ntype SyncMessage =\n | {type: 'hello'; from: string}\n | {type: 'state'; state: ThemerState; revision: number; from: string; to?: string}\n\n/** What the sync needs of a `BroadcastChannel` @internal */\nexport interface SyncChannel {\n /** Sends a message to the other tabs — not to this one */\n post(message: unknown): void\n /** Hears the other tabs' messages; returns what stops listening */\n listen(listener: (data: unknown) => void): () => void\n close(): void\n}\n\n/** What the sync needs of `navigator.locks` @internal */\nexport interface SyncLocks {\n request(\n name: string,\n options: {signal?: AbortSignal},\n callback: () => Promise<unknown>,\n ): Promise<unknown>\n}\n\n/**\n * What the sync does with the browser, each replaceable — so that it can run\n * under test, and so that it degrades where a browser lacks an API: without\n * a channel the tabs do not hear of each other, and without locks every tab\n * persists for itself\n *\n * @internal\n */\nexport interface ThemerSyncOptions {\n /** Persists the machine's snapshot — from the one tab that holds the lock */\n persist: (snapshot: Snapshot<unknown>) => void\n /** Opens the channel to the other tabs, or `null` where there is none */\n openChannel?: () => SyncChannel | null\n /** Elects the tab that persists, or `undefined` where there is no lock manager */\n locks?: SyncLocks | undefined\n /** Defers a persist to an idle moment; returns what cancels it */\n schedule?: (callback: () => void) => () => void\n}\n\nfunction openBroadcastChannel(): SyncChannel | null {\n if (typeof BroadcastChannel !== 'function') return null\n\n const channel = new BroadcastChannel(CHANNEL_NAME)\n\n return {\n // oxlint-disable-next-line require-post-message-target-origin -- a BroadcastChannel is same-origin by nature, its postMessage takes no target\n post: (message) => channel.postMessage(message),\n listen: (listener) => {\n const onMessage = (event: MessageEvent) => listener(event.data)\n\n channel.addEventListener('message', onMessage)\n\n return () => channel.removeEventListener('message', onMessage)\n },\n close: () => channel.close(),\n }\n}\n\nfunction browserLocks(): SyncLocks | undefined {\n // Web Locks need a secure context, where `navigator.locks` is undefined otherwise\n return typeof navigator !== 'undefined' && 'locks' in navigator ? navigator.locks : undefined\n}\n\nfunction scheduleIdle(callback: () => void): () => void {\n if (typeof requestIdleCallback === 'function') {\n const id = requestIdleCallback(callback, {timeout: PERSIST_TIMEOUT})\n\n return () => cancelIdleCallback(id)\n }\n\n const id = setTimeout(callback, PERSIST_TIMEOUT)\n\n return () => clearTimeout(id)\n}\n\nfunction parseMessage(data: unknown): SyncMessage | null {\n if (!data || typeof data !== 'object') return null\n\n const type: unknown = Reflect.get(data, 'type')\n\n if (type === 'hello') {\n const from: unknown = Reflect.get(data, 'from')\n\n return typeof from === 'string' ? {type, from} : null\n }\n\n if (type === 'state') {\n const state = parseThemerState(Reflect.get(data, 'state'))\n const revision: unknown = Reflect.get(data, 'revision')\n const from: unknown = Reflect.get(data, 'from')\n const to: unknown = Reflect.get(data, 'to')\n\n if (!state || typeof revision !== 'number' || !Number.isFinite(revision)) return null\n if (typeof from !== 'string') return null\n\n return {type, state, revision, from, ...(typeof to === 'string' ? {to} : {})}\n }\n\n return null\n}\n\nfunction tabId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2)\n}\n\n/**\n * Keeps the themes of every tab of the Studio the same, and persists them\n * from one tab only.\n *\n * Every tab runs its own machine, and what the machine persists — the\n * applied theme, the user's themes, what was removed and reordered — goes\n * around to the other tabs on a `BroadcastChannel` as it changes, where it\n * reaches their machines as a `themes.sync` event: the same themes\n * everywhere, with the flow each tab is in, the tool's open and split state\n * (the reducer's, in `plugin.tsx`) and the images of this session staying\n * the tab's own. A tab that starts asks the others for the current state,\n * in case what it read from storage was not written yet.\n *\n * Persisting is the business of one tab at a time: the one holding the Web\n * Lock, which it keeps for as long as it lives. The others queue for it, so\n * the next tab takes over as the holder goes away, and a tab that takes\n * over persists right away, in case the last one left with a write pending.\n * Writes happen when the browser is idle — a run of edits is written once —\n * and as the page hides, so that nothing pending is lost.\n *\n * Returns what stops the sync: it lets go of the lock, which hands the\n * persisting on, and closes the channel.\n *\n * @internal\n */\nexport function syncThemer(\n actorRef: ActorRefFrom<typeof themerMachine>,\n options: ThemerSyncOptions,\n): () => void {\n const {persist, openChannel = openBroadcastChannel, schedule = scheduleIdle} = options\n const channel = openChannel()\n // Without a channel the tabs cannot hear of each other's changes, so each\n // persists its own — a lock would only silence the tabs not holding it.\n // Given as `undefined` means no lock manager, not the browser's: Node has\n // one too these days\n const locks = channel ? ('locks' in options ? options.locks : browserLocks()) : undefined\n const id = tabId()\n const abort = new AbortController()\n let leader = false\n let cancelPersist: (() => void) | undefined\n /** Whether another tab's state is being sent into the machine, which is nothing to pass on */\n let applying = false\n /** The persisted state as the other tabs last heard it from this one, or this one from them */\n let shared = selectStoredState(actorRef.getSnapshot())\n /** The revision `shared` is at, and the tab whose change it was */\n let revision = 0\n let origin = id\n /** Whether this tab still has what it started with, so that an answer to its `hello` is welcome */\n let unchanged = true\n /** The tabs that said hello before this one held the lock, to answer once it does */\n const unanswered = new Set<string>()\n\n const isNewer = (message: {revision: number; from: string}) =>\n message.revision > revision || (message.revision === revision && message.from > origin)\n\n const persistNow = () => {\n cancelPersist?.()\n cancelPersist = undefined\n persist(actorRef.getPersistedSnapshot())\n }\n const persistSoon = () => {\n cancelPersist?.()\n cancelPersist = schedule(persistNow)\n }\n const answer = (to: string) => {\n channel?.post({type: 'state', state: shared, revision, from: origin, to} satisfies SyncMessage)\n }\n const becomeLeader = () => {\n leader = true\n persistNow()\n // Tabs that started alongside this one asked before anyone held the lock\n for (const from of unanswered) answer(from)\n unanswered.clear()\n }\n\n if (locks) {\n locks\n .request(LOCK_NAME, {signal: abort.signal}, () => {\n becomeLeader()\n\n // Held until the sync stops, which hands the lock to the next tab in line\n return new Promise<void>((resolve) => {\n abort.signal.addEventListener('abort', () => resolve(), {once: true})\n })\n })\n .catch(() => {\n // The sync stopped before the lock was granted — nothing to let go of\n })\n } else {\n becomeLeader()\n }\n\n const subscription = actorRef.subscribe((snapshot) => {\n if (leader) persistSoon()\n if (applying) return\n\n const state = selectStoredState(snapshot)\n\n // Only changes to what is persisted concern the other tabs — the flow is this tab's\n if (dequal(state, shared)) return\n\n shared = state\n unchanged = false\n revision += 1\n origin = id\n channel?.post({type: 'state', state, revision, from: id} satisfies SyncMessage)\n })\n\n if (channel) {\n channel.listen((data) => {\n const message = parseMessage(data)\n\n if (!message) return\n\n if (message.type === 'hello') {\n // The tab that persists speaks for all, so that the newcomer hears one\n // answer — or hears it once a tab holds the lock\n if (leader) {\n answer(message.from)\n } else {\n unanswered.add(message.from)\n }\n\n return\n }\n\n if (message.to !== undefined) {\n if (message.to !== id) return\n\n if (!unchanged) {\n // Asked before this tab changed anything, answered after: the change\n // is the later one, and goes around again ahead of the answer\n if (origin === id) {\n revision = Math.max(revision, message.revision) + 1\n channel.post({type: 'state', state: shared, revision, from: id} satisfies SyncMessage)\n\n return\n }\n\n // Answered twice — without a lock manager every tab answers — the\n // answers are ordered like any other state\n if (!isNewer(message)) return\n }\n } else if (!isNewer(message)) {\n return\n }\n\n revision = message.revision\n origin = message.from\n if (dequal(message.state, shared)) return\n\n shared = message.state\n unchanged = false\n applying = true\n try {\n actorRef.send({type: 'themes.sync', state: message.state})\n } finally {\n applying = false\n }\n })\n channel.post({type: 'hello', from: id} satisfies SyncMessage)\n }\n\n const flush = () => {\n if (leader && cancelPersist) persistNow()\n }\n const page = typeof window === 'undefined' ? undefined : window\n\n page?.addEventListener('pagehide', flush)\n\n return () => {\n subscription.unsubscribe()\n page?.removeEventListener('pagehide', flush)\n flush()\n abort.abort()\n channel?.close()\n }\n}\n","import {useEffect} from 'react'\n\nimport type {ThemerProps} from '#types'\n\nimport {writePersistedSnapshot} from './storage'\nimport {syncThemer} from './sync'\n\n/**\n * Persists the machine's snapshot for the next session, and keeps the themes\n * in step with the other tabs of the Studio — from the one tab holding the\n * Web Lock, when the browser is idle; see `sync.ts`. Rendered by the layout,\n * outside the sidebar's `Activity`, so that it runs while the sidebar is\n * closed too: other tabs change the themes whether this one shows them or\n * not.\n */\nexport function PersistActorSnapshot({actorRef}: Pick<ThemerProps, 'actorRef'>) {\n useEffect(() => syncThemer(actorRef, {persist: writePersistedSnapshot}), [actorRef])\n\n return null\n}\n","import {BoundaryElementProvider, Card, Flex} from '@sanity/ui'\nimport type {ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useActorRef} from '@xstate/react'\nimport {Activity, lazy, startTransition, useEffect, useState, ViewTransition} from 'react'\n\nimport type {PluginConfig} from '#types'\nimport type {ThemerProps} from '#types'\n\nimport {addThemerTransitionType} from './addThemerTransitionType'\nimport {themerMachine} from './machine'\nimport {PersistActorSnapshot} from './PersistActorSnapshot'\nimport type {ToolReducerState} from './reducer'\nimport {resolveActiveThemeOptions} from './selectors'\nimport type {readPersistedSnapshot} from './storage'\nimport {ThemerNavbarProvider} from './ThemerNavbar'\n\nimport {layout, viewTransitionClasses} from './ViewTransitions.css'\n\n/**\n * The sidebar — everything in it, from the theme list to the snippet dialog —\n * loads the first time it opens. There is no `Suspense` boundary around it,\n * so it must only ever mount from a transition: the navbar toggle prerenders\n * it in one as the pointer comes near, which keeps the Studio as it was while\n * the code loads, where an urgent render would suspend up to the Studio's\n * own boundary and swap the whole Studio for its loading screen.\n */\nconst ResizableSidebar = lazy(() => import('./ResizableSidebar'))\n\ninterface ThemerLayoutProps extends Omit<ToolReducerState, 'theme'> {\n children: React.ReactNode\n config: PluginConfig\n dispatch: ThemerProps['dispatch']\n /** The machine's snapshot as the last session persisted it, for the machine to pick up where it left off */\n persistedSnapshot: ReturnType<typeof readPersistedSnapshot>\n}\n\n/**\n * Wraps the whole Studio to run the themer machine that the sidebar's flows\n * share — the user's themes, which one is applied and which flow the sidebar\n * is in — and renders the sidebar next to the Studio. The sidebar sits at\n * the `layout` level rather than in `activeToolLayout` because the split\n * preview renders the Studio twice, and the sidebar must not come along.\n *\n * Whether the sidebar is open and whether the Studio shows twice comes in as\n * props from the tool reducer in `ThemerProvider`, and goes on to the sidebar\n * as props along with the machine's actor, which the sidebar selects its\n * themes and flow from (see `ThemerProps`): the navbar toggle and the sidebar\n * dispatch to the reducer inside transitions tagged with a transition type\n * (see `addThemerTransitionType`), and the `ViewTransition` boundaries below\n * pick their classes by that type, so React animates the sidebar sliding in\n * and out, the split copy coming and going and a picked theme cross-fading\n * in (React 19.3), styled in `ViewTransitions.css.ts`. The sidebar and the\n * split copy stay mounted while hidden, in `Activity`, so that opening them\n * again is instant and they can prerender ahead of the transition. The\n * navbar toggle, which the Studio renders somewhere in `children`, reads the\n * same values and the plugin's options from the contexts\n * `ThemerNavbarProvider` provides in each copy of the Studio, and measures\n * the Studio navbar for the sidebar's header — in the Studio proper, not in\n * the split copy, which `ToolShouldDetectNavbarHeightContext` tells apart.\n *\n * The applied theme is the reducer's, provided above this layout in\n * `ThemerProvider`: the machine publishes it synchronously, which React does\n * not animate, so `ThemerThemeCrossfader` hands it to the reducer in a\n * transition of its own. The Studio next to the sidebar always follows the\n * appearance setting (light/dark/system) and the picked theme like any other\n * theme would; the split preview adds a second copy in the opposite scheme\n * on the far side.\n *\n * Whatever else on the page has a `view-transition-name` — an avatar the\n * Studio names so it moves as one piece — comes along in step: every\n * transition the layout starts carries `layoutTransitionType`, which the\n * stylesheet keys on to move every group in the layout's time (see\n * `ViewTransitions.css.ts`).\n *\n * @internal\n */\nexport function ThemerLayout({\n children,\n backgroundColor,\n config,\n dispatch,\n navbarHeight,\n open,\n persistedSnapshot,\n prerender,\n prerenderSplitScreen,\n scheme,\n split,\n}: ThemerLayoutProps & {backgroundColor: string | undefined; scheme: ThemeColorSchemeKey}) {\n const actorRef = useActorRef(themerMachine, {snapshot: persistedSnapshot})\n\n const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\n\n return (\n <>\n <Flex\n className={layout}\n direction={'row'}\n height=\"fill\"\n sizing=\"border\"\n style={{backgroundColor}}\n >\n {/* Renders the Studio for the second time, in the opposite scheme of the main studio. Hidden by default */}\n <Activity mode={split && open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.splitscreen.enter}\n update={viewTransitionClasses.splitscreen.update}\n exit={viewTransitionClasses.splitscreen.exit}\n >\n {prerenderSplitScreen && (\n <StudioPreview\n borderRight\n config={config}\n dispatch={dispatch}\n open={open}\n scheme={oppositeScheme}\n split={split}\n >\n {children}\n </StudioPreview>\n )}\n </ViewTransition>\n </Activity>\n {/* Renders the Studio */}\n <ViewTransition default=\"none\" update={viewTransitionClasses.studio.update}>\n <StudioPreview\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight\n split={split}\n >\n {children}\n </StudioPreview>\n </ViewTransition>\n {/* Renders the Themer tool, hidden by default */}\n <Activity mode={open ? 'visible' : 'hidden'}>\n <ViewTransition\n default=\"none\"\n enter={viewTransitionClasses.sidebar.enter}\n update={viewTransitionClasses.sidebar.update}\n exit={viewTransitionClasses.sidebar.exit}\n >\n {prerender && (\n <ResizableSidebar\n actorRef={actorRef}\n dispatch={dispatch}\n navbarHeight={navbarHeight}\n split={split}\n />\n )}\n </ViewTransition>\n </Activity>\n </Flex>\n\n <ThemerThemeCrossfader actorRef={actorRef} dispatch={dispatch} />\n <RevokeImageUrls actorRef={actorRef} />\n <PersistActorSnapshot actorRef={actorRef} />\n </>\n )\n}\n\n/**\n * One copy of the Studio, in the given color scheme — or, without one, in the\n * scheme the Studio is showing. The two copies of the split preview share the\n * router, the document store and every other provider above the layout —\n * only the scheme differs — so they stay in sync while navigating. The\n * `color-scheme` of a forced scheme keeps native form controls and scrollbars\n * in step with it. Each copy provides the navbar toggle it renders with the\n * contexts it reads (see `ThemerNavbarProvider`).\n */\nfunction StudioPreview({\n borderRight,\n children,\n config,\n dispatch,\n open,\n scheme,\n shouldDetectNavbarHeight,\n split,\n}: {\n borderRight?: boolean\n scheme?: ThemeColorSchemeKey\n} & React.ComponentProps<typeof ThemerNavbarProvider>) {\n const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)\n\n return (\n <Card\n borderRight={borderRight}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n scheme={scheme}\n ref={(boundaryElement) => {\n startTransition(() => setBoundaryElement(boundaryElement))\n // Intentionally not unsetting the element as it would create unnecessary work when <Activity> unhides\n return () => {}\n }}\n >\n <BoundaryElementProvider element={boundaryElement}>\n <ThemerNavbarProvider\n config={config}\n dispatch={dispatch}\n open={open}\n shouldDetectNavbarHeight={shouldDetectNavbarHeight}\n split={split}\n >\n <ViewTransition update=\"none\">{children}</ViewTransition>\n </ThemerNavbarProvider>\n </BoundaryElementProvider>\n </Card>\n )\n}\n\n/**\n * Hands the applied theme to the tool reducer as the machine publishes it —\n * in a transition tagged to cross-fade while the machine says a theme is\n * being switched to, so that editing the applied theme's colors applies live\n * while picking another theme fades the Studio over to it\n */\nfunction ThemerThemeCrossfader({actorRef, dispatch}: Pick<ThemerProps, 'actorRef' | 'dispatch'>) {\n useEffect(() => {\n const subscription = actorRef.subscribe((snapshot) => {\n const switching = snapshot.hasTag('switching')\n const theme = resolveActiveThemeOptions(snapshot.context)\n\n startTransition(() => {\n if (switching) {\n addThemerTransitionType('crossfade')\n }\n if (!theme) {\n dispatch({type: 'unset-theme'})\n } else {\n dispatch({type: 'set-theme', theme})\n }\n })\n })\n\n return () => subscription.unsubscribe()\n }, [actorRef, dispatch])\n\n return null\n}\n\nfunction RevokeImageUrls({actorRef}: Pick<ThemerProps, 'actorRef'>) {\n // The machine releases the image of a palette as it is replaced or its\n // theme deleted; whatever it still holds goes with the layout — the actor\n // keeps its last snapshot once stopped\n useEffect(\n () => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n },\n [actorRef],\n )\n\n return null\n}\n","import {ThemeProvider, useMediaIndex} from '@sanity/ui'\nimport {use, useReducer, useState} from 'react'\nimport {browser} from 'react-dom'\nimport {useColorSchemeValue} from 'sanity'\n\nimport {MIN_MEDIA_INDEX_FOR_SIDEBAR, MIN_MEDIA_INDEX_FOR_SPLIT_SCREEN} from '#constants'\nimport type {ThemerProps} from '#types'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport {initialToolState, toolReducer} from './reducer'\nimport {resolveActiveThemeOptions} from './selectors'\nimport {readPersistedSnapshot} from './storage'\nimport {ThemerLayout} from './ThemerLayout'\n\n/**\n * Keep this component extremely lightweight, and only include state that are needed by both\n * `studio.components.layout` and `studio.components.navbar`, where it's not possible to prop-drill into `studio.components.navbar`,\n * and that need to survive open/close of the tool\n * @TODO will mount on the future `studio.components.provider` entrypoint to ensure other plugins using `studio.components.layout` is correctly wrapped in the top-level theme regardless of plugin ordering\n */\nexport function ThemerProvider({\n children,\n config,\n}: {\n children: React.ReactNode\n} & Pick<ThemerProps, 'config'>) {\n use(browser('The current theme is stored in localStorage.'))\n\n const [persistedSnapshot] = useState(readPersistedSnapshot)\n const [state, dispatch] = useReducer(toolReducer, initialToolState, (initialState) => {\n if (!persistedSnapshot) return initialState\n const theme = resolveActiveThemeOptions(persistedSnapshot.context)\n return theme ? {...initialState, theme} : initialState\n })\n\n const scheme = useColorSchemeValue()\n const index = useMediaIndex()\n const isMobile = index <= MIN_MEDIA_INDEX_FOR_SIDEBAR\n const isTooSmallForSplitScreen = index <= MIN_MEDIA_INDEX_FOR_SPLIT_SCREEN\n\n // @TODO Themer does not yet support mobile\n const open = state.open && !isMobile\n // Split screen needs some space\n const split = state.split && !isTooSmallForSplitScreen\n\n return (\n <ThemeProvider theme={(state.theme !== null ? buildTheme(state.theme) : null) ?? undefined}>\n <ThemerLayout\n config={config}\n backgroundColor={state.theme?.[scheme]?.background}\n dispatch={dispatch}\n navbarHeight={state.navbarHeight}\n open={open}\n persistedSnapshot={persistedSnapshot}\n prerender={state.prerender}\n prerenderSplitScreen={state.prerenderSplitScreen}\n scheme={scheme}\n split={split}\n >\n {children}\n </ThemerLayout>\n </ThemeProvider>\n )\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport type {PluginConfig} from '#types'\n\nimport {ThemerNavbar} from './ThemerNavbar'\nimport {ThemerProvider} from './ThemerProvider'\n\n/**\n * Options for the {@link themerTool} plugin.\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport interface ThemerToolOptions {\n /**\n * What the tool goes by in the Studio navbar — the toggle's label and\n * tooltip. Defaults to `Themer`.\n */\n title?: string\n}\n\n/**\n * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a\n * navbar toggle opens the sidebar next to the Studio, with a list of themes —\n * the configured theme, the presets and your own — each previewed as a tiny\n * Studio in both color schemes. Picking one applies it live to the whole\n * Studio while you browse around; your own themes can be edited with\n * accent/text/background pickers and a contrast slider per scheme, added,\n * duplicated, given the colors of an image (its palette is read on device),\n * removed and restored, and a dialog shows the `buildTheme` snippet that\n * makes the applied theme permanent. Toggle between light and dark mode with\n * the regular appearance menu — the preview follows it — or split the\n * preview to see the whole Studio in light and dark side by side.\n *\n * ```ts\n * import {themerTool} from '@sanity/themer/tool'\n * import {defineConfig} from 'sanity'\n *\n * export default defineConfig({\n * plugins: [themerTool()],\n * // ...rest of the config\n * })\n * ```\n *\n * This is experimental and may change or be removed in any release without\n * notice — use at your own risk.\n *\n * @alpha\n */\nexport const themerTool = definePlugin<ThemerToolOptions | void>((options) => {\n const {title = 'Themer'} = options ?? {}\n const ThemerLayoutProvider = defineThemerLayout({title})\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {layout: ThemerLayoutProvider, navbar: ThemerNavbar},\n },\n }\n})\n\n// This wrapper creates a clean closure that allows safely using the `'use memo'` directive\n// so that the dynamically created component can be memoized by react compiler\nfunction defineThemerLayout(config: PluginConfig) {\n return function DefinedThemerLayoutProvider(props: LayoutProps) {\n 'use memo'\n\n return <ThemerProvider config={config}>{props.renderDefault(props)}</ThemerProvider>\n }\n}\n"],"mappings":"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"}
\ No newline at end of file