@sanity/themer 0.10.3 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AnimatedColorWheelIcon-CwWOVAsw.js.map +1 -1
- package/dist/{ResizableSidebar-B5Px5VCf.js → ResizableSidebar-Qo2gLzlm.js} +29 -29
- package/dist/{ResizableSidebar-B5Px5VCf.js.map → ResizableSidebar-Qo2gLzlm.js.map} +1 -1
- package/dist/bundle.css +109 -109
- package/dist/{themes-sELelb-j.js → themes-C_-85qf1.js} +29 -29
- package/dist/{themes-sELelb-j.js.map → themes-C_-85qf1.js.map} +1 -1
- package/dist/tool.js +2 -2
- package/dist/tool.js.map +1 -1
- package/package.json +8 -8
package/dist/tool.js.map
CHANGED
|
@@ -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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"}
|
|
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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"}
|