@sanity/themer 0.8.1 → 0.9.0

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/tool.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tool.js","names":["usePrefersReducedMotion","() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }","[ref]","ref.current","_c","() => {\n if (Object.is(deferred, value)) return\n\n startTransition(() => {\n addTransitionType(type)\n setTransitions((count) => count + 1)\n })\n }","[deferred, type, value]","(count) => count + 1","_c","() => ({baseOptions, stored: readStoredState()})","{input}","snapshot.hasTag('panel')","snapshot.hasTag('split')","snapshot.hasTag('moving')","snapshot.hasTag('switching')","resolveThemes(stored, baseOptions)","useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )","activeOptions === null ? null : buildTheme(activeOptions)","useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )","() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }","color","[scheme, shownTheme]","() => () => {\n for (const url of Object.values(actorRef.getSnapshot().context.images)) {\n URL.revokeObjectURL(url)\n }\n }","[actorRef]","{\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n loading,\n split,\n mobile,\n navbarHeight,\n send,\n }","useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n loading,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [\n active,\n baseOptions,\n images,\n loading,\n mobile,\n navbarHeight,\n open,\n removed,\n send,\n split,\n themes,\n view,\n ],\n )","() => send({type: 'layout.transitioned'})","reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }","shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )","copyEnter","copyExit","update","<StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>","<ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>","studioUpdate","panelMounted && <ResizableSidebar overlay={mobile} />","<ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>","shownTheme ?? undefined","<ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>\n </ViewTransition>","panelEnter","panelExit","<Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>\n </ViewTransition>\n </Activity>","shownOpen ? 'visible' : 'hidden'","<Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: once it has opened it stays\n mounted, hidden while closed, keeping its state and ready to show.\n Until then its content is left out — hidden, it would be\n pre-rendered, and its code loaded, as soon as the Studio renders */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>","mobile ? 'column' : 'row'","<ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: once it has opened it stays\n mounted, hidden while closed, keeping its state and ready to show.\n Until then its content is left out — hidden, it would be\n pre-rendered, and its code loaded, as soon as the Studio renders */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>","ThemerContext.Provider","<Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>","scheme ? studioScheme[scheme] : undefined","<ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>","theme ?? undefined","_c","usePrefersReducedMotion","(value) => (isSliceFilled(value, index) ? 1 : 0)","{opacity}","<motion.path d={SLICE_PATHS[index]} fill={SLICE_COLORS[index]} style={{opacity}} />","SLICE_PATHS[index]","SLICE_COLORS[index]","motion.path","(value) =>\n prefersReducedMotion ? 0 : wheelRotation(value)","{rotate}","SLICE_COLORS.map((color, index) => (\n <Slice index={index} key={color} progress={progress} />\n ))","<path d={OUTLINE} stroke=\"currentColor\" strokeLinejoin=\"round\" strokeWidth={1.2} />","<motion.svg\n data-sanity-icon=\"color-wheel\"\n fill=\"none\"\n height=\"1em\"\n style={{rotate}}\n viewBox=\"0 0 25 25\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n {SLICE_COLORS.map((color, index) => (\n <Slice index={index} key={color} progress={progress} />\n ))}\n <path d={OUTLINE} stroke=\"currentColor\" strokeLinejoin=\"round\" strokeWidth={1.2} />\n </motion.svg>","motion.svg","_c","() => {\n // A run in progress plays out — hovering again does not cut it short\n if (progress.isAnimating()) return\n\n animate(progress, [0, 1], {duration: ANIMATION_DURATION, ease: 'linear'})\n }","useCallback(() => {\n // A run in progress plays out — hovering again does not cut it short\n if (progress.isAnimating()) return\n\n animate(progress, [0, 1], {duration: ANIMATION_DURATION, ease: 'linear'})\n }, [progress])","() => {\n if (!loading) return undefined\n\n let looping = true\n const lap = (from: number) => {\n animate(progress, [from, 1], {\n duration: (1 - from) * ANIMATION_DURATION,\n ease: 'linear',\n onComplete: () => {\n if (looping) lap(0)\n },\n })\n }\n\n lap(progress.get())\n\n return () => {\n looping = false\n }\n }","[loading, progress]","() => () => progress.stop()","[progress]","() => {\n const navbar = button?.closest(NAVBAR_SELECTOR)\n\n if (!navbar || introduced) return undefined\n\n const introduce = () => {\n // Another navbar (the split preview's) may have had the chance meanwhile\n if (introduced) return\n\n introduced = true\n // Checked as the pointer comes in, not up front: a click in between\n // (on the button itself) counts\n if (!hasVisited()) play()\n }\n\n navbar.addEventListener('mouseenter', introduce, {once: true})\n\n return () => navbar.removeEventListener('mouseenter', introduce)\n }","[button, play]","button?.closest","<Text size={1}>Themer</Text>","<AnimatedColorWheelIcon progress={progress} />","() => toggleSidebar(send)","() => {\n preloadSidebar()\n play()\n }","<Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-busy={loading}\n aria-label=\"Themer\"\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n disabled={loading}\n icon={<AnimatedColorWheelIcon progress={progress} />}\n mode=\"bleed\"\n onClick={() => toggleSidebar(send)}\n onFocus={preloadSidebar}\n onMouseEnter={() => {\n preloadSidebar()\n play()\n }}\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 ref={setButton}\n selected={open}\n />\n </Tooltip>","props.__internal_actions ?? []","__internal_actions","props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\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 and its once-only introduction\n render: ThemerNavbarButton,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => toggleSidebar(send),\n selected: open,\n title: 'Themer',\n },\n ],\n })","_c","<ThemerLayout {...props} baseOptions={baseOptions} />","_c"],"sources":["../src/tool/storage.ts","../src/tool/machine.ts","../src/tool/usePrefersReducedMotion.ts","../src/tool/useStudioNavbarHeight.ts","../src/tool/useTypedDeferredValue.ts","../src/tool/ThemerLayout.css.ts","../src/tool/ThemerLayout.tsx","../src/tool/colorWheel.ts","../src/tool/AnimatedColorWheelIcon.tsx","../src/tool/ThemerNavbar.tsx","../src/tool/plugin.tsx"],"sourcesContent":["import {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 {IMAGE_PALETTE_KEYS, type ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n type CustomTheme,\n displayTitle,\n initialThemerState,\n type ThemerState,\n} from './themes'\n\nconst STORAGE_KEY = 'sanityStudio:themer:state'\n\n/**\n * Where earlier versions of the tool kept their single draft theme — it is\n * migrated into a custom theme, so a draft survives the upgrade.\n */\nconst LEGACY_STORAGE_KEY = 'sanityStudio:themer:options'\n\n/** The title a draft from before themes had titles is imported under */\nconst LEGACY_DRAFT_TITLE = 'Draft theme'\n\n/**\n * Where it is noted that the sidebar has been opened at least once — until\n * then, the navbar introduces the tool with an animation of its icon\n */\nconst VISITED_STORAGE_KEY = 'sanityStudio:themer:visited'\n\nfunction sanitizeColor(value: unknown): string | null {\n return typeof value === 'string' && isColor(value) ? value.toLowerCase() : null\n}\n\nfunction sanitizeContrast(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value)\n ? Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))\n : null\n}\n\nfunction sanitizeSchemeOptions(value: unknown): SchemeThemeOptions {\n const options: SchemeThemeOptions = {}\n\n if (!value || typeof value !== 'object') return options\n\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const background = sanitizeColor(Reflect.get(value, 'background'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (background) options.background = background\n if (contrast !== null) options.contrast = contrast\n\n return options\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 */\nfunction sanitizeFlatOptions(value: object): BuildThemeOptions | null {\n const accent = sanitizeColor(Reflect.get(value, 'accent'))\n\n if (!accent) return null\n\n const shared: SchemeThemeOptions = {accent}\n const text = sanitizeColor(Reflect.get(value, 'text'))\n const contrast = sanitizeContrast(Reflect.get(value, 'contrast'))\n\n if (text) shared.text = text\n if (contrast !== null) shared.contrast = contrast\n\n const backgrounds: unknown = Reflect.get(value, 'background')\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const background =\n backgrounds && typeof backgrounds === 'object'\n ? sanitizeColor(Reflect.get(backgrounds, scheme))\n : null\n\n options[scheme] = background ? {...shared, background} : {...shared}\n }\n\n return options\n}\n\nfunction sanitizeOptions(value: unknown): BuildThemeOptions | null {\n if (!value || typeof value !== 'object') return null\n\n // The flat shape always had an accent, the scheme shape never has one at the top\n if ('accent' in value) {\n return sanitizeFlatOptions(value)\n }\n\n const options: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const schemeOptions = sanitizeSchemeOptions(Reflect.get(value, scheme))\n\n if (Object.keys(schemeOptions).length > 0) options[scheme] = schemeOptions\n }\n\n return options\n}\n\nfunction isReservedSlug(slug: string): boolean {\n return slug === CONFIG_SLUG || presets.some((preset) => preset.slug === slug)\n}\n\nfunction sanitizeCustomTheme(value: unknown): CustomTheme | null {\n if (!value || typeof value !== 'object') return null\n\n const slug: unknown = Reflect.get(value, 'slug')\n\n if (typeof slug !== 'string' || !slug || isReservedSlug(slug)) return null\n\n const options = sanitizeOptions(Reflect.get(value, 'options'))\n\n if (!options) return null\n\n const title: unknown = Reflect.get(value, 'title')\n const palette = sanitizePalette(Reflect.get(value, 'palette'))\n\n return {\n slug,\n title: displayTitle(typeof title === 'string' ? title : ''),\n options,\n ...(palette ? {palette} : {}),\n }\n}\n\nfunction sanitizePalette(value: unknown): ImagePalette | null {\n if (!value || typeof value !== 'object') return null\n\n const palette: Partial<ImagePalette> = {}\n let swatches = 0\n\n for (const key of IMAGE_PALETTE_KEYS) {\n const swatch = sanitizeColor(Reflect.get(value, key))\n\n palette[key] = swatch\n if (swatch) swatches++\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every key\n return swatches > 0 ? (palette as ImagePalette) : null\n}\n\nfunction sanitizeState(value: unknown): ThemerState {\n if (!value || typeof value !== 'object') return initialThemerState\n\n const custom: CustomTheme[] = []\n const rawCustom: unknown = Reflect.get(value, 'custom')\n\n if (Array.isArray(rawCustom)) {\n for (const item of rawCustom) {\n const theme = sanitizeCustomTheme(item)\n\n if (theme && !custom.some((existing) => existing.slug === theme.slug)) {\n custom.push(theme)\n }\n }\n }\n\n const removed: string[] = []\n const rawRemoved: unknown = Reflect.get(value, 'removed')\n\n if (Array.isArray(rawRemoved)) {\n for (const slug of rawRemoved) {\n const known =\n typeof slug === 'string' &&\n slug !== CONFIG_SLUG &&\n (presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !removed.includes(slug)) removed.push(slug)\n }\n }\n\n const order: string[] = []\n const rawOrder: unknown = Reflect.get(value, 'order')\n\n if (Array.isArray(rawOrder)) {\n for (const slug of rawOrder) {\n const known =\n typeof slug === 'string' &&\n (slug === CONFIG_SLUG ||\n presets.some((preset) => preset.slug === slug) ||\n custom.some((theme) => theme.slug === slug))\n\n if (known && !order.includes(slug)) order.push(slug)\n }\n }\n\n const rawActive: unknown = Reflect.get(value, 'active')\n\n return {active: typeof rawActive === 'string' ? rawActive : null, custom, removed, order}\n}\n\nfunction readLegacyState(): ThemerState | null {\n const raw = localStorage.getItem(LEGACY_STORAGE_KEY)\n\n if (!raw) return null\n\n const options = sanitizeOptions(JSON.parse(raw))\n\n if (!options) return null\n\n const theme = createCustomTheme(LEGACY_DRAFT_TITLE, options)\n\n return {active: theme.slug, custom: [theme], removed: [], order: []}\n}\n\n/**\n * Restores the themer state from localStorage, so the user's themes survive\n * studio reloads. Falls back to the initial state when nothing usable is\n * stored.\n *\n * @internal\n */\nexport function readStoredState(): ThemerState {\n try {\n if (typeof localStorage === 'undefined') return initialThemerState\n\n const raw = localStorage.getItem(STORAGE_KEY)\n\n if (raw) return sanitizeState(JSON.parse(raw))\n\n return readLegacyState() ?? initialThemerState\n } catch {\n return initialThemerState\n }\n}\n\n/** @internal */\nexport function writeStoredState(state: ThemerState): void {\n try {\n if (typeof localStorage === 'undefined') return\n\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\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\n/**\n * Whether the themer sidebar has been opened before, in any session — the\n * cue for the navbar to stop introducing the tool.\n *\n * @internal\n */\nexport function hasVisited(): boolean {\n try {\n return typeof localStorage !== 'undefined' && localStorage.getItem(VISITED_STORAGE_KEY) !== null\n } catch {\n return false\n }\n}\n\n/**\n * Notes that the themer sidebar has been opened, keeping the time it first\n * was.\n *\n * @internal\n */\nexport function markVisited(): void {\n try {\n if (typeof localStorage === 'undefined' || localStorage.getItem(VISITED_STORAGE_KEY) !== null) {\n return\n }\n\n localStorage.setItem(VISITED_STORAGE_KEY, new Date().toISOString())\n } catch {\n // Storage can be unavailable (e.g. private browsing) — the tool is introduced again next time\n }\n}\n","import {assign, not, or, setup, type SnapshotFrom, stateIn} from 'xstate'\n\nimport type {BuildThemeOptions} from '../theme/options'\nimport type {ImagePalette} from './imagePalette'\nimport {writeStoredState} from './storage'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n duplicateTitle,\n resolveThemes,\n type ThemerState,\n UNTITLED_THEME,\n} from './themes'\n\n/**\n * How long the layout's motions — the panel's and the split preview's, and\n * the cross-fade to another theme — are taken to last when the layout never\n * says: it reports every view transition it starts, so this only ends a\n * motion that has none (reduced motion, a browser without view transitions),\n * where the `moving` and `switching` tags have nothing to affect. Generous,\n * so that it never runs out before a slow deferred commit starts the real one.\n *\n * @internal\n */\nexport const MOTION_DURATION = 2000\n\n/** What the themer machine starts from @internal */\nexport interface ThemerInput {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The persisted state of an earlier session */\n stored: ThemerState\n}\n\n/** @internal */\nexport interface ThemerMachineContext extends ThemerState {\n baseOptions: BuildThemeOptions\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 | {type: 'sidebar.toggle'}\n | {type: 'sidebar.close'}\n /**\n * A view transition of the layout is under way — the panel or the split copy\n * arriving or leaving, the Studio giving way, taking room or cross-fading to\n * another theme — and the layout is no longer `moving` or `switching` for\n * the machine\n */\n | {type: 'layout.transitioned'}\n /**\n * Switches between showing the Studio once, in its own appearance, and\n * twice side by side, in light and dark\n */\n | {type: 'preview.toggle'}\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\nfunction themesOf(context: ThemerMachineContext) {\n return resolveThemes(context, context.baseOptions)\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 state of the themer tool: three parallel regions, whether the\n * `sidebar` is open, which `flow` it is in — picking a theme from the `list`,\n * `edit`ing one of the user's own themes, or restoring `removed` ones — and\n * whether the `preview` shows the Studio once (`single`) or in light and dark\n * side by side (`split`). The context carries the persisted state (the\n * applied theme, the user's themes and what was removed) alongside what the\n * flows need.\n *\n * The sidebar and the preview pass through `opening`/`closing` and\n * `splitting`/`unsplitting` on their way, states tagged for the UI: `panel`\n * and `split` say what shows (the panel from `opening` on, the split copy from\n * `splitting` on), `moving` that the Studio is about to give way or take room\n * — which is when, and only when, the layout lets a view transition animate\n * it. A fourth region, `theme`, is `switching` (its tag) right after an event\n * that applies another theme — picking one, adding, duplicating, importing or\n * editing one, removing or deleting the applied one — when the layout\n * cross-fades to it; edits to the applied theme's colors are not switches,\n * they follow the pointer. These states last until the layout reports its\n * transition under way — every boundary reports, the panel's and the copy's\n * entering and leaving as much as the Studio's morphing — or for\n * `MOTION_DURATION` when nothing animates.\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 // oxlint-disable-next-line no-unsafe-type-assertion -- type-level placeholder\n input: {} as ThemerInput,\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 sidebarShown: or([stateIn({sidebar: 'opening'}), stateIn({sidebar: 'open'})]),\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 // 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 // The state that survives sessions is written as the machine starts — that\n // completes the migration of what an earlier version stored — and on\n // every change to it\n persist: ({context}) => {\n const {active, custom, removed, order} = context\n\n writeStoredState({active, custom, removed, 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 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 const source = [...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: ({input}) => ({\n ...input.stored,\n baseOptions: input.baseOptions,\n editing: null,\n images: {},\n }),\n entry: 'persist',\n type: 'parallel',\n states: {\n sidebar: {\n initial: 'closed',\n states: {\n closed: {\n on: {'sidebar.toggle': 'opening'},\n },\n opening: {\n tags: ['panel', 'moving'],\n after: {[MOTION_DURATION]: 'open'},\n on: {\n 'layout.transitioned': 'open',\n 'sidebar.toggle': 'closing',\n 'sidebar.close': 'closing',\n },\n },\n open: {\n tags: ['panel'],\n on: {'sidebar.toggle': 'closing', 'sidebar.close': 'closing'},\n },\n closing: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'closed'},\n on: {'layout.transitioned': 'closed', 'sidebar.toggle': 'opening'},\n },\n },\n },\n // The split preview belongs to a sidebar session: closing the sidebar —\n // from its header or from the navbar — ends the split with it\n preview: {\n initial: 'single',\n states: {\n single: {\n on: {'preview.toggle': 'splitting'},\n },\n splitting: {\n tags: ['split', 'moving'],\n after: {[MOTION_DURATION]: 'split'},\n on: {\n 'layout.transitioned': 'split',\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n split: {\n tags: ['split'],\n on: {\n 'preview.toggle': 'unsplitting',\n 'sidebar.close': 'unsplitting',\n 'sidebar.toggle': {guard: 'sidebarShown', target: 'unsplitting'},\n },\n },\n unsplitting: {\n tags: ['moving'],\n after: {[MOTION_DURATION]: 'single'},\n on: {'layout.transitioned': 'single', 'preview.toggle': 'splitting'},\n },\n },\n },\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, nothing would report the switch over, and the\n // first edit that followed would cross-fade instead of following the\n // 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 },\n },\n switching: {\n tags: ['switching'],\n after: {[MOTION_DURATION]: 'applied'},\n on: {\n 'layout.transitioned': '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 },\n },\n },\n },\n flow: {\n initial: 'list',\n on: {\n 'theme.pick': {\n actions: [{type: 'pick', params: ({event}) => ({slug: event.slug})}, 'persist'],\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 'persist',\n ],\n },\n 'theme.duplicate': {\n target: '.edit',\n actions: [{type: 'duplicate', params: ({event}) => ({slug: event.slug})}, 'persist'],\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})}, 'persist'],\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 'persist',\n ],\n },\n 'theme.remove': {\n actions: [{type: 'remove', params: ({event}) => ({slug: event.slug})}, 'persist'],\n },\n 'theme.restore': {\n actions: [{type: 'restore', params: ({event}) => ({slug: event.slug})}, 'persist'],\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 'persist',\n ],\n },\n 'theme.reorder': {\n actions: [{type: 'reorder', params: ({event}) => ({order: event.order})}, 'persist'],\n },\n 'theme.import': {\n actions: [\n {\n type: 'import',\n params: ({event}) => ({title: event.title, options: event.options}),\n },\n 'persist',\n ],\n },\n 'flow.list': '.list',\n 'flow.removed': {\n target: '.removed',\n guard: 'hasRemovedThemes',\n },\n },\n states: {\n list: {},\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 {useSyncExternalStore} from 'react'\n\nconst QUERY = '(prefers-reduced-motion: reduce)'\n\nfunction subscribe(onChange: () => void): () => void {\n const media = matchMedia(QUERY)\n\n media.addEventListener('change', onChange)\n\n return () => media.removeEventListener('change', onChange)\n}\n\nfunction getSnapshot(): boolean {\n return matchMedia(QUERY).matches\n}\n\nfunction getServerSnapshot(): boolean {\n return false\n}\n\n/**\n * Whether the user prefers reduced motion — live, so that turning the\n * setting on while the Studio is open takes effect right away.\n *\n * @internal\n */\nexport function usePrefersReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","import {type RefObject, useEffect, useState} from 'react'\n\n/**\n * The Studio navbar's root, the `Card` that draws its bottom border — its\n * test id is stable and unique to the navbar, unlike its `data-ui` name\n *\n * @internal\n */\nexport const NAVBAR_SELECTOR = '[data-testid=\"studio-navbar\"]'\n\n/**\n * The height of the Studio navbar inside the given element, in pixels — kept\n * up to date as the navbar renders, remounts and resizes, and `null` until\n * there is one. The themer's header takes this height, so that its bottom\n * border lines up with the navbar's whatever height the Studio version, its\n * breakpoint or a custom navbar gives it.\n *\n * @internal\n */\nexport function useStudioNavbarHeight(ref: RefObject<HTMLElement | null>): number | null {\n const [height, setHeight] = useState<number | null>(null)\n\n useEffect(() => {\n const root = ref.current\n\n if (!root) return undefined\n\n let navbar: Element | null = null\n const resizeObserver = new ResizeObserver(() => {\n if (navbar) setHeight(navbar.getBoundingClientRect().height)\n })\n\n // The navbar can render after the layout (the Studio suspends while it\n // loads) and remount (switching workspaces), so it is looked up again\n // whenever the Studio's DOM changes — cheap, as it sits near the top\n const observeNavbar = () => {\n const next = root.querySelector(NAVBAR_SELECTOR)\n\n if (next === navbar) return\n\n if (navbar) resizeObserver.unobserve(navbar)\n\n navbar = next\n\n if (navbar) {\n resizeObserver.observe(navbar)\n setHeight(navbar.getBoundingClientRect().height)\n } else {\n setHeight(null)\n }\n }\n\n const mutationObserver = new MutationObserver(observeNavbar)\n\n mutationObserver.observe(root, {childList: true, subtree: true})\n observeNavbar()\n\n return () => {\n mutationObserver.disconnect()\n resizeObserver.disconnect()\n }\n }, [ref])\n\n return height\n}\n","import {addTransitionType, startTransition, useDeferredValue, useEffect, useState} from 'react'\n\n/**\n * `useDeferredValue` with a transition type: the returned value lags behind\n * `value` like a deferred one — rendered in a transition, which React\n * animates — and the view transition React starts for it carries the given\n * type (`document.startViewTransition({types})`), so a stylesheet tells it\n * from any other transition on the page through\n * `:active-view-transition-type()`.\n *\n * React only takes types from `startTransition`, and a deferred render is\n * not one. So while the deferred value lags, a transition of the type joins\n * it, with a state update of its own for the type to belong to: React renders\n * every pending transition in one go, and the commit — the view transition\n * — carries the types of all of them. The value itself stays with\n * `useDeferredValue`, which reads the latest `value` as the deferred render\n * runs: should `value` change back before that render commits, nothing\n * changes and nothing animates — a queued state update would commit the\n * stale value and animate it, only to animate back.\n *\n * @internal\n */\nexport function useTypedDeferredValue<T>(value: T, type: string): T {\n const deferred = useDeferredValue(value)\n const [, setTransitions] = useState(0)\n\n useEffect(() => {\n if (Object.is(deferred, value)) return\n\n startTransition(() => {\n addTransitionType(type)\n setTransitions((count) => count + 1)\n })\n }, [deferred, type, value])\n\n return deferred\n}\n","import {\n createViewTransition,\n globalStyle,\n keyframes,\n style,\n styleVariants,\n} from '@vanilla-extract/css'\n\nimport {sidebarTransition} from './ResizableSidebar.css'\n\n/**\n * The view transition classes of the split preview — what React puts in\n * `view-transition-class`, which the pseudo-element rules below select on.\n * Scoped identifiers, so nothing else on the page can mean the same.\n */\nexport const splitTransitionClasses = {\n /** The Studio the user was looking at cross-fades between its two widths */\n resize: createViewTransition('splitResize'),\n /** The Studio and the panel cross-fade to another theme, in place */\n crossfade: createViewTransition('themeCrossfade'),\n /** The split copy slides in from the side, or out to it */\n slideIn: createViewTransition('splitSlideIn'),\n slideOut: createViewTransition('splitSlideOut'),\n /** The split copy drops in from the top, or out to it, where the copies stack */\n dropIn: createViewTransition('splitDropIn'),\n dropOut: createViewTransition('splitDropOut'),\n}\n\n/** The view transition classes of the panel, which slides in from its edge and out to it */\nexport const panelTransitionClasses = {\n slideIn: createViewTransition('panelSlideIn'),\n slideOut: createViewTransition('panelSlideOut'),\n}\n\n/**\n * The transition type of the layout's motions — what `ThemerLayout` adds to\n * every transition it starts, and React passes on to the view transition.\n * `:active-view-transition-type()` tells the layout's transitions from any\n * other on the page while they run.\n */\nexport const layoutTransitionType = 'sanity-themer'\n\n/** How long the layout's motions take, and how they ease — every group they move shares these */\nconst motion = {\n animationDuration: '320ms',\n animationTimingFunction: 'cubic-bezier(0.2, 0, 0, 1)',\n}\n\n/** The layout's `Flex` positions the sidebar overlay on small screens */\nexport const layout = style({\n position: 'relative',\n})\n\n/** A copy of the Studio in a color scheme of its own, for `color-scheme` */\nexport const studioScheme = styleVariants({\n light: {colorScheme: 'light'},\n dark: {colorScheme: 'dark'},\n})\n\nconst slideIn = keyframes({from: {transform: 'translateX(-100%)'}})\nconst slideOut = keyframes({to: {transform: 'translateX(-100%)'}})\nconst dropIn = keyframes({from: {transform: 'translateY(-100%)'}})\nconst dropOut = keyframes({to: {transform: 'translateY(-100%)'}})\nconst slideInFromEnd = keyframes({from: {transform: 'translateX(100%)'}})\nconst slideOutToEnd = keyframes({to: {transform: 'translateX(100%)'}})\n\n/**\n * How the panel and the split preview animate, through React's view\n * transitions: the panel slides in from its edge and the split copy from the\n * far one — a transform, nothing fades — and out the same way; the Studio the\n * user was looking at cross-fades between its two widths, its old and new\n * snapshots stretched to the group's box so it keeps its height. Everything\n * shares one duration and easing, so the edge a panel slides in on and the\n * edge the Studio gives way with stay together.\n */\nglobalStyle(\n [\n `::view-transition-group(.${splitTransitionClasses.resize})`,\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.slideIn})`,\n `::view-transition-old(.${splitTransitionClasses.slideOut})`,\n `::view-transition-new(.${splitTransitionClasses.dropIn})`,\n `::view-transition-old(.${splitTransitionClasses.dropOut})`,\n `::view-transition-new(.${panelTransitionClasses.slideIn})`,\n `::view-transition-old(.${panelTransitionClasses.slideOut})`,\n `::view-transition-old(.${splitTransitionClasses.crossfade})`,\n `::view-transition-new(.${splitTransitionClasses.crossfade})`,\n ].join(', '),\n motion,\n)\n\n/**\n * Whatever else on the page has a `view-transition-name` of its own — an\n * avatar the Studio names so it moves as one piece instead of stretching\n * along with the navbar's snapshot, say; names the Studio makes up as it\n * renders, so the split copy's elements have names of their own — takes part\n * in the layout's transitions too, as a group of its own that the browser\n * moves and cross-fades from where it was to where it ends up: at its default\n * quarter of a second and `ease`, out of step with the navbar it sits in.\n * While one of the layout's transitions runs, every group keeps the layout's\n * time instead. Only then: the Studio's own transitions are none of the\n * layout's business and keep their own — and the layout's own groups, which\n * the rules above set to the same time, are no different for it.\n */\nglobalStyle(\n [\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-group(*)`,\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-old(*)`,\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-new(*)`,\n ].join(', '),\n motion,\n)\n\nglobalStyle(\n [\n `::view-transition-old(.${splitTransitionClasses.resize})`,\n `::view-transition-new(.${splitTransitionClasses.resize})`,\n ].join(', '),\n {\n inlineSize: '100%',\n blockSize: '100%',\n objectFit: 'fill',\n },\n)\n\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.slideIn})`, {animationName: slideIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.slideOut})`, {\n animationName: slideOut,\n})\nglobalStyle(`::view-transition-new(.${splitTransitionClasses.dropIn})`, {animationName: dropIn})\nglobalStyle(`::view-transition-old(.${splitTransitionClasses.dropOut})`, {animationName: dropOut})\nglobalStyle(`::view-transition-new(.${panelTransitionClasses.slideIn})`, {\n animationName: slideInFromEnd,\n})\nglobalStyle(`::view-transition-old(.${panelTransitionClasses.slideOut})`, {\n animationName: slideOutToEnd,\n})\n\n/**\n * While the split preview toggles, the sidebar — a group of its own, which\n * `ResizableSidebar` names — does not animate at all: its new snapshot simply\n * shows, stacked above the Studio copies where it covers the Studio (groups of\n * elements that only exist in the new state, the arriving copy, would\n * otherwise be stacked last, over it). While the panel itself opens or closes,\n * or cross-fades to another theme, React names it instead, and the rules\n * above apply.\n *\n */\nglobalStyle(`::view-transition-group(${sidebarTransition})`, {zIndex: 1})\nglobalStyle(\n [\n `::view-transition-group(${sidebarTransition})`,\n `::view-transition-image-pair(${sidebarTransition})`,\n `::view-transition-old(${sidebarTransition})`,\n `::view-transition-new(${sidebarTransition})`,\n ].join(', '),\n {animation: 'none'},\n)\n\n/**\n * Someone who prefers reduced motion sees no motion here: the layout does not\n * start these transitions in the first place (see `ThemerLayout`), and should\n * one run anyway — the preference changed after the layout read it, the\n * Studio ran one of its own over these groups — none of their animations does\n * anything, nor those of any other group the layout's transition moves: the\n * new state simply shows.\n */\nglobalStyle(\n ['group', 'image-pair', 'old', 'new']\n .flatMap((part) => {\n const groups = [\n ...Object.values(splitTransitionClasses).map((className) => `.${className}`),\n ...Object.values(panelTransitionClasses).map((className) => `.${className}`),\n sidebarTransition,\n ]\n const selectors = groups.map((group) => `::view-transition-${part}(${group})`)\n\n selectors.push(\n `:root:active-view-transition-type(${layoutTransitionType})::view-transition-${part}(*)`,\n )\n\n return selectors\n })\n .join(', '),\n {\n '@media': {\n '(prefers-reduced-motion: reduce)': {\n animation: 'none',\n },\n },\n },\n)\n","import {Card, Flex, ThemeProvider, useMediaIndex} from '@sanity/ui'\nimport {type RootTheme, type ThemeColorSchemeKey} from '@sanity/ui/theme'\nimport {useActor, useSelector} from '@xstate/react'\nimport {Activity, lazy, useEffect, useMemo, useRef, useState, ViewTransition} from 'react'\nimport {type LayoutProps, useColorSchemeValue} from 'sanity'\n\nimport {buildTheme} from '../theme/buildTheme'\nimport type {BuildThemeOptions} from '../theme/options'\nimport {ThemerContext, type ThemerContextValue, type ThemerView} from './context'\nimport {selectStoredState, type ThemerInput, themerMachine, type ThemerSnapshot} from './machine'\nimport {readStoredState} from './storage'\nimport {resolveThemes, type ThemerState} from './themes'\nimport {usePrefersReducedMotion} from './usePrefersReducedMotion'\nimport {useStudioNavbarHeight} from './useStudioNavbarHeight'\nimport {useTypedDeferredValue} from './useTypedDeferredValue'\n\nimport {\n layout,\n layoutTransitionType,\n panelTransitionClasses,\n splitTransitionClasses,\n studioScheme,\n} from './ThemerLayout.css'\n\n/**\n * Below this media index the Studio collapses its navbar into a drawer, and\n * the themer follows suit: the sidebar covers the Studio instead of standing\n * next to it, and the split preview stacks instead of sitting side by side.\n */\nconst MOBILE_MEDIA_INDEX = 1\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 deferred render: that keeps the Studio as\n * it was while the code loads, with the navbar toggle showing the deferred\n * value as pending, 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\nfunction sameStoredState(a: ThemerState, b: ThemerState): boolean {\n return (\n a.active === b.active && a.custom === b.custom && a.removed === b.removed && a.order === b.order\n )\n}\n\nfunction selectView(snapshot: ThemerSnapshot): ThemerView {\n const {editing} = snapshot.context\n\n if (snapshot.matches({flow: 'edit'}) && editing) {\n return {name: 'edit', slug: editing.slug, focusTitle: editing.focusTitle}\n }\n\n if (snapshot.matches({flow: 'removed'})) {\n return {name: 'removed'}\n }\n\n return {name: 'list'}\n}\n\nfunction sameView(a: ThemerView, b: ThemerView): boolean {\n if (a.name !== b.name) return false\n\n return (\n a.name !== 'edit' || b.name !== 'edit' || (a.slug === b.slug && a.focusTitle === b.focusTitle)\n )\n}\n\n/**\n * Wraps the whole Studio so that the theme picked in the themer sidebar\n * applies everywhere while the user browses around, runs the themer machine\n * that the navbar toggle and the sidebar share — the user's themes, which one\n * is applied, which flow the sidebar is in and how the Studio is previewed —\n * and renders the sidebar next to the Studio. The sidebar sits at the\n * `layout` level rather than in `activeToolLayout` because the split preview\n * renders the Studio twice, and the sidebar must not come along.\n *\n * The Studio next to the sidebar always follows the appearance setting\n * (light/dark/system) and the picked theme like any other theme would. The\n * split preview adds a second copy in the opposite scheme on the far side —\n * or on top, on small screens — through React's view transitions (React\n * 19.3), styled in `ThemerLayout.css.ts`.\n *\n * The machine says what shows and when the layout is in motion (its `panel`,\n * `split` and `moving` tags); the layout defers what shows, which puts the\n * panel's and the copy's mounts in a transition — what lets React animate\n * them — and picks the view transition classes from the tags.\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 * `ThemerLayout.css.ts`).\n *\n * @internal\n */\nexport function ThemerLayout(props: LayoutProps & {baseOptions: BuildThemeOptions}) {\n const {baseOptions, ...layoutProps} = props\n const [input] = useState<ThemerInput>(() => ({baseOptions, stored: readStoredState()}))\n const [snapshot, send, actorRef] = useActor(themerMachine, {input})\n const stored = useSelector(actorRef, selectStoredState, sameStoredState)\n const view = useSelector(actorRef, selectView, sameView)\n const open = snapshot.hasTag('panel')\n const split = snapshot.hasTag('split')\n const moving = snapshot.hasTag('moving')\n const switching = snapshot.hasTag('switching')\n const {images} = snapshot.context\n const mobile = useMediaIndex() <= MOBILE_MEDIA_INDEX\n const studioRef = useRef<HTMLDivElement | null>(null)\n const navbarHeight = useStudioNavbarHeight(studioRef)\n\n // The machine publishes synchronously, which React does not animate. What\n // shows is deferred: that renders the panel's and the copy's mounts in a\n // transition — once the sidebar's own changes (the toggle's pressed state)\n // have committed, so nothing in the sidebar changes while it runs. The\n // transition carries the layout's type, which tells the stylesheet that the\n // view transition running is the layout's. Someone who prefers reduced\n // motion gets no view transition at all: nothing is deferred, so there is\n // no transition render for React to animate, no boundary has a class, and\n // what shows changes along with the machine — and should one run anyway,\n // the stylesheet gives its animations nothing to do\n const reduceMotion = usePrefersReducedMotion()\n const deferredOpen = useTypedDeferredValue(reduceMotion ? null : open, layoutTransitionType)\n const deferredSplit = useTypedDeferredValue(reduceMotion ? null : split, layoutTransitionType)\n const shownOpen = deferredOpen ?? open\n const shownSplit = deferredSplit ?? split\n\n // The sidebar's content mounts the first time it opens — which is when its\n // code loads — and stays mounted from then on. It goes by a deferred copy\n // of whether it should be there, deferred even for reduced motion: the\n // render that loads the code must never be an urgent one. With motion it\n // arrives with the panel's own deferred reveal; without, in a panel that\n // already shows, as an update no view transition animates. The two copies\n // differ while the code loads, which the navbar toggle shows.\n //\n // It should be there while open, or once it has been: a sidebar closed\n // again while its code loads is not mounted into the hidden panel, where\n // the code could still be loading as a reduced-motion reopen — which is\n // urgent — reveals it, and the Studio would give way to its loading screen\n const [panelShown, setPanelShown] = useState(false)\n const panelWanted = open || panelShown\n const panelMounted = useTypedDeferredValue(panelWanted, layoutTransitionType)\n if (panelMounted && !panelShown) setPanelShown(true)\n const loading = panelWanted !== panelMounted\n\n const {themes, removed, active} = useMemo(\n () => resolveThemes(stored, baseOptions),\n [stored, baseOptions],\n )\n\n // The theme identity must be stable between renders: it feeds the\n // styled-components theme context for the whole Studio, and rebuilding it\n // would re-render everything. Keying on the options object keeps it stable\n // across unrelated changes, like renaming a theme.\n const activeOptions = active.source === 'config' ? null : active.options\n const theme = useMemo(\n () => (activeOptions === null ? null : buildTheme(activeOptions)),\n [activeOptions],\n )\n\n // The applied theme is deferred the same way, and the Studio and the panel\n // cross-fade to it while the machine says a theme is being switched to —\n // edits to the applied theme's colors render deferred too, which keeps the\n // pickers responsive, but with nothing to animate them they simply show\n const deferredTheme = useTypedDeferredValue(\n reduceMotion ? undefined : theme,\n layoutTransitionType,\n )\n const shownTheme = deferredTheme === undefined ? theme : deferredTheme\n\n // The Studio paints the body with its configured theme from above this\n // layout, and Safari tints its chrome from the body — so the applied theme\n // has to reach the body too, in the scheme the Studio is showing\n const scheme = useColorSchemeValue()\n const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\n\n useEffect(() => {\n const background = shownTheme?.v2?.color[scheme].default.bg\n\n if (background === undefined) return undefined\n\n const {style} = document.body\n const previous = style.backgroundColor\n\n style.backgroundColor = background\n\n return () => {\n style.backgroundColor = previous\n }\n }, [scheme, shownTheme])\n\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 const context = useMemo<ThemerContextValue>(\n () => ({\n baseOptions,\n themes,\n removed,\n active,\n images,\n view,\n open,\n loading,\n split,\n mobile,\n navbarHeight,\n send,\n }),\n [\n active,\n baseOptions,\n images,\n loading,\n mobile,\n navbarHeight,\n open,\n removed,\n send,\n split,\n themes,\n view,\n ],\n )\n\n const studio = layoutProps.renderDefault(layoutProps)\n\n // Every view transition of the layout tells the machine its motion is under way\n const transitioned = () => send({type: 'layout.transitioned'})\n\n // What the view transitions do this render, from what the machine says\n const classes = reduceMotion\n ? {\n copyEnter: 'none',\n copyExit: 'none',\n panelEnter: 'none',\n panelExit: 'none',\n update: 'none',\n studioUpdate: 'none',\n }\n : {\n copyEnter: mobile ? splitTransitionClasses.dropIn : splitTransitionClasses.slideIn,\n copyExit: mobile ? splitTransitionClasses.dropOut : splitTransitionClasses.slideOut,\n panelEnter: panelTransitionClasses.slideIn,\n panelExit: panelTransitionClasses.slideOut,\n // The copy and the panel cross-fade to another theme, in place\n update: switching ? splitTransitionClasses.crossfade : 'none',\n // The Studio too — unless it is giving way or taking room, which cross-fades as it goes\n studioUpdate: moving\n ? splitTransitionClasses.resize\n : switching\n ? splitTransitionClasses.crossfade\n : 'none',\n }\n\n return (\n <ThemerContext.Provider value={context}>\n <Flex className={layout} direction={mobile ? 'column' : 'row'} height=\"fill\" sizing=\"border\">\n {/* The opposite scheme comes first — on the far side of the sidebar,\n or on top on small screens — so the Studio the user was looking at\n stays where it is, mounted, in its own scheme. A whole second\n Studio is too costly to keep around (its styled-components alone\n insert CSS as they render), so it only exists while the sidebar is\n open — hidden until the split shows, warmed up for the transition */}\n {shownOpen && (\n <Activity mode={shownSplit ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"opposite\"\n enter={classes.copyEnter}\n exit={classes.copyExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <StudioPreview\n borderBottom={mobile}\n borderRight={!mobile}\n scheme={oppositeScheme}\n theme={shownTheme}\n >\n {studio}\n </StudioPreview>\n </ViewTransition>\n </Activity>\n )}\n {/* The Studio gives way and takes room while the machine says the\n layout is moving, and cross-fades while it says a theme is being\n switched to — the Studio updates in transitions of its own all the\n time, and none of those may animate it. Once a transition is under\n way the machine hears of it, and the Studio is its own again before\n the next commit */}\n <ViewTransition key=\"primary\" onUpdate={transitioned} update={classes.studioUpdate}>\n <StudioPreview ref={studioRef} theme={shownTheme}>\n {studio}\n </StudioPreview>\n </ViewTransition>\n\n {/* The sidebar is small and cheap: once it has opened it stays\n mounted, hidden while closed, keeping its state and ready to show.\n Until then its content is left out — hidden, it would be\n pre-rendered, and its code loaded, as soon as the Studio renders */}\n <Activity mode={shownOpen ? 'visible' : 'hidden'}>\n <ViewTransition\n key=\"panel\"\n enter={classes.panelEnter}\n exit={classes.panelExit}\n onEnter={transitioned}\n onExit={transitioned}\n onUpdate={transitioned}\n update={classes.update}\n >\n <ThemeProvider theme={shownTheme ?? undefined}>\n {panelMounted && <ResizableSidebar overlay={mobile} />}\n </ThemeProvider>\n </ViewTransition>\n </Activity>\n </Flex>\n </ThemerContext.Provider>\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 configured theme (`null`) goes through\n * the provider too, inheriting the Studio's, so picking a theme swaps it\n * instead of remounting the Studio under a new provider.\n *\n * The two copies of the split preview share the router, the document store\n * and every other provider above the layout — only the scheme differs — so\n * they stay in sync while navigating. The `color-scheme` of a forced scheme\n * keeps native form controls and scrollbars in step with it.\n */\nfunction StudioPreview(props: {\n borderBottom?: boolean\n borderRight?: boolean\n children: React.ReactNode\n ref?: React.Ref<HTMLDivElement>\n scheme?: ThemeColorSchemeKey\n theme: RootTheme | null\n}) {\n const {borderBottom, borderRight, children, ref, scheme, theme} = props\n\n return (\n <ThemeProvider scheme={scheme} theme={theme ?? undefined}>\n <Card\n borderBottom={borderBottom}\n borderRight={borderRight}\n className={scheme ? studioScheme[scheme] : undefined}\n flex={1}\n height=\"fill\"\n overflow=\"hidden\"\n ref={ref}\n >\n {children}\n </Card>\n </ThemeProvider>\n )\n}\n","import {blue, cyan, green, magenta, orange, purple, red, yellow} from '@sanity/color'\n\n/**\n * The spokes of the color wheel icon (`ColorWheelIcon` from `@sanity/icons`)\n * cut its ring into this many slices, one of them centered on 12 o'clock\n */\nexport const SLICE_COUNT = 8\n\n/**\n * The colors the slices fill with, clockwise from 12 o'clock: the palette's\n * hues in rainbow order, like a real color swatch\n */\nexport const SLICE_COLORS: readonly string[] = [\n red[500].hex,\n orange[500].hex,\n yellow[400].hex,\n green[500].hex,\n cyan[500].hex,\n blue[500].hex,\n purple[500].hex,\n magenta[500].hex,\n]\n\n/**\n * The animation plays in acts of this length, in seconds. In the first act\n * the top half of the slices pop in, one at a time like the segments of a\n * classic spinner; in the second the wheel spins up while the bottom half pop\n * in; in the third the colors pop out again as the spin's speed carries over\n * into the clearing.\n */\nconst ACT_DURATION = 0.55\n\n/**\n * The clearing would take a whole act to come to rest, but its last slice\n * pops out as the clearing reaches that slice's leading edge — with nothing\n * left to show, the animation ends there\n */\nconst CLEARING_ACTS = 1 - Math.cbrt(1 / SLICE_COUNT)\n\nconst TOTAL_ACTS = 2 + CLEARING_ACTS\n\n/** How long the whole animation takes, in seconds */\nexport const ANIMATION_DURATION = ACT_DURATION * TOTAL_ACTS\n\nfunction actAt(progress: number): number {\n return progress * TOTAL_ACTS\n}\n\n/**\n * How far the wheel has turned at `progress` (0–1), in degrees.\n *\n * It stands still while the top half of the slices pop in. As the 6 o'clock\n * slice pops in it starts spinning — one full turn with a cubic ease-in, from\n * rest to top speed as the bottom half pop in — and it stands still again the\n * moment it is back upright: the clearing of {@link isSliceFilled} takes off\n * at the speed the spin reached, as if the wheel kept turning and eased out\n * over a second turn, so the two read as one movement.\n */\nexport function wheelRotation(progress: number): number {\n const spin = actAt(progress) - 1\n\n if (spin <= 0 || spin >= 1) return 0\n\n return 360 * spin ** 3\n}\n\n/**\n * Whether slice `index` (clockwise from 12 o'clock) shows its color at\n * `progress` (0–1). A slice pops in whole as the fill reaches its leading\n * edge, and pops out whole as the clearing does — stepped, never partial.\n *\n * The fill goes clockwise at a steady pace over the first two acts, half of\n * the slices in each. The clearing follows in the third act, the same way\n * round, on a cubic ease-out that starts at the speed the spin of\n * {@link wheelRotation} ends with: the slices at first pop out as quickly as\n * they were passing by, then ever more slowly.\n */\nexport function isSliceFilled(progress: number, index: number): boolean {\n const act = actAt(progress)\n const filling = (act * SLICE_COUNT) / 2\n\n if (filling <= index) return false\n\n const clearing = act - 2\n\n if (clearing <= 0) return true\n\n return (1 - (1 - clearing) ** 3) * SLICE_COUNT < index\n}\n","import {usePrefersReducedMotion} from '@sanity/ui'\nimport {motion, MotionValue, useTransform} from 'motion/react'\n\nimport {isSliceFilled, SLICE_COLORS, SLICE_COUNT, wheelRotation} from './colorWheel'\n\n/**\n * The geometry of `ColorWheelIcon` from `@sanity/icons`: a ring between two\n * circles around the center of a 25×25 viewBox, cut into eight slices by the\n * spokes of its outline path\n */\nconst CENTER = 12.5\nconst OUTER_RADIUS = 8\nconst INNER_RADIUS = 3.5\nconst OUTLINE =\n 'M9.43853 5.10896L11.1606 9.26642M13.8394 15.7336L15.5615 19.891M15.7336 11.1606L19.891 9.43853M9.26642 13.8394L5.10896 15.5615M5.3139 9.52342L9.23359 11.147M15.7664 13.853L19.6861 15.4766M13.853 9.23359L15.4766 5.3139M9.52342 19.6861L11.147 15.7664M20.5 12.5C20.5 16.9183 16.9183 20.5 12.5 20.5C8.08172 20.5 4.5 16.9183 4.5 12.5C4.5 8.08172 8.08172 4.5 12.5 4.5C16.9183 4.5 20.5 8.08172 20.5 12.5ZM16 12.5C16 14.433 14.433 16 12.5 16C10.567 16 9 14.433 9 12.5C9 10.567 10.567 9 12.5 9C14.433 9 16 10.567 16 12.5Z'\n\nconst SLICE_ANGLE = 360 / SLICE_COUNT\n\n/** A point on a circle around the center, at `degrees` clockwise from 3 o'clock */\nfunction point(radius: number, degrees: number): string {\n const radians = (degrees * Math.PI) / 180\n\n return `${CENTER + radius * Math.cos(radians)} ${CENTER + radius * Math.sin(radians)}`\n}\n\n/**\n * The part of the ring a slice colors in: between its two spokes, from the\n * inner circle out to the outer. The 12 o'clock slice is centered on 12, so\n * it starts half a slice before it.\n */\nfunction slicePath(index: number): string {\n const start = index * SLICE_ANGLE - SLICE_ANGLE / 2 - 90\n const end = start + SLICE_ANGLE\n\n return [\n `M${point(OUTER_RADIUS, start)}`,\n `A${OUTER_RADIUS} ${OUTER_RADIUS} 0 0 1 ${point(OUTER_RADIUS, end)}`,\n `L${point(INNER_RADIUS, end)}`,\n `A${INNER_RADIUS} ${INNER_RADIUS} 0 0 0 ${point(INNER_RADIUS, start)}`,\n 'Z',\n ].join('')\n}\n\nconst SLICE_PATHS = SLICE_COLORS.map((_, index) => slicePath(index))\n\n/**\n * A filled shape, not a stroked one: the icon styles of `@sanity/ui` force\n * `vector-effect: non-scaling-stroke` on every path in an icon, which fills\n * are none the wiser about\n */\nfunction Slice(props: {index: number; progress: MotionValue<number>}) {\n const {index, progress} = props\n const opacity = useTransform(progress, (value) => (isSliceFilled(value, index) ? 1 : 0))\n\n return <motion.path d={SLICE_PATHS[index]} fill={SLICE_COLORS[index]} style={{opacity}} />\n}\n\n/**\n * The `ColorWheelIcon` of `@sanity/icons`, with an animation driven by\n * `progress` (0–1): the slices pop in with color one at a time, clockwise\n * from 12 o'clock like the segments of a classic spinner, the wheel spins once\n * as the 6 o'clock slice pops in, and the colors pop out the same way round,\n * carrying the spin's speed to a rest (see `colorWheel.ts`). At rest — at\n * either end — it looks exactly like the icon it stands in for. With\n * `prefers-reduced-motion: reduce` the wheel keeps still; only the colors come\n * and go.\n *\n * @internal\n */\nexport function AnimatedColorWheelIcon(props: {progress: MotionValue<number>}) {\n const {progress} = props\n const prefersReducedMotion = usePrefersReducedMotion()\n const rotate = useTransform(progress, (value) =>\n prefersReducedMotion ? 0 : wheelRotation(value),\n )\n\n return (\n <motion.svg\n data-sanity-icon=\"color-wheel\"\n fill=\"none\"\n height=\"1em\"\n style={{rotate}}\n viewBox=\"0 0 25 25\"\n width=\"1em\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n {SLICE_COLORS.map((color, index) => (\n <Slice index={index} key={color} progress={progress} />\n ))}\n <path d={OUTLINE} stroke=\"currentColor\" strokeLinejoin=\"round\" strokeWidth={1.2} />\n </motion.svg>\n )\n}\n","import {ColorWheelIcon} from '@sanity/icons/ColorWheel'\nimport {Button, Text} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {animate, useMotionValue} from 'motion/react'\nimport {useCallback, useEffect, useState} from 'react'\nimport {type NavbarProps} from 'sanity'\n\nimport {AnimatedColorWheelIcon} from './AnimatedColorWheelIcon'\nimport {ANIMATION_DURATION} from './colorWheel'\nimport {type ThemerContextValue, useThemer} from './context'\nimport {hasVisited, markVisited} from './storage'\nimport {NAVBAR_SELECTOR} from './useStudioNavbarHeight'\n\n/**\n * Starts loading the sidebar's code as the toggle is about to be pressed —\n * the same import `React.lazy` makes, which then waits for this one\n */\nfunction preloadSidebar() {\n // A failed load is for the lazy import to report, as the sidebar opens\n import('./ResizableSidebar').catch(() => {})\n}\n\n/**\n * Opens or closes the sidebar — and notes the visit, which is what stops the\n * navbar from introducing the tool\n */\nfunction toggleSidebar(send: ThemerContextValue['send']) {\n markVisited()\n send({type: 'sidebar.toggle'})\n}\n\n/**\n * Whether the navbar has had its one chance per page load to introduce the\n * tool. Module state rather than component state on purpose: the button\n * remounts with the navbar (switching workspaces, crossing the narrow-screen\n * breakpoint) and renders once per Studio copy in the split preview, and none\n * of those should earn another introduction — only loading the page again does.\n */\nlet introduced = false\n\n/**\n * The topbar toggle, with an icon that plays its color wheel animation: on\n * hovering the button, every time; on hovering the navbar it sits in, once —\n * a \"hey, look, new tool!\" for anyone who has never opened the sidebar, which\n * the visit noted by {@link toggleSidebar} ends for good; and lap after lap\n * while the sidebar's code loads, the way a spinner would.\n */\nfunction ThemerNavbarButton() {\n const {open, loading, send} = useThemer()\n const progress = useMotionValue(0)\n // The button element comes through state rather than a ref: the tooltip\n // wraps the button and hands the element on once it has rendered, which is\n // after a plain ref would have been read\n const [button, setButton] = useState<HTMLButtonElement | null>(null)\n\n const play = useCallback(() => {\n // A run in progress plays out — hovering again does not cut it short\n if (progress.isAnimating()) return\n\n animate(progress, [0, 1], {duration: ANIMATION_DURATION, ease: 'linear'})\n }, [progress])\n\n // Laps from wherever a hover's run has got to, so the wheel never jumps,\n // and the lap in progress as the code arrives plays out: at rest, the wheel\n // looks like the icon again\n useEffect(() => {\n if (!loading) return undefined\n\n let looping = true\n const lap = (from: number) => {\n animate(progress, [from, 1], {\n duration: (1 - from) * ANIMATION_DURATION,\n ease: 'linear',\n onComplete: () => {\n if (looping) lap(0)\n },\n })\n }\n\n lap(progress.get())\n\n return () => {\n looping = false\n }\n }, [loading, progress])\n\n useEffect(() => () => progress.stop(), [progress])\n\n useEffect(() => {\n const navbar = button?.closest(NAVBAR_SELECTOR)\n\n if (!navbar || introduced) return undefined\n\n const introduce = () => {\n // Another navbar (the split preview's) may have had the chance meanwhile\n if (introduced) return\n\n introduced = true\n // Checked as the pointer comes in, not up front: a click in between\n // (on the button itself) counts\n if (!hasVisited()) play()\n }\n\n navbar.addEventListener('mouseenter', introduce, {once: true})\n\n return () => navbar.removeEventListener('mouseenter', introduce)\n }, [button, play])\n\n return (\n <Tooltip animate content={<Text size={1}>Themer</Text>} portal>\n <Button\n aria-busy={loading}\n aria-label=\"Themer\"\n // The wheel itself shows the load, where the `loading` prop would\n // cover it with a spinner\n disabled={loading}\n icon={<AnimatedColorWheelIcon progress={progress} />}\n mode=\"bleed\"\n onClick={() => toggleSidebar(send)}\n onFocus={preloadSidebar}\n onMouseEnter={() => {\n preloadSidebar()\n play()\n }}\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 ref={setButton}\n selected={open}\n />\n </Tooltip>\n )\n}\n\n/**\n * Adds the toggle that opens and closes the themer sidebar to the Studio\n * navbar — an icon button with a tooltip in the top bar (like the Tasks\n * toggle), and a regular titled action in the narrow-screen sidebar menu.\n *\n * In the split preview every Studio copy renders its own navbar, so the\n * toggle shows up in both — they drive the same sidebar.\n *\n * @internal\n */\nexport function ThemerNavbar(props: NavbarProps) {\n const {open, send} = useThemer()\n\n return props.renderDefault({\n ...props,\n __internal_actions: [\n ...(props.__internal_actions ?? []),\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 and its once-only introduction\n render: ThemerNavbarButton,\n },\n {\n icon: ColorWheelIcon,\n location: 'sidebar',\n name: 'themer-sidebar',\n onAction: () => toggleSidebar(send),\n selected: open,\n title: 'Themer',\n },\n ],\n })\n}\n","import {definePlugin, type LayoutProps} from 'sanity'\n\nimport type {BuildThemeOptions} from '../theme/options'\nimport {ThemerLayout} from './ThemerLayout'\nimport {ThemerNavbar} from './ThemerNavbar'\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 * The `buildTheme` options that the Studio's configured theme was generated\n * from — the themer starts editing from these, so pass the same object that\n * the `theme` in the Studio config uses:\n *\n * ```ts\n * const config: BuildThemeOptions = {light: {accent: '#1cb485'}, dark: {accent: '#22fca8'}}\n *\n * export default defineConfig({\n * theme: buildTheme(config),\n * plugins: [themerTool({config})],\n * })\n * ```\n */\n config?: BuildThemeOptions\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 // No options generate the stock theme, which is what a Studio without a\n // `theme` in its config gets\n const baseOptions = options?.config ?? {}\n\n function ThemerLayoutWithOptions(props: LayoutProps) {\n return <ThemerLayout {...props} baseOptions={baseOptions} />\n }\n\n return {\n name: '@sanity/themer/tool',\n studio: {\n components: {\n layout: ThemerLayoutWithOptions,\n navbar: ThemerNavbar,\n },\n },\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>","<ColorWheelIcon />","<Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>","<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\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\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\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\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>","props.renderDefault({\n ...props,\n __internal_actions: [...(props.__internal_actions ?? []), button],\n })","props.__internal_actions","props.__internal_actions ?? []","__internal_actions","{\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'>","() => {\n let cancel: any\n function cancelOrClear() {\n if ('cancelIdleCallback' in window) {\n cancelIdleCallback(cancel)\n } else {\n clearTimeout(cancel)\n }\n }\n\n // @TODO use a selector or similar to only subscribe to changes that are relevant to the persisted snapshot\n // @TODO use WebLocks and BroadcastChannel to ensure the snapshot is persisted in a consistent way cross tabs\n // @TODO actually only run this when the document is visible or has lock or something?\n const subscription = actorRef.subscribe(() => {\n cancelOrClear()\n const callback = () => {\n const persistedSnapshot = actorRef.getPersistedSnapshot()\n writePersistedSnapshot(persistedSnapshot)\n }\n cancel =\n 'cancelIdleCallback' in window ? requestIdleCallback(callback) : setTimeout(callback, 1_000)\n })\n return () => {\n subscription.unsubscribe()\n cancelOrClear()\n }\n }","[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 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 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/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} from '@sanity/ui'\nimport {Tooltip} from '@sanity/ui/tooltip'\nimport {lazy, startTransition, Suspense, use, useRef, useTransition, ViewTransition} 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\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\n <Suspense fallback={<ColorWheelIcon />}>\n <AnimatedColorWheelIcon busy={busy} ref={iconRef} />\n </Suspense>\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\n// This component isn't auto identified by react compiler as a react component,\n// the 'use memo' directive opts it in\nexport function ThemerNavbar(props: NavbarProps) {\n 'use memo'\n const button = {\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 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\nexport type ToolReducerState = {\n navbarHeight: number | null\n open: boolean\n prerender: boolean\n prerenderSplitScreen: boolean\n split: boolean\n theme: BuildThemeOptions | null\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'}\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}\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 machine's state value, as persisted — only what the machine has states for restores @internal */\nconst snapshotValueSchema = v.object({\n flow: v.picklist(['list', 'edit', 'removed']),\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: v.InferOutput<typeof snapshotValueSchema>\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 switch that was under way is over,\n * and the image URLs of the last session 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 (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 /**\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`, `edit`ing one of\n * the user's own themes, or restoring `removed` ones — and, in a parallel\n * `theme` region, whether another theme was just applied. The context carries\n * the persisted state (the applied theme, the user's themes and what was\n * removed) alongside what the flows need. Whether the sidebar is open and\n * whether the Studio shows twice is not the machine's: the tool reducer in\n * `ThemerProvider` owns that, so that the navbar toggle and the sidebar can\n * change it in transitions of their own. Nor is persisting: the machine\n * never touches storage and takes no input — it starts from no themes, and a\n * session with something to pick up from restores its persisted snapshot\n * instead (see `storage.ts` and `sync.ts`), while the persisted state of\n * other tabs reaches it as a `themes.sync` event like any 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 // 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 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 {useEffect} from 'react'\n\nimport type {ThemerProps} from '#types'\n\nimport {writePersistedSnapshot} from './storage'\n\nexport function PersistActorSnapshot({actorRef}: Pick<ThemerProps, 'actorRef'>) {\n useEffect(() => {\n let cancel: any\n function cancelOrClear() {\n if ('cancelIdleCallback' in window) {\n cancelIdleCallback(cancel)\n } else {\n clearTimeout(cancel)\n }\n }\n\n // @TODO use a selector or similar to only subscribe to changes that are relevant to the persisted snapshot\n // @TODO use WebLocks and BroadcastChannel to ensure the snapshot is persisted in a consistent way cross tabs\n // @TODO actually only run this when the document is visible or has lock or something?\n const subscription = actorRef.subscribe(() => {\n cancelOrClear()\n const callback = () => {\n const persistedSnapshot = actorRef.getPersistedSnapshot()\n writePersistedSnapshot(persistedSnapshot)\n }\n cancel =\n 'cancelIdleCallback' in window ? requestIdleCallback(callback) : setTimeout(callback, 1_000)\n })\n return () => {\n subscription.unsubscribe()\n cancelOrClear()\n }\n }, [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 deferred render: that keeps the Studio as\n * it was while the code loads, with the navbar toggle showing the deferred\n * value as pending, 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 persistedSnapshot: ReturnType<typeof readPersistedSnapshot>\n}\n\n/**\n * Wraps the whole Studio so that the theme picked in the themer sidebar\n * applies everywhere while the user browses around, runs the themer machine\n * that the navbar toggle and the sidebar share — the user's themes, which one\n * is applied, which flow the sidebar is in and how the Studio is previewed —\n * and renders the sidebar next to the Studio. The sidebar sits at the\n * `layout` level rather than in `activeToolLayout` because the split preview\n * renders the Studio twice, and the sidebar must not come along.\n *\n * The Studio next to the sidebar always follows the appearance setting\n * (light/dark/system) and the picked theme like any other theme would. The\n * split preview adds a second copy in the opposite scheme on the far side —\n * or on top, on small screens — through React's view transitions (React\n * 19.3), styled in `ThemerLayout.css.ts`.\n *\n * The machine says what shows and when the layout is in motion (its `panel`,\n * `split` and `moving` tags); the layout defers what shows, which puts the\n * panel's and the copy's mounts in a transition — what lets React animate\n * them — and picks the view transition classes from the tags.\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 * `ThemerLayout.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 const oppositeScheme: ThemeColorSchemeKey = scheme === 'dark' ? 'light' : 'dark'\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 configured theme (`null`) goes through\n * the provider too, inheriting the Studio's, so picking a theme swaps it\n * instead of remounting the Studio under a new provider.\n *\n * The two copies of the split preview share the router, the document store\n * and every other provider above the layout — only the scheme differs — so\n * they stay in sync while navigating. The `color-scheme` of a forced scheme\n * keeps native form controls and scrollbars in step with it.\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 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\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 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 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_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 <= 2\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/themer",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Generate Sanity Studio themes, and preview them with a Studio tool.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -29,6 +29,12 @@
29
29
  "*.css"
30
30
  ],
31
31
  "types": "./dist/index.d.ts",
32
+ "imports": {
33
+ "#constants": "./src/tool/constants.ts",
34
+ "#context": "./src/tool/context.ts",
35
+ "#reducer": "./src/tool/reducer.ts",
36
+ "#types": "./src/tool/types.ts"
37
+ },
32
38
  "exports": {
33
39
  ".": "./dist/index.js",
34
40
  "./legacy": "./dist/legacy.js",
@@ -46,13 +52,15 @@
46
52
  "@sanity/color": "^3.0.8",
47
53
  "@sanity/icons": "^5.2.2",
48
54
  "@sanity/themer-legacy": "^0.2.1",
49
- "@sanity/ui": "^4.2.7",
55
+ "@sanity/ui": "^4.3.0",
50
56
  "@vanilla-extract/dynamic": "^2.1.5",
51
57
  "@xstate/react": "^6.1.0",
58
+ "dequal": "^2.0.3",
52
59
  "motion": "^13.4.4",
53
60
  "react-refractor": "^4.0.0",
54
61
  "refractor": "^5.0.0",
55
62
  "scroll-into-view-if-needed": "^3.1.0",
63
+ "valibot": "^1.5.0",
56
64
  "xstate": "^5.33.2"
57
65
  },
58
66
  "devDependencies": {
@@ -63,7 +71,6 @@
63
71
  "@types/react": "^19.3.0",
64
72
  "@types/react-dom": "^19.3.0",
65
73
  "@vanilla-extract/css": "^1.21.2",
66
- "jsdom": "^29.1.1",
67
74
  "lightningcss": "^1.33.0",
68
75
  "oxc-transform-react": "^0.147.0",
69
76
  "react": "^19.3.0",
@@ -74,6 +81,7 @@
74
81
  },
75
82
  "peerDependencies": {
76
83
  "react": "^19.3.0",
84
+ "react-dom": "^19.3.0",
77
85
  "sanity": "^3 || ^4 || ^5 || ^6"
78
86
  },
79
87
  "peerDependenciesMeta": {