@sanity/themer
0.8.00.8.1
~
Modified (12 files)
Index: package/package.json
===================================================================
--- package/package.json
+++ package/package.json
@@ -1,25 +1,25 @@
{
"name": "@sanity/themer",
- "version": "0.8.0",
+ "version": "0.8.1",
"description": "Generate Sanity Studio themes, and preview them with a Studio tool.",
"keywords": [
"sanity",
"studio",
"theme",
"themer",
"ui"
],
- "homepage": "https://www.sanity.io/",
+ "homepage": "https://github.com/sanity-io/plugins/tree/main/plugins/%40sanity/themer#readme",
"bugs": {
- "url": "https://github.com/sanity-io/ui/issues"
+ "url": "https://github.com/sanity-io/plugins/issues"
},
"license": "MIT",
"author": "Sanity.io <[email protected]>",
"repository": {
"type": "git",
- "url": "git+https://github.com/sanity-io/ui.git",
- "directory": "packages/themer"
+ "url": "git+ssh://[email protected]/sanity-io/plugins.git",
+ "directory": "plugins/@sanity/themer"
},
"files": [
"dist"
],
@@ -55,24 +55,23 @@
"scroll-into-view-if-needed": "^3.1.0",
"xstate": "^5.33.2"
},
"devDependencies": {
- "@sanity/tsdown-config": "^0.28.1",
- "@sanity/vanilla-extract-vite-plugin": "^0.2.19",
- "@types/node": "^24.13.6",
+ "@sanity/tsconfig": "^2.2.1",
+ "@sanity/tsdown-config": "^0.27.1",
+ "@sanity/vanilla-extract-vite-plugin": "^0.2.20",
+ "@types/node": "^24.13.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vanilla-extract/css": "^1.21.2",
- "jsdom": "^30.1.1",
+ "jsdom": "^29.1.1",
"lightningcss": "^1.33.0",
- "oxc-transform-react": "^0.151.0",
+ "oxc-transform-react": "^0.147.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
- "rimraf": "^5.0.5",
"sanity": "^6.16.0",
- "tsdown": "^0.23.0",
- "typescript": "7.0.2",
- "vitest": "^5.0.2"
+ "styled-components": "^6.5.3",
+ "tsdown": "^0.22.14"
},
"peerDependencies": {
"react": "^19.3.0",
"sanity": "^3 || ^4 || ^5 || ^6"
@@ -85,11 +84,7 @@
"engines": {
"node": ">=20.19 <22 || >=22.12"
},
"scripts": {
- "build": "tsdown",
- "clean": "rimraf dist",
- "test": "vitest run",
- "test:watch": "vitest",
- "watch": "tsdown --watch"
+ "build": "tsdown"
}
}
\ No newline at end of file Index: package/dist/index.d.ts.map
===================================================================
--- package/dist/index.d.ts.map
+++ package/dist/index.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"index.d.ts","names":[],"sources":["../src/theme/buildPalette.ts","../src/theme/buildTheme.ts","../src/theme/presets.ts"],"mappings":";;;;;;;;;KAuCY;EAAyB;EAAe;IAAiB,OACnE,aACA,OAAO;;;;;;KAQG,yBAAyB,OAAO,qBAAqB;;;;;;;;;;;;;;;;;wBAkBjD,aAAa,UAAS,oBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBCjB/C,WAAW,UAAS,oBAAyB;;;;;;;UC1C5C;EACf;EACA;;EAEA,SAAS;;;;;;;;;;;;;;;;;qBA6BE,SAAS"}
\ No newline at end of file
+{"version":3,"file":"index.d.ts","names":[],"sources":["../src/theme/buildPalette.ts","../src/theme/buildTheme.ts","../src/theme/presets.ts"],"mappings":";;;;;;;;;KA6CY;EAAyB;EAAe;IAAiB,OACnE,aACA,OAAO;;;;;;KAQG,yBAAyB,OAAO,qBAAqB;;;;;;;;;;;;;;;;;iBAkBjD,aAAa,UAAS,oBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCvB/C,WAAW,UAAS,oBAAyB;;;;;;;UC1C5C;EACf;EACA;;EAEA,SAAS;;;;;;;;;;;;;;;;;cA6BE,SAAS"}
\ No newline at end of file Index: package/dist/legacy.d.ts.map
===================================================================
--- package/dist/legacy.d.ts.map
+++ package/dist/legacy.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"legacy.d.ts","names":[],"sources":["../src/legacy/index.ts"],"mappings":";;;;;;;qBAYa,0BAA0B,aAAa;;;;;;;qBASvC,oBAAoB,aAAa;;;;;;;qBAQjC,yBAAyB,aAAa;;;;;;;qBAQtC,aAAa,aAAa;;;;;;;qBAQ1B,cAAc,aAAa;;;;;;;qBAQ3B,gBAAgB,aAAa;;;;;;YAO9B,MAAM,aAAa;;;;;;YAOnB,cAAc,aAAa;;;;;;YAO3B,OAAO,aAAa;;;;;;YAOpB,cAAc,aAAa;;;;;;YAO3B,cAAc,aAAa;;;;;;YAO3B,cAAc,aAAa"}
\ No newline at end of file
+{"version":3,"file":"legacy.d.ts","names":[],"sources":["../src/legacy/index.ts"],"mappings":";;;;;;;cAYa,0BAA0B,aAAa;;;;;;;cASvC,oBAAoB,aAAa;;;;;;;cAQjC,yBAAyB,aAAa;;;;;;;cAQtC,aAAa,aAAa;;;;;;;cAQ1B,cAAc,aAAa;;;;;;;cAQ3B,gBAAgB,aAAa;;;;;;KAO9B,MAAM,aAAa;;;;;;KAOnB,cAAc,aAAa;;;;;;KAO3B,OAAO,aAAa;;;;;;KAOpB,cAAc,aAAa;;;;;;KAO3B,cAAc,aAAa;;;;;;KAO3B,cAAc,aAAa"}
\ No newline at end of file Index: package/dist/presets-CqBgS3A-.js.map
===================================================================
--- package/dist/presets-CqBgS3A-.js.map
+++ package/dist/presets-CqBgS3A-.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"presets-CqBgS3A-.js","names":["buildTheme","buildUITheme"],"sources":["../src/theme/hsl.ts","../src/lib/mix.ts","../src/theme/options.ts","../src/theme/buildPalette.ts","../src/theme/buildTheme.ts","../src/theme/presets.ts"],"sourcesContent":["/**\n * Color math for the palette generator, kept at full float precision: unlike\n * the HSL helpers in `@sanity/ui/theme`, nothing is rounded between the two\n * conversions, so `hslToHex(hexToHsl(hex))` returns `hex` unchanged for every\n * color. `buildTheme` relies on that to reproduce the stock `@sanity/color`\n * palette byte-for-byte when it is called with the stock anchor colors.\n */\n\n/**\n * A color in HSL space: `h` in degrees within `[0, 360)`, `s` and `l` as\n * fractions within `[0, 1]`.\n *\n * @internal\n */\nexport interface Hsl {\n h: number\n s: number\n l: number\n}\n\n/** @internal */\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = parseHex(hex)\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const delta = max - min\n\n const l = (max + min) / 2\n\n if (delta === 0) {\n return {h: 0, s: 0, l}\n }\n\n const s = delta / (1 - Math.abs(2 * l - 1))\n\n let h: number\n\n if (max === r) {\n h = ((g - b) / delta) % 6\n } else if (max === g) {\n h = (b - r) / delta + 2\n } else {\n h = (r - g) / delta + 4\n }\n\n return {h: normalizeHue(h * 60), s, l}\n}\n\n/** @internal */\nexport function hslToHex(hsl: Hsl): string {\n const h = normalizeHue(hsl.h)\n const s = clamp01(hsl.s)\n const l = clamp01(hsl.l)\n\n const c = (1 - Math.abs(2 * l - 1)) * s\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = l - c / 2\n\n let r = 0\n let g = 0\n let b = 0\n\n if (h < 60) {\n r = c\n g = x\n } else if (h < 120) {\n r = x\n g = c\n } else if (h < 180) {\n g = c\n b = x\n } else if (h < 240) {\n g = x\n b = c\n } else if (h < 300) {\n r = x\n b = c\n } else {\n r = c\n b = x\n }\n\n return `#${channelToHex(r + m)}${channelToHex(g + m)}${channelToHex(b + m)}`\n}\n\n/**\n * Rotates `from` towards `to` along the shortest arc of the hue circle.\n *\n * @internal\n */\nexport function mixHue(from: number, to: number, amount: number): number {\n const delta = ((((to - from) % 360) + 540) % 360) - 180\n\n return normalizeHue(from + delta * amount)\n}\n\n/**\n * The WCAG relative luminance of a hex color, between 0 (black) and 1 (white).\n *\n * @internal\n */\nexport function relativeLuminance(hex: string): number {\n const [r, g, b] = parseHex(hex)\n\n return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b)\n}\n\nfunction linearChannel(value: number): number {\n return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\n/** Parses `#abc` and `#aabbcc` hex colors into channel fractions of 1 */\nfunction parseHex(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16) / 255,\n parseInt(expanded.slice(2, 4), 16) / 255,\n parseInt(expanded.slice(4, 6), 16) / 255,\n ]\n}\n\nfunction channelToHex(value: number): string {\n return Math.round(clamp01(value) * 255)\n .toString(16)\n .padStart(2, '0')\n}\n\nfunction normalizeHue(hue: number): number {\n return ((hue % 360) + 360) % 360\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n","/**\n * Matches `#abc` and `#aabbcc` hex colors (the only formats the theme\n * generators accept), same as the hosted Themer service did.\n *\n * The `mix` function this module was named for moved to `@sanity/themer-legacy`\n * along with the legacy generator; `isColor` stays here for `theme/options.ts`\n * and `tool/storage.ts`.\n *\n * @internal\n */\nexport function isColor(input: string): boolean {\n return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input)\n}\n","import {black, blue, gray, white} from '@sanity/color'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {isColor} from '../lib/mix'\nimport {hexToHsl, hslToHex} from './hsl'\n\n/**\n * The colors of one color scheme, for {@link BuildThemeOptions}. Every color\n * is a hex color (`#rgb` or `#rrggbb`), and everything is optional: omitted\n * options fall back to the stock Studio colors, so an empty object reproduces\n * the stock scheme exactly.\n *\n * @public\n */\nexport interface SchemeThemeOptions {\n /**\n * The accent color — the brand color of the scheme. Replaces the `blue`\n * scale of the palette, which Sanity UI uses for primary buttons, focus\n * rings and links.\n *\n * The stock accent is `#556bfc` (`blue/500` of `@sanity/color`).\n */\n accent?: string\n\n /**\n * The background of the scheme — every other color of the scheme is\n * blended onto it. The dark scheme's background replaces `black` in the\n * palette and the light scheme's replaces `white`.\n *\n * It is kept far enough from the text and accent colors to stay usable:\n * a dark background is made darker until it has enough contrast with them\n * (it can never be lighter than either), and a light background can never\n * be darker than either.\n *\n * The stock backgrounds are `#0d0e12` (dark) and `#ffffff` (light).\n */\n background?: string\n\n /**\n * How strongly text and borders separate from the accent color, between 15\n * and 100. The default is 85, which uses the text color as-is. Above 85 the\n * text scale loses its tint until it is fully neutral at 100 — a high\n * contrast scheme with no mixing of the text and accent colors. Below 85\n * more and more of the accent color mixes into the text scale, giving text\n * and borders more color, until the scale fully adopts the accent hue and\n * saturation at 15.\n */\n contrast?: number\n\n /**\n * The text color. Replaces the `gray` scale of the palette, which Sanity UI\n * uses for text, icons, borders and neutral surfaces. When omitted, it is\n * derived from the accent color: a mostly desaturated version of it, the\n * way the stock `gray` scale carries a hint of the stock blue.\n *\n * The stock text color is `#727892` (`gray/500` of `@sanity/color`).\n */\n text?: string\n}\n\n/**\n * Options for {@link buildTheme} and {@link buildPalette}: the colors of the\n * dark and the light scheme, each with its own accent, text, background and\n * contrast — so the two schemes can differ in more than their background.\n * Both schemes are optional, and so is every color: whatever is omitted\n * falls back to the stock Studio colors, so `buildTheme({})` matches\n * `buildTheme()` from `@sanity/ui/theme` exactly.\n *\n * @public\n */\nexport interface BuildThemeOptions {\n /** The colors of the dark scheme */\n dark?: SchemeThemeOptions\n /** The colors of the light scheme */\n light?: SchemeThemeOptions\n}\n\n/**\n * {@link SchemeThemeOptions} with every option filled in: colors normalized\n * to lowercase `#rrggbb`, the text color derived from the accent when it was\n * omitted, and the contrast clamped into its valid range.\n *\n * @internal\n */\nexport interface ResolvedSchemeOptions {\n accent: string\n background: string\n contrast: number\n text: string\n}\n\n/** {@link BuildThemeOptions} with both schemes resolved @internal */\nexport type ResolvedThemeOptions = Record<ThemeColorSchemeKey, ResolvedSchemeOptions>\n\n/** The color schemes, in the order the tool presents them @internal */\nexport const SCHEMES: ThemeColorSchemeKey[] = ['light', 'dark']\n\n/** The stock accent color — `blue/500` of `@sanity/color` @internal */\nexport const DEFAULT_ACCENT: string = blue[500].hex\n\n/** The stock text color — `gray/500` of `@sanity/color` @internal */\nexport const DEFAULT_TEXT: string = gray[500].hex\n\n/** The stock backgrounds — `black` and `white` of `@sanity/color` @internal */\nexport const DEFAULT_BACKGROUND: Record<ThemeColorSchemeKey, string> = {\n dark: black.hex,\n light: white.hex,\n}\n\n/** The contrast that applies the text color as-is @internal */\nexport const DEFAULT_CONTRAST = 85\n\n/** @internal */\nexport const MINIMUM_CONTRAST = 15\n\n/** @internal */\nexport const MAXIMUM_CONTRAST = 100\n\n/** The keys of the options shape before they were grouped by scheme */\nconst FLAT_OPTION_KEYS = ['accent', 'text', 'background', 'contrast']\n\n/** @internal */\nexport function resolveThemeOptions(options: BuildThemeOptions = {}): ResolvedThemeOptions {\n if (FLAT_OPTION_KEYS.some((key) => key in options)) {\n throw new TypeError(\n 'The `buildTheme` options are grouped by color scheme: pass `{light: {accent, text, background, contrast}, dark: {…}}` instead of one set of colors for both',\n )\n }\n\n return {\n dark: resolveSchemeOptions(options.dark ?? {}, 'dark'),\n light: resolveSchemeOptions(options.light ?? {}, 'light'),\n }\n}\n\nfunction resolveSchemeOptions(\n options: SchemeThemeOptions,\n scheme: ThemeColorSchemeKey,\n): ResolvedSchemeOptions {\n const accent =\n options.accent === undefined\n ? DEFAULT_ACCENT\n : normalizeColor(options.accent, `${scheme}.accent`)\n\n return {\n accent,\n background:\n options.background === undefined\n ? DEFAULT_BACKGROUND[scheme]\n : normalizeColor(options.background, `${scheme}.background`),\n contrast: normalizeContrast(options.contrast, scheme),\n text:\n options.text === undefined\n ? deriveTextColor(accent)\n : normalizeColor(options.text, `${scheme}.text`),\n }\n}\n\n/**\n * Derives the text color from an accent color, keeping the relationship the\n * stock palette has between `gray/500` and `blue/500`: the text color takes\n * the accent's hue (offset the way the stock gray is offset from the stock\n * blue), carries about an eighth of its saturation, and keeps the stock text\n * lightness. Deriving from the stock accent returns the stock text color.\n *\n * @internal\n */\nexport function deriveTextColor(accent: string): string {\n const accentHsl = hexToHsl(normalizeColor(accent, 'accent'))\n const defaultAccent = hexToHsl(DEFAULT_ACCENT)\n const defaultText = hexToHsl(DEFAULT_TEXT)\n\n return hslToHex({\n h: defaultText.h + (accentHsl.h - defaultAccent.h),\n s: Math.min(1, defaultText.s * (accentHsl.s / defaultAccent.s)),\n l: defaultText.l,\n })\n}\n\nfunction normalizeColor(value: string, name: string): string {\n if (typeof value !== 'string' || !isColor(value)) {\n throw new TypeError(\n `Invalid \\`${name}\\` color: ${JSON.stringify(value)} — expected a hex color like #556bfc`,\n )\n }\n\n const lower = value.toLowerCase()\n\n return lower.length === 4\n ? `#${lower[1]}${lower[1]}${lower[2]}${lower[2]}${lower[3]}${lower[3]}`\n : lower\n}\n\nfunction normalizeContrast(value: number | undefined, scheme: ThemeColorSchemeKey): number {\n if (value === undefined) {\n return DEFAULT_CONTRAST\n }\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new TypeError(\n `Invalid \\`${scheme}.contrast\\`: ${JSON.stringify(value)} — expected a number between ${MINIMUM_CONTRAST} and ${MAXIMUM_CONTRAST}`,\n )\n }\n\n return Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))\n}\n","import {color, COLOR_TINTS, ColorHueKey, ColorTintKey, ColorTints} from '@sanity/color'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, Hsl, hslToHex, mixHue, relativeLuminance} from './hsl'\nimport {\n BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_CONTRAST,\n DEFAULT_TEXT,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n} from './options'\n\n/**\n * The lightness band the accent and text colors are clamped into — anchors\n * outside it would squeeze most of their scale's tints into an\n * indistinguishable sliver, messing with the rest of the scale.\n */\nconst MINIMUM_ANCHOR_LIGHTNESS = 0.25\nconst MAXIMUM_ANCHOR_LIGHTNESS = 0.75\n\n/**\n * The contrast ratio the dark background must reach against the 400 tints of\n * the text and accent scales — the tints Sanity UI paints muted text and\n * solid buttons with on dark backgrounds. The stock palette sits at ~6.8.\n */\nconst MINIMUM_DARK_BACKGROUND_CONTRAST = 4.5\n\n/** How much lightness each step of a background adjustment removes or adds */\nconst BACKGROUND_LIGHTNESS_STEP = 0.005\n\n/**\n * A color palette generated by {@link buildPalette}: the `palette` config of\n * `buildTheme` from `@sanity/ui/theme`, with every color as a hex string.\n *\n * @public\n */\nexport type GeneratedColorPalette = {black: string; white: string} & Record<\n ColorHueKey,\n Record<ColorTintKey, string>\n>\n\n/**\n * The palettes generated by {@link buildPalette} — one per color scheme.\n *\n * @public\n */\nexport type GeneratedColorPalettes = Record<ThemeColorSchemeKey, GeneratedColorPalette>\n\n/**\n * Generates a Sanity UI color palette for each color scheme from the theme\n * options: in each palette the scheme's accent color replaces the `blue`\n * scale, its text color the `gray` scale, and the backgrounds replace `black`\n * and `white`. All other hues keep their stock `@sanity/color` values. Called\n * with the stock colors, both palettes come back as the stock palette\n * byte-for-byte.\n *\n * ```ts\n * import {buildPalette} from '@sanity/themer'\n *\n * const {light, dark} = buildPalette({light: {accent: '#556bfc'}})\n * ```\n *\n * @public\n */\nexport function buildPalette(options: BuildThemeOptions = {}): GeneratedColorPalettes {\n const resolved = resolveThemeOptions(options)\n\n return {\n dark: buildSchemePalette('dark', resolved.dark, resolved.light.background),\n light: buildSchemePalette('light', resolved.light, resolved.dark.background),\n }\n}\n\n/**\n * Generates the palette of one scheme. The scheme's own background takes the\n * slot the scheme paints with (`black` for dark, `white` for light), while\n * the other slot — which a scheme only touches for a few inverted details —\n * takes the other scheme's background, so the theme stays one palette.\n */\nfunction buildSchemePalette(\n key: ThemeColorSchemeKey,\n scheme: ResolvedSchemeOptions,\n otherBackground: string,\n): GeneratedColorPalette {\n const accentAnchor = clampAnchorLightness(hexToHsl(scheme.accent))\n const textAnchor = clampAnchorLightness(hexToHsl(scheme.text))\n const grayAnchor = applyContrast(textAnchor, accentAnchor, scheme.contrast)\n\n const blueTints = remapTints(color.blue, hexToHsl(DEFAULT_ACCENT), accentAnchor)\n const grayTints = remapTints(color.gray, hexToHsl(DEFAULT_TEXT), grayAnchor)\n\n const anchorLuminances = [\n relativeLuminance(hslToHex(grayAnchor)),\n relativeLuminance(hslToHex(accentAnchor)),\n ]\n\n const darkBackground = key === 'dark' ? scheme.background : otherBackground\n const lightBackground = key === 'light' ? scheme.background : otherBackground\n\n return {\n black: darkenBackground(darkBackground, {\n anchorLuminances,\n tints: [grayTints['400'], blueTints['400']],\n }),\n white: lightenBackground(lightBackground, anchorLuminances),\n gray: grayTints,\n blue: blueTints,\n purple: tintHexes(color.purple),\n magenta: tintHexes(color.magenta),\n red: tintHexes(color.red),\n orange: tintHexes(color.orange),\n yellow: tintHexes(color.yellow),\n green: tintHexes(color.green),\n cyan: tintHexes(color.cyan),\n }\n}\n\n/**\n * Mixes the accent color into the text color according to the contrast: 85\n * applies the text color as-is, 100 removes its tint entirely, and lower\n * values rotate and saturate it towards the accent.\n */\nfunction applyContrast(text: Hsl, accent: Hsl, contrast: number): Hsl {\n if (contrast === DEFAULT_CONTRAST) {\n return text\n }\n\n if (contrast > DEFAULT_CONTRAST) {\n const amount = (contrast - DEFAULT_CONTRAST) / (MAXIMUM_CONTRAST - DEFAULT_CONTRAST)\n\n return {h: text.h, s: text.s * (1 - amount), l: text.l}\n }\n\n const amount = (DEFAULT_CONTRAST - contrast) / (DEFAULT_CONTRAST - MINIMUM_CONTRAST)\n\n return {\n h: mixHue(text.h, accent.h, amount),\n s: text.s + (accent.s - text.s) * amount,\n l: text.l,\n }\n}\n\n/**\n * Rebuilds a stock tint scale around a new anchor color, where `from` is the\n * scale's stock 500 tint: hues rotate along, saturation scales\n * proportionally, and lightness is remapped piecewise so the anchor takes the\n * new lightness while the scale keeps spanning light to dark. When the anchor\n * matches the stock one, every tint comes back unchanged.\n */\nfunction remapTints(tints: ColorTints, from: Hsl, to: Hsl): Record<ColorTintKey, string> {\n const rotation = to.h - from.h\n const result: Partial<Record<ColorTintKey, string>> = {}\n\n for (const tint of COLOR_TINTS) {\n const value = hexToHsl(tints[tint].hex)\n\n result[tint] = hslToHex({\n h: value.h + rotation,\n s: remapSaturation(value.s, from.s, to.s),\n l: remapLightness(value.l, from.l, to.l),\n })\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every tint key\n return result as Record<ColorTintKey, string>\n}\n\nfunction remapSaturation(value: number, from: number, to: number): number {\n if (from === to) {\n return value\n }\n\n return Math.min(1, value * (to / from))\n}\n\nfunction remapLightness(value: number, from: number, to: number): number {\n if (from === to) {\n return value\n }\n\n if (value <= from) {\n return value * (to / from)\n }\n\n return to + ((value - from) * (1 - to)) / (1 - from)\n}\n\nfunction clampAnchorLightness(anchor: Hsl): Hsl {\n const l = Math.min(MAXIMUM_ANCHOR_LIGHTNESS, Math.max(MINIMUM_ANCHOR_LIGHTNESS, anchor.l))\n\n return l === anchor.l ? anchor : {...anchor, l}\n}\n\n/**\n * Enforces the dark background rules: it can never be lighter than the text\n * or accent colors, and it must reach enough contrast with the 400 tints of\n * their scales — it is made darker until it does (or bottoms out at black).\n */\nfunction darkenBackground(\n hex: string,\n rules: {anchorLuminances: number[]; tints: string[]},\n): string {\n const maximumLuminance = Math.min(...rules.anchorLuminances)\n const tintLuminances = rules.tints.map((tint) => relativeLuminance(tint))\n\n const isDarkEnough = (background: string): boolean => {\n const luminance = relativeLuminance(background)\n\n return (\n luminance < maximumLuminance &&\n tintLuminances.every(\n (tintLuminance) =>\n (tintLuminance + 0.05) / (luminance + 0.05) >= MINIMUM_DARK_BACKGROUND_CONTRAST,\n )\n )\n }\n\n const {h, s, l} = hexToHsl(hex)\n let lightness = l\n let current = hex\n\n while (!isDarkEnough(current) && lightness > 0) {\n lightness = Math.max(0, lightness - BACKGROUND_LIGHTNESS_STEP)\n current = hslToHex({h, s, l: lightness})\n }\n\n return current\n}\n\n/**\n * Enforces the light background rule: it can never be darker than the text\n * or accent colors — it is made lighter until it is (or tops out at white).\n */\nfunction lightenBackground(hex: string, anchorLuminances: number[]): string {\n const minimumLuminance = Math.max(...anchorLuminances)\n\n const {h, s, l} = hexToHsl(hex)\n let lightness = l\n let current = hex\n\n while (relativeLuminance(current) <= minimumLuminance && lightness < 1) {\n lightness = Math.min(1, lightness + BACKGROUND_LIGHTNESS_STEP)\n current = hslToHex({h, s, l: lightness})\n }\n\n return current\n}\n\nfunction tintHexes(tints: ColorTints): Record<ColorTintKey, string> {\n const result: Partial<Record<ColorTintKey, string>> = {}\n\n for (const tint of COLOR_TINTS) {\n result[tint] = tints[tint].hex\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every tint key\n return result as Record<ColorTintKey, string>\n}\n","import {\n buildTheme as buildUITheme,\n RootTheme,\n ThemeColorSchemes_v2,\n v0_v2,\n v2_v0,\n} from '@sanity/ui/theme'\n\nimport {buildPalette} from './buildPalette'\nimport {BuildThemeOptions} from './options'\n\n/**\n * Generates a Studio theme from a handful of colors per color scheme, by\n * replacing the color palette that the `buildTheme` from `@sanity/ui/theme`\n * otherwise fills with `@sanity/color`: in each scheme the accent color\n * replaces the `blue` scale (primary buttons, focus rings, links), the\n * optional text color the `gray` scale (text, icons, borders), and the\n * optional background replaces `black` (dark) or `white` (light). The result\n * is the same type of theme that `@sanity/ui/theme` builds, ready for the\n * `theme` property of a Studio config.\n *\n * ```ts\n * import {buildTheme} from '@sanity/themer'\n * import {defineConfig} from 'sanity'\n *\n * export const theme = buildTheme({\n * light: {\n * accent: '#556bfc',\n * text: '#727892', // optional — derived from `accent` when omitted\n * background: '#ffffff', // optional\n * contrast: 85, // optional, 15–100\n * },\n * dark: {\n * accent: '#556bfc',\n * background: '#0d0e12',\n * },\n * })\n *\n * export default defineConfig({\n * theme,\n * // ...rest of the config\n * })\n * ```\n *\n * Every option is optional and falls back to the stock Studio colors: called\n * with the stock colors — like the example — or with no options at all, it\n * returns the exact same colors as `buildTheme()` from `@sanity/ui/theme`.\n *\n * @public\n */\nexport function buildTheme(options: BuildThemeOptions = {}): RootTheme {\n const palettes = buildPalette(options)\n\n // Sanity UI renders both schemes of a theme from one palette, so each\n // scheme gets a theme of its own palette, and the two are composed into one\n // theme that takes its light scheme from the one and its dark scheme from\n // the other. The scheme getters keep Sanity UI's lazy rendering: a scheme is\n // only computed once something reads it.\n const light = v0_v2(buildUITheme({palette: palettes.light}))\n const dark = v0_v2(buildUITheme({palette: palettes.dark}))\n\n const color: ThemeColorSchemes_v2 = {\n get light() {\n return light.color.light\n },\n get dark() {\n return dark.color.dark\n },\n }\n\n return v2_v0({...light, color})\n}\n","import {BuildThemeOptions} from './options'\n\n/**\n * A preset theme: a named set of {@link BuildThemeOptions} ready to pass to\n * `buildTheme`.\n *\n * @public\n */\nexport interface ThemePreset {\n slug: string\n title: string\n /** The preset's theme options, ready to pass to `buildTheme` */\n options: BuildThemeOptions\n}\n\n/**\n * The hosted Themer presets had one accent and one text color for both\n * schemes, and a background per scheme\n */\nfunction scheme(accent: string, text: string, backgrounds: {dark: string; light: string}) {\n return {\n dark: {accent, text, background: backgrounds.dark},\n light: {accent, text, background: backgrounds.light},\n } satisfies BuildThemeOptions\n}\n\n/**\n * Preset themes for `buildTheme`, carried over from the hosted Themer service\n * (themer.sanity.build) presets: each one maps the legacy preset's primary\n * hue to `accent`, its default hue to `text` and its lightest/darkest colors\n * to the backgrounds of the light and dark scheme.\n *\n * ```ts\n * import {buildTheme, presets} from '@sanity/themer'\n *\n * const verdant = presets.find((preset) => preset.slug === 'verdant')\n * const theme = buildTheme(verdant.options)\n * ```\n *\n * @public\n */\nexport const presets: ThemePreset[] = [\n {\n slug: 'studio',\n title: 'Studio',\n options: {},\n },\n {\n slug: 'dew',\n title: 'Dew',\n options: scheme('#d1a308', '#5e63b4', {dark: '#0d0d15', light: '#fcfcfd'}),\n },\n {\n slug: 'pink-synth',\n title: 'Pink Synth',\n options: scheme('#ec4899', '#8b6584', {dark: '#171721', light: '#f7f2f5'}),\n },\n {\n slug: 'pixel-art',\n title: 'Pixel Art',\n options: scheme('#f10784', '#57619c', {dark: '#0d0e15', light: '#fcfcfd'}),\n },\n {\n slug: 'retro-colonial',\n title: 'Retro Colonial',\n options: scheme('#fa7a78', '#8bb9b5', {dark: '#0d1515', light: '#fcfdfd'}),\n },\n {\n slug: 'rosabel',\n title: 'Rosabel',\n options: scheme('#ed2555', '#9d8966', {dark: '#15120d', light: '#fdfdfc'}),\n },\n {\n slug: 'stereofidelic',\n title: 'Stereofidelic',\n options: scheme('#f13009', '#678e9a', {dark: '#0e1315', light: '#fcfdfd'}),\n },\n {\n slug: 'verdant',\n title: 'Verdant',\n options: scheme('#1cb485', '#5c9199', {dark: '#0d1415', light: '#fcfdfd'}),\n },\n]\n"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"presets-CqBgS3A-.js","names":["buildTheme","buildUITheme"],"sources":["../src/theme/hsl.ts","../src/lib/mix.ts","../src/theme/options.ts","../src/theme/buildPalette.ts","../src/theme/buildTheme.ts","../src/theme/presets.ts"],"sourcesContent":["/**\n * Color math for the palette generator, kept at full float precision: unlike\n * the HSL helpers in `@sanity/ui/theme`, nothing is rounded between the two\n * conversions, so `hslToHex(hexToHsl(hex))` returns `hex` unchanged for every\n * color. `buildTheme` relies on that to reproduce the stock `@sanity/color`\n * palette byte-for-byte when it is called with the stock anchor colors.\n */\n\n/**\n * A color in HSL space: `h` in degrees within `[0, 360)`, `s` and `l` as\n * fractions within `[0, 1]`.\n *\n * @internal\n */\nexport interface Hsl {\n h: number\n s: number\n l: number\n}\n\n/** @internal */\nexport function hexToHsl(hex: string): Hsl {\n const [r, g, b] = parseHex(hex)\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const delta = max - min\n\n const l = (max + min) / 2\n\n if (delta === 0) {\n return {h: 0, s: 0, l}\n }\n\n const s = delta / (1 - Math.abs(2 * l - 1))\n\n let h: number\n\n if (max === r) {\n h = ((g - b) / delta) % 6\n } else if (max === g) {\n h = (b - r) / delta + 2\n } else {\n h = (r - g) / delta + 4\n }\n\n return {h: normalizeHue(h * 60), s, l}\n}\n\n/** @internal */\nexport function hslToHex(hsl: Hsl): string {\n const h = normalizeHue(hsl.h)\n const s = clamp01(hsl.s)\n const l = clamp01(hsl.l)\n\n const c = (1 - Math.abs(2 * l - 1)) * s\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = l - c / 2\n\n let r = 0\n let g = 0\n let b = 0\n\n if (h < 60) {\n r = c\n g = x\n } else if (h < 120) {\n r = x\n g = c\n } else if (h < 180) {\n g = c\n b = x\n } else if (h < 240) {\n g = x\n b = c\n } else if (h < 300) {\n r = x\n b = c\n } else {\n r = c\n b = x\n }\n\n return `#${channelToHex(r + m)}${channelToHex(g + m)}${channelToHex(b + m)}`\n}\n\n/**\n * Rotates `from` towards `to` along the shortest arc of the hue circle.\n *\n * @internal\n */\nexport function mixHue(from: number, to: number, amount: number): number {\n const delta = ((((to - from) % 360) + 540) % 360) - 180\n\n return normalizeHue(from + delta * amount)\n}\n\n/**\n * The WCAG relative luminance of a hex color, between 0 (black) and 1 (white).\n *\n * @internal\n */\nexport function relativeLuminance(hex: string): number {\n const [r, g, b] = parseHex(hex)\n\n return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b)\n}\n\nfunction linearChannel(value: number): number {\n return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\n/** Parses `#abc` and `#aabbcc` hex colors into channel fractions of 1 */\nfunction parseHex(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16) / 255,\n parseInt(expanded.slice(2, 4), 16) / 255,\n parseInt(expanded.slice(4, 6), 16) / 255,\n ]\n}\n\nfunction channelToHex(value: number): string {\n return Math.round(clamp01(value) * 255)\n .toString(16)\n .padStart(2, '0')\n}\n\nfunction normalizeHue(hue: number): number {\n return ((hue % 360) + 360) % 360\n}\n\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n","/**\n * Matches `#abc` and `#aabbcc` hex colors (the only formats the theme\n * generators accept), same as the hosted Themer service did.\n *\n * The `mix` function this module was named for moved to `@sanity/themer-legacy`\n * along with the legacy generator; `isColor` stays here for `theme/options.ts`\n * and `tool/storage.ts`.\n *\n * @internal\n */\nexport function isColor(input: string): boolean {\n return /^#(?:[0-9a-f]{3}){1,2}$/i.test(input)\n}\n","import {black, blue, gray, white} from '@sanity/color'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {isColor} from '../lib/mix'\nimport {hexToHsl, hslToHex} from './hsl'\n\n/**\n * The colors of one color scheme, for {@link BuildThemeOptions}. Every color\n * is a hex color (`#rgb` or `#rrggbb`), and everything is optional: omitted\n * options fall back to the stock Studio colors, so an empty object reproduces\n * the stock scheme exactly.\n *\n * @public\n */\nexport interface SchemeThemeOptions {\n /**\n * The accent color — the brand color of the scheme. Replaces the `blue`\n * scale of the palette, which Sanity UI uses for primary buttons, focus\n * rings and links.\n *\n * The stock accent is `#556bfc` (`blue/500` of `@sanity/color`).\n */\n accent?: string\n\n /**\n * The background of the scheme — every other color of the scheme is\n * blended onto it. The dark scheme's background replaces `black` in the\n * palette and the light scheme's replaces `white`.\n *\n * It is kept far enough from the text and accent colors to stay usable:\n * a dark background is made darker until it has enough contrast with them\n * (it can never be lighter than either), and a light background can never\n * be darker than either.\n *\n * The stock backgrounds are `#0d0e12` (dark) and `#ffffff` (light).\n */\n background?: string\n\n /**\n * How strongly text and borders separate from the accent color, between 15\n * and 100. The default is 85, which uses the text color as-is. Above 85 the\n * text scale loses its tint until it is fully neutral at 100 — a high\n * contrast scheme with no mixing of the text and accent colors. Below 85\n * more and more of the accent color mixes into the text scale, giving text\n * and borders more color, until the scale fully adopts the accent hue and\n * saturation at 15.\n */\n contrast?: number\n\n /**\n * The text color. Replaces the `gray` scale of the palette, which Sanity UI\n * uses for text, icons, borders and neutral surfaces. When omitted, it is\n * derived from the accent color: a mostly desaturated version of it, the\n * way the stock `gray` scale carries a hint of the stock blue.\n *\n * The stock text color is `#727892` (`gray/500` of `@sanity/color`).\n */\n text?: string\n}\n\n/**\n * Options for {@link buildTheme} and {@link buildPalette}: the colors of the\n * dark and the light scheme, each with its own accent, text, background and\n * contrast — so the two schemes can differ in more than their background.\n * Both schemes are optional, and so is every color: whatever is omitted\n * falls back to the stock Studio colors, so `buildTheme({})` matches\n * `buildTheme()` from `@sanity/ui/theme` exactly.\n *\n * @public\n */\nexport interface BuildThemeOptions {\n /** The colors of the dark scheme */\n dark?: SchemeThemeOptions\n /** The colors of the light scheme */\n light?: SchemeThemeOptions\n}\n\n/**\n * {@link SchemeThemeOptions} with every option filled in: colors normalized\n * to lowercase `#rrggbb`, the text color derived from the accent when it was\n * omitted, and the contrast clamped into its valid range.\n *\n * @internal\n */\nexport interface ResolvedSchemeOptions {\n accent: string\n background: string\n contrast: number\n text: string\n}\n\n/** {@link BuildThemeOptions} with both schemes resolved @internal */\nexport type ResolvedThemeOptions = Record<ThemeColorSchemeKey, ResolvedSchemeOptions>\n\n/** The color schemes, in the order the tool presents them @internal */\nexport const SCHEMES: ThemeColorSchemeKey[] = ['light', 'dark']\n\n/** The stock accent color — `blue/500` of `@sanity/color` @internal */\nexport const DEFAULT_ACCENT: string = blue[500].hex\n\n/** The stock text color — `gray/500` of `@sanity/color` @internal */\nexport const DEFAULT_TEXT: string = gray[500].hex\n\n/** The stock backgrounds — `black` and `white` of `@sanity/color` @internal */\nexport const DEFAULT_BACKGROUND: Record<ThemeColorSchemeKey, string> = {\n dark: black.hex,\n light: white.hex,\n}\n\n/** The contrast that applies the text color as-is @internal */\nexport const DEFAULT_CONTRAST = 85\n\n/** @internal */\nexport const MINIMUM_CONTRAST = 15\n\n/** @internal */\nexport const MAXIMUM_CONTRAST = 100\n\n/** The keys of the options shape before they were grouped by scheme */\nconst FLAT_OPTION_KEYS = ['accent', 'text', 'background', 'contrast']\n\n/** @internal */\nexport function resolveThemeOptions(options: BuildThemeOptions = {}): ResolvedThemeOptions {\n if (FLAT_OPTION_KEYS.some((key) => key in options)) {\n throw new TypeError(\n 'The `buildTheme` options are grouped by color scheme: pass `{light: {accent, text, background, contrast}, dark: {…}}` instead of one set of colors for both',\n )\n }\n\n return {\n dark: resolveSchemeOptions(options.dark ?? {}, 'dark'),\n light: resolveSchemeOptions(options.light ?? {}, 'light'),\n }\n}\n\nfunction resolveSchemeOptions(\n options: SchemeThemeOptions,\n scheme: ThemeColorSchemeKey,\n): ResolvedSchemeOptions {\n const accent =\n options.accent === undefined\n ? DEFAULT_ACCENT\n : normalizeColor(options.accent, `${scheme}.accent`)\n\n return {\n accent,\n background:\n options.background === undefined\n ? DEFAULT_BACKGROUND[scheme]\n : normalizeColor(options.background, `${scheme}.background`),\n contrast: normalizeContrast(options.contrast, scheme),\n text:\n options.text === undefined\n ? deriveTextColor(accent)\n : normalizeColor(options.text, `${scheme}.text`),\n }\n}\n\n/**\n * Derives the text color from an accent color, keeping the relationship the\n * stock palette has between `gray/500` and `blue/500`: the text color takes\n * the accent's hue (offset the way the stock gray is offset from the stock\n * blue), carries about an eighth of its saturation, and keeps the stock text\n * lightness. Deriving from the stock accent returns the stock text color.\n *\n * @internal\n */\nexport function deriveTextColor(accent: string): string {\n const accentHsl = hexToHsl(normalizeColor(accent, 'accent'))\n const defaultAccent = hexToHsl(DEFAULT_ACCENT)\n const defaultText = hexToHsl(DEFAULT_TEXT)\n\n return hslToHex({\n h: defaultText.h + (accentHsl.h - defaultAccent.h),\n s: Math.min(1, defaultText.s * (accentHsl.s / defaultAccent.s)),\n l: defaultText.l,\n })\n}\n\nfunction normalizeColor(value: string, name: string): string {\n if (typeof value !== 'string' || !isColor(value)) {\n throw new TypeError(\n `Invalid \\`${name}\\` color: ${JSON.stringify(value)} — expected a hex color like #556bfc`,\n )\n }\n\n const lower = value.toLowerCase()\n\n return lower.length === 4\n ? `#${lower[1]}${lower[1]}${lower[2]}${lower[2]}${lower[3]}${lower[3]}`\n : lower\n}\n\nfunction normalizeContrast(value: number | undefined, scheme: ThemeColorSchemeKey): number {\n if (value === undefined) {\n return DEFAULT_CONTRAST\n }\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new TypeError(\n `Invalid \\`${scheme}.contrast\\`: ${JSON.stringify(value)} — expected a number between ${MINIMUM_CONTRAST} and ${MAXIMUM_CONTRAST}`,\n )\n }\n\n return Math.min(MAXIMUM_CONTRAST, Math.max(MINIMUM_CONTRAST, value))\n}\n","import {\n color,\n COLOR_TINTS,\n type ColorHueKey,\n type ColorTintKey,\n type ColorTints,\n} from '@sanity/color'\nimport {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, type Hsl, hslToHex, mixHue, relativeLuminance} from './hsl'\nimport {\n type BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_CONTRAST,\n DEFAULT_TEXT,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n type ResolvedSchemeOptions,\n resolveThemeOptions,\n} from './options'\n\n/**\n * The lightness band the accent and text colors are clamped into — anchors\n * outside it would squeeze most of their scale's tints into an\n * indistinguishable sliver, messing with the rest of the scale.\n */\nconst MINIMUM_ANCHOR_LIGHTNESS = 0.25\nconst MAXIMUM_ANCHOR_LIGHTNESS = 0.75\n\n/**\n * The contrast ratio the dark background must reach against the 400 tints of\n * the text and accent scales — the tints Sanity UI paints muted text and\n * solid buttons with on dark backgrounds. The stock palette sits at ~6.8.\n */\nconst MINIMUM_DARK_BACKGROUND_CONTRAST = 4.5\n\n/** How much lightness each step of a background adjustment removes or adds */\nconst BACKGROUND_LIGHTNESS_STEP = 0.005\n\n/**\n * A color palette generated by {@link buildPalette}: the `palette` config of\n * `buildTheme` from `@sanity/ui/theme`, with every color as a hex string.\n *\n * @public\n */\nexport type GeneratedColorPalette = {black: string; white: string} & Record<\n ColorHueKey,\n Record<ColorTintKey, string>\n>\n\n/**\n * The palettes generated by {@link buildPalette} — one per color scheme.\n *\n * @public\n */\nexport type GeneratedColorPalettes = Record<ThemeColorSchemeKey, GeneratedColorPalette>\n\n/**\n * Generates a Sanity UI color palette for each color scheme from the theme\n * options: in each palette the scheme's accent color replaces the `blue`\n * scale, its text color the `gray` scale, and the backgrounds replace `black`\n * and `white`. All other hues keep their stock `@sanity/color` values. Called\n * with the stock colors, both palettes come back as the stock palette\n * byte-for-byte.\n *\n * ```ts\n * import {buildPalette} from '@sanity/themer'\n *\n * const {light, dark} = buildPalette({light: {accent: '#556bfc'}})\n * ```\n *\n * @public\n */\nexport function buildPalette(options: BuildThemeOptions = {}): GeneratedColorPalettes {\n const resolved = resolveThemeOptions(options)\n\n return {\n dark: buildSchemePalette('dark', resolved.dark, resolved.light.background),\n light: buildSchemePalette('light', resolved.light, resolved.dark.background),\n }\n}\n\n/**\n * Generates the palette of one scheme. The scheme's own background takes the\n * slot the scheme paints with (`black` for dark, `white` for light), while\n * the other slot — which a scheme only touches for a few inverted details —\n * takes the other scheme's background, so the theme stays one palette.\n */\nfunction buildSchemePalette(\n key: ThemeColorSchemeKey,\n scheme: ResolvedSchemeOptions,\n otherBackground: string,\n): GeneratedColorPalette {\n const accentAnchor = clampAnchorLightness(hexToHsl(scheme.accent))\n const textAnchor = clampAnchorLightness(hexToHsl(scheme.text))\n const grayAnchor = applyContrast(textAnchor, accentAnchor, scheme.contrast)\n\n const blueTints = remapTints(color.blue, hexToHsl(DEFAULT_ACCENT), accentAnchor)\n const grayTints = remapTints(color.gray, hexToHsl(DEFAULT_TEXT), grayAnchor)\n\n const anchorLuminances = [\n relativeLuminance(hslToHex(grayAnchor)),\n relativeLuminance(hslToHex(accentAnchor)),\n ]\n\n const darkBackground = key === 'dark' ? scheme.background : otherBackground\n const lightBackground = key === 'light' ? scheme.background : otherBackground\n\n return {\n black: darkenBackground(darkBackground, {\n anchorLuminances,\n tints: [grayTints['400'], blueTints['400']],\n }),\n white: lightenBackground(lightBackground, anchorLuminances),\n gray: grayTints,\n blue: blueTints,\n purple: tintHexes(color.purple),\n magenta: tintHexes(color.magenta),\n red: tintHexes(color.red),\n orange: tintHexes(color.orange),\n yellow: tintHexes(color.yellow),\n green: tintHexes(color.green),\n cyan: tintHexes(color.cyan),\n }\n}\n\n/**\n * Mixes the accent color into the text color according to the contrast: 85\n * applies the text color as-is, 100 removes its tint entirely, and lower\n * values rotate and saturate it towards the accent.\n */\nfunction applyContrast(text: Hsl, accent: Hsl, contrast: number): Hsl {\n if (contrast === DEFAULT_CONTRAST) {\n return text\n }\n\n if (contrast > DEFAULT_CONTRAST) {\n const amount = (contrast - DEFAULT_CONTRAST) / (MAXIMUM_CONTRAST - DEFAULT_CONTRAST)\n\n return {h: text.h, s: text.s * (1 - amount), l: text.l}\n }\n\n const amount = (DEFAULT_CONTRAST - contrast) / (DEFAULT_CONTRAST - MINIMUM_CONTRAST)\n\n return {\n h: mixHue(text.h, accent.h, amount),\n s: text.s + (accent.s - text.s) * amount,\n l: text.l,\n }\n}\n\n/**\n * Rebuilds a stock tint scale around a new anchor color, where `from` is the\n * scale's stock 500 tint: hues rotate along, saturation scales\n * proportionally, and lightness is remapped piecewise so the anchor takes the\n * new lightness while the scale keeps spanning light to dark. When the anchor\n * matches the stock one, every tint comes back unchanged.\n */\nfunction remapTints(tints: ColorTints, from: Hsl, to: Hsl): Record<ColorTintKey, string> {\n const rotation = to.h - from.h\n const result: Partial<Record<ColorTintKey, string>> = {}\n\n for (const tint of COLOR_TINTS) {\n const value = hexToHsl(tints[tint].hex)\n\n result[tint] = hslToHex({\n h: value.h + rotation,\n s: remapSaturation(value.s, from.s, to.s),\n l: remapLightness(value.l, from.l, to.l),\n })\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every tint key\n return result as Record<ColorTintKey, string>\n}\n\nfunction remapSaturation(value: number, from: number, to: number): number {\n if (from === to) {\n return value\n }\n\n return Math.min(1, value * (to / from))\n}\n\nfunction remapLightness(value: number, from: number, to: number): number {\n if (from === to) {\n return value\n }\n\n if (value <= from) {\n return value * (to / from)\n }\n\n return to + ((value - from) * (1 - to)) / (1 - from)\n}\n\nfunction clampAnchorLightness(anchor: Hsl): Hsl {\n const l = Math.min(MAXIMUM_ANCHOR_LIGHTNESS, Math.max(MINIMUM_ANCHOR_LIGHTNESS, anchor.l))\n\n return l === anchor.l ? anchor : {...anchor, l}\n}\n\n/**\n * Enforces the dark background rules: it can never be lighter than the text\n * or accent colors, and it must reach enough contrast with the 400 tints of\n * their scales — it is made darker until it does (or bottoms out at black).\n */\nfunction darkenBackground(\n hex: string,\n rules: {anchorLuminances: number[]; tints: string[]},\n): string {\n const maximumLuminance = Math.min(...rules.anchorLuminances)\n const tintLuminances = rules.tints.map((tint) => relativeLuminance(tint))\n\n const isDarkEnough = (background: string): boolean => {\n const luminance = relativeLuminance(background)\n\n return (\n luminance < maximumLuminance &&\n tintLuminances.every(\n (tintLuminance) =>\n (tintLuminance + 0.05) / (luminance + 0.05) >= MINIMUM_DARK_BACKGROUND_CONTRAST,\n )\n )\n }\n\n const {h, s, l} = hexToHsl(hex)\n let lightness = l\n let current = hex\n\n while (!isDarkEnough(current) && lightness > 0) {\n lightness = Math.max(0, lightness - BACKGROUND_LIGHTNESS_STEP)\n current = hslToHex({h, s, l: lightness})\n }\n\n return current\n}\n\n/**\n * Enforces the light background rule: it can never be darker than the text\n * or accent colors — it is made lighter until it is (or tops out at white).\n */\nfunction lightenBackground(hex: string, anchorLuminances: number[]): string {\n const minimumLuminance = Math.max(...anchorLuminances)\n\n const {h, s, l} = hexToHsl(hex)\n let lightness = l\n let current = hex\n\n while (relativeLuminance(current) <= minimumLuminance && lightness < 1) {\n lightness = Math.min(1, lightness + BACKGROUND_LIGHTNESS_STEP)\n current = hslToHex({h, s, l: lightness})\n }\n\n return current\n}\n\nfunction tintHexes(tints: ColorTints): Record<ColorTintKey, string> {\n const result: Partial<Record<ColorTintKey, string>> = {}\n\n for (const tint of COLOR_TINTS) {\n result[tint] = tints[tint].hex\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- the loop assigns every tint key\n return result as Record<ColorTintKey, string>\n}\n","import {\n buildTheme as buildUITheme,\n type RootTheme,\n type ThemeColorSchemes_v2,\n v0_v2,\n v2_v0,\n} from '@sanity/ui/theme'\n\nimport {buildPalette} from './buildPalette'\nimport type {BuildThemeOptions} from './options'\n\n/**\n * Generates a Studio theme from a handful of colors per color scheme, by\n * replacing the color palette that the `buildTheme` from `@sanity/ui/theme`\n * otherwise fills with `@sanity/color`: in each scheme the accent color\n * replaces the `blue` scale (primary buttons, focus rings, links), the\n * optional text color the `gray` scale (text, icons, borders), and the\n * optional background replaces `black` (dark) or `white` (light). The result\n * is the same type of theme that `@sanity/ui/theme` builds, ready for the\n * `theme` property of a Studio config.\n *\n * ```ts\n * import {buildTheme} from '@sanity/themer'\n * import {defineConfig} from 'sanity'\n *\n * export const theme = buildTheme({\n * light: {\n * accent: '#556bfc',\n * text: '#727892', // optional — derived from `accent` when omitted\n * background: '#ffffff', // optional\n * contrast: 85, // optional, 15–100\n * },\n * dark: {\n * accent: '#556bfc',\n * background: '#0d0e12',\n * },\n * })\n *\n * export default defineConfig({\n * theme,\n * // ...rest of the config\n * })\n * ```\n *\n * Every option is optional and falls back to the stock Studio colors: called\n * with the stock colors — like the example — or with no options at all, it\n * returns the exact same colors as `buildTheme()` from `@sanity/ui/theme`.\n *\n * @public\n */\nexport function buildTheme(options: BuildThemeOptions = {}): RootTheme {\n const palettes = buildPalette(options)\n\n // Sanity UI renders both schemes of a theme from one palette, so each\n // scheme gets a theme of its own palette, and the two are composed into one\n // theme that takes its light scheme from the one and its dark scheme from\n // the other. The scheme getters keep Sanity UI's lazy rendering: a scheme is\n // only computed once something reads it.\n const light = v0_v2(buildUITheme({palette: palettes.light}))\n const dark = v0_v2(buildUITheme({palette: palettes.dark}))\n\n const color: ThemeColorSchemes_v2 = {\n get light() {\n return light.color.light\n },\n get dark() {\n return dark.color.dark\n },\n }\n\n return v2_v0({...light, color})\n}\n","import type {BuildThemeOptions} from './options'\n\n/**\n * A preset theme: a named set of {@link BuildThemeOptions} ready to pass to\n * `buildTheme`.\n *\n * @public\n */\nexport interface ThemePreset {\n slug: string\n title: string\n /** The preset's theme options, ready to pass to `buildTheme` */\n options: BuildThemeOptions\n}\n\n/**\n * The hosted Themer presets had one accent and one text color for both\n * schemes, and a background per scheme\n */\nfunction scheme(accent: string, text: string, backgrounds: {dark: string; light: string}) {\n return {\n dark: {accent, text, background: backgrounds.dark},\n light: {accent, text, background: backgrounds.light},\n } satisfies BuildThemeOptions\n}\n\n/**\n * Preset themes for `buildTheme`, carried over from the hosted Themer service\n * (themer.sanity.build) presets: each one maps the legacy preset's primary\n * hue to `accent`, its default hue to `text` and its lightest/darkest colors\n * to the backgrounds of the light and dark scheme.\n *\n * ```ts\n * import {buildTheme, presets} from '@sanity/themer'\n *\n * const verdant = presets.find((preset) => preset.slug === 'verdant')\n * const theme = buildTheme(verdant.options)\n * ```\n *\n * @public\n */\nexport const presets: ThemePreset[] = [\n {\n slug: 'studio',\n title: 'Studio',\n options: {},\n },\n {\n slug: 'dew',\n title: 'Dew',\n options: scheme('#d1a308', '#5e63b4', {dark: '#0d0d15', light: '#fcfcfd'}),\n },\n {\n slug: 'pink-synth',\n title: 'Pink Synth',\n options: scheme('#ec4899', '#8b6584', {dark: '#171721', light: '#f7f2f5'}),\n },\n {\n slug: 'pixel-art',\n title: 'Pixel Art',\n options: scheme('#f10784', '#57619c', {dark: '#0d0e15', light: '#fcfcfd'}),\n },\n {\n slug: 'retro-colonial',\n title: 'Retro Colonial',\n options: scheme('#fa7a78', '#8bb9b5', {dark: '#0d1515', light: '#fcfdfd'}),\n },\n {\n slug: 'rosabel',\n title: 'Rosabel',\n options: scheme('#ed2555', '#9d8966', {dark: '#15120d', light: '#fdfdfc'}),\n },\n {\n slug: 'stereofidelic',\n title: 'Stereofidelic',\n options: scheme('#f13009', '#678e9a', {dark: '#0e1315', light: '#fcfdfd'}),\n },\n {\n slug: 'verdant',\n title: 'Verdant',\n options: scheme('#1cb485', '#5c9199', {dark: '#0d1415', light: '#fcfdfd'}),\n },\n]\n"],"mappings":"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"}
\ No newline at end of file File too large for inline diff
Index: package/dist/ResizableSidebar.css.ts.vanilla-M9EuPu-q.js.map
===================================================================
--- package/dist/ResizableSidebar.css.ts.vanilla-M9EuPu-q.js.map
+++ package/dist/ResizableSidebar.css.ts.vanilla-M9EuPu-q.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"ResizableSidebar.css.ts.vanilla-M9EuPu-q.js","names":[],"sources":["../src/tool/context.ts","../src/tool/imagePalette.ts","../src/tool/options.ts","../src/tool/themes.ts"],"sourcesContent":["import {createContext, useContext} from 'react'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ThemerEvent} from './machine'\nimport {ThemerTheme} from './themes'\n\n/**\n * The flow the sidebar is in: picking a theme from the list, editing one of\n * the user's own themes, or restoring removed ones.\n *\n * @internal\n */\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\n/** @internal */\nexport interface ThemerContextValue {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n /** Object URLs of the images themes took their palette from this session, by slug */\n images: Record<string, string>\n view: ThemerView\n /** Whether the themer sidebar is open */\n open: boolean\n /** Whether the sidebar is on its way to opening for the first time, pending while its code loads */\n loading: boolean\n /** Whether the Studio shows twice side by side, in light and dark */\n split: boolean\n /** Whether the Studio is on a small screen, where the sidebar covers it and the split preview stacks */\n mobile: boolean\n /** The height of the Studio navbar, which the sidebar's header matches — `null` until it has rendered */\n navbarHeight: number | null\n /** Sends an event to the themer machine */\n send: (event: ThemerEvent) => void\n}\n\n/** @internal */\nexport const ThemerContext = createContext<ThemerContextValue | null>(null)\n\n/** @internal */\nexport function useThemer(): ThemerContextValue {\n const context = useContext(ThemerContext)\n\n if (!context) {\n throw new Error('useThemer must be used within the `themerTool` plugin')\n }\n\n return context\n}\n","import {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, hslToHex, relativeLuminance} from '../theme/hsl'\nimport {BuildThemeOptions, DEFAULT_BACKGROUND, SCHEMES, SchemeThemeOptions} from '../theme/options'\n\n/**\n * The swatches of an image palette — the same set the Sanity image pipeline\n * derives for uploaded assets (`metadata.palette`), extracted on device here.\n *\n * @internal\n */\nexport type ImagePaletteKey =\n | 'dominant'\n | 'vibrant'\n | 'lightVibrant'\n | 'darkVibrant'\n | 'muted'\n | 'lightMuted'\n | 'darkMuted'\n\n/**\n * An image palette: a hex color per swatch, or `null` when the image has no\n * color that qualifies for it.\n *\n * @internal\n */\nexport type ImagePalette = Record<ImagePaletteKey, string | null>\n\n/** @internal */\nexport const IMAGE_PALETTE_KEYS: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n 'dominant',\n]\n\n/**\n * The swatches a theme can be built around — every swatch but the dominant\n * one, which only repeats one of them.\n *\n * @internal\n */\nexport type ImagePaletteVariant = Exclude<ImagePaletteKey, 'dominant'>\n\n/** The variants in the order the tool offers them @internal */\nexport const IMAGE_PALETTE_VARIANTS: ImagePaletteVariant[] = [\n 'muted',\n 'vibrant',\n 'lightMuted',\n 'lightVibrant',\n 'darkMuted',\n 'darkVibrant',\n]\n\n/** @internal */\nexport const IMAGE_PALETTE_TITLES: Record<ImagePaletteKey, string> = {\n dominant: 'Dominant',\n vibrant: 'Vibrant',\n lightVibrant: 'Light vibrant',\n darkVibrant: 'Dark vibrant',\n muted: 'Muted',\n lightMuted: 'Light muted',\n darkMuted: 'Dark muted',\n}\n\n/** Pixels the way a canvas hands them out: rows of RGBA bytes @internal */\nexport interface PixelData {\n data: ArrayLike<number>\n width: number\n height: number\n}\n\ninterface Bucket {\n count: number\n r: number\n g: number\n b: number\n}\n\ninterface Candidate {\n hex: string\n population: number\n saturation: number\n lightness: number\n}\n\n/** What a swatch looks for: a lightness and saturation to aim at, within bounds */\ninterface Target {\n lightness: [target: number, min: number, max: number]\n saturation: [target: number, min: number, max: number]\n}\n\n/**\n * The swatch targets of the Vibrant algorithm (the one behind Sanity's image\n * palettes), in the order they get to claim colors.\n */\nconst TARGETS: Array<[key: Exclude<ImagePaletteKey, 'dominant'>, target: Target]> = [\n ['vibrant', {lightness: [0.5, 0.3, 0.7], saturation: [1, 0.35, 1]}],\n ['lightVibrant', {lightness: [0.74, 0.55, 1], saturation: [1, 0.35, 1]}],\n ['darkVibrant', {lightness: [0.26, 0, 0.45], saturation: [1, 0.35, 1]}],\n ['muted', {lightness: [0.5, 0.3, 0.7], saturation: [0.3, 0, 0.4]}],\n ['lightMuted', {lightness: [0.74, 0.55, 1], saturation: [0.3, 0, 0.4]}],\n ['darkMuted', {lightness: [0.26, 0, 0.45], saturation: [0.3, 0, 0.4]}],\n]\n\nconst SATURATION_WEIGHT = 3\nconst LIGHTNESS_WEIGHT = 6.5\nconst POPULATION_WEIGHT = 0.5\n\n/** Pixels this transparent are not part of the picture */\nconst MINIMUM_ALPHA = 125\n\n/** Near-black and near-white pixels are backdrop rather than color */\nconst MINIMUM_LIGHTNESS = 0.05\nconst MAXIMUM_LIGHTNESS = 0.95\n\n/** How many of the 8 bits per channel survive the quantization */\nconst QUANTIZE_BITS = 5\n\n/**\n * Extracts the color palette of an image from its pixels, the way the Vibrant\n * algorithm does: the colors are quantized into a coarse histogram, and each\n * swatch claims the color that comes closest to its target lightness and\n * saturation — weighted by how much of the image it covers — that no other\n * swatch has claimed yet. The dominant swatch is simply the most common\n * color. Runs anywhere pixels come from, so it can run on device.\n *\n * @internal\n */\nexport function extractImagePalette(pixels: PixelData): ImagePalette {\n const candidates = quantize(pixels)\n const palette: ImagePalette = {\n dominant: null,\n vibrant: null,\n lightVibrant: null,\n darkVibrant: null,\n muted: null,\n lightMuted: null,\n darkMuted: null,\n }\n\n if (candidates.length === 0) return palette\n\n const maximumPopulation = Math.max(...candidates.map((candidate) => candidate.population))\n const claimed = new Set<string>()\n\n for (const [key, target] of TARGETS) {\n let best: Candidate | null = null\n let bestScore = -Infinity\n\n for (const candidate of candidates) {\n if (claimed.has(candidate.hex) || !matchesTarget(candidate, target)) continue\n\n const score = scoreCandidate(candidate, target, maximumPopulation)\n\n if (score > bestScore) {\n best = candidate\n bestScore = score\n }\n }\n\n if (best) {\n palette[key] = best.hex\n claimed.add(best.hex)\n }\n }\n\n palette.dominant = candidates.reduce((most, candidate) =>\n candidate.population > most.population ? candidate : most,\n ).hex\n\n return palette\n}\n\n/**\n * Turns the pixels into a histogram of quantized colors, each averaged over\n * the pixels it stands for, leaving out transparent pixels and the near-black\n * and near-white ones that are backdrop rather than color.\n */\nfunction quantize(pixels: PixelData): Candidate[] {\n const {data} = pixels\n const buckets = new Map<number, Bucket>()\n const shift = 8 - QUANTIZE_BITS\n const length = Math.min(data.length, pixels.width * pixels.height * 4)\n\n for (let offset = 0; offset + 3 < length; offset += 4) {\n const r = data[offset]\n const g = data[offset + 1]\n const b = data[offset + 2]\n const a = data[offset + 3]\n\n if (a < MINIMUM_ALPHA) continue\n\n const lightness = (Math.max(r, g, b) + Math.min(r, g, b)) / 510\n\n if (lightness < MINIMUM_LIGHTNESS || lightness > MAXIMUM_LIGHTNESS) continue\n\n const key =\n ((r >> shift) << (QUANTIZE_BITS * 2)) | ((g >> shift) << QUANTIZE_BITS) | (b >> shift)\n const bucket = buckets.get(key)\n\n if (bucket) {\n bucket.count++\n bucket.r += r\n bucket.g += g\n bucket.b += b\n } else {\n buckets.set(key, {count: 1, r, g, b})\n }\n }\n\n const candidates: Candidate[] = []\n\n for (const bucket of buckets.values()) {\n const r = Math.round(bucket.r / bucket.count)\n const g = Math.round(bucket.g / bucket.count)\n const b = Math.round(bucket.b / bucket.count)\n const {saturation, lightness} = saturationAndLightness(r, g, b)\n\n candidates.push({hex: rgbToHex(r, g, b), population: bucket.count, saturation, lightness})\n }\n\n return candidates\n}\n\nfunction matchesTarget(candidate: Candidate, target: Target): boolean {\n return (\n candidate.lightness >= target.lightness[1] &&\n candidate.lightness <= target.lightness[2] &&\n candidate.saturation >= target.saturation[1] &&\n candidate.saturation <= target.saturation[2]\n )\n}\n\nfunction scoreCandidate(candidate: Candidate, target: Target, maximumPopulation: number): number {\n return (\n SATURATION_WEIGHT * (1 - Math.abs(candidate.saturation - target.saturation[0])) +\n LIGHTNESS_WEIGHT * (1 - Math.abs(candidate.lightness - target.lightness[0])) +\n POPULATION_WEIGHT * (candidate.population / maximumPopulation)\n )\n}\n\nfunction saturationAndLightness(r: number, g: number, b: number) {\n const max = Math.max(r, g, b) / 255\n const min = Math.min(r, g, b) / 255\n const lightness = (max + min) / 2\n const delta = max - min\n const saturation =\n delta === 0 ? 0 : lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n\n return {saturation, lightness}\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`\n}\n\n/** Reads `#rgb` and `#rrggbb` alike — stored palettes may hold either */\nfunction hexToRgb(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16),\n parseInt(expanded.slice(2, 4), 16),\n parseInt(expanded.slice(4, 6), 16),\n ]\n}\n\n/** Mixes `amount` of `color` into `base` */\nfunction tint(base: string, color: string, amount: number): string {\n const [r1, g1, b1] = hexToRgb(base)\n const [r2, g2, b2] = hexToRgb(color)\n\n return rgbToHex(\n Math.round(r1 + (r2 - r1) * amount),\n Math.round(g1 + (g2 - g1) * amount),\n Math.round(b1 + (b2 - b1) * amount),\n )\n}\n\n/** How much of the palette tints each scheme's stock background */\nconst BACKGROUND_TINT = {light: 0.08, dark: 0.3}\n\n/**\n * The WCAG AA contrast for normal text — what a button label needs against\n * the button. Sanity UI labels solid buttons in white on the accent's 500\n * tint in the light scheme and in black on its 400 tint in the dark scheme\n * (lighter than the accent, so an accent that passes against black passes\n * there too).\n */\nconst MINIMUM_ACCENT_CONTRAST = 4.5\nconst BUTTON_LABEL: Record<ThemeColorSchemeKey, string> = {light: '#ffffff', dark: '#000000'}\nconst ACCENT_LIGHTNESS_STEP = 0.01\n\nfunction contrastRatio(a: string, b: string): number {\n const [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x)\n\n return (lighter + 0.05) / (darker + 0.05)\n}\n\n/**\n * Makes an accent readable as a button in the given scheme: a light scheme\n * accent is darkened until white text on it reaches AA contrast, a dark\n * scheme accent lightened until black text does — as far as the accent's\n * hue and saturation allow. Accents that already pass come back untouched.\n *\n * @internal\n */\nexport function readableAccent(hex: string, scheme: ThemeColorSchemeKey): string {\n const label = BUTTON_LABEL[scheme]\n const {h, s} = hexToHsl(hex)\n let {l} = hexToHsl(hex)\n let current = hex\n\n while (contrastRatio(current, label) < MINIMUM_ACCENT_CONTRAST) {\n const next = scheme === 'light' ? l - ACCENT_LIGHTNESS_STEP : l + ACCENT_LIGHTNESS_STEP\n\n if (next < 0 || next > 1) break\n\n l = next\n current = hslToHex({h, s, l})\n }\n\n return current\n}\n\n/** The accent a variant gives a scheme, made readable on a button */\nfunction variantAccent(\n palette: ImagePalette,\n variant: ImagePaletteVariant | null,\n scheme: ThemeColorSchemeKey,\n): string | null {\n const swatch =\n (variant ? palette[variant] : null) ??\n palette.vibrant ??\n (scheme === 'light' ? palette.darkVibrant : palette.lightVibrant) ??\n palette.dominant\n\n return swatch === null ? null : readableAccent(swatch, scheme)\n}\n\n/**\n * Derives theme options from an image palette: the accent of both schemes is\n * the swatch of the chosen variant — or, without one, the vibrant swatch,\n * each scheme falling back to the vibrant swatch of its own end of the scale —\n * adjusted until it is readable as a button. The muted swatch becomes the text color, and the light and dark muted\n * swatches tint the stock backgrounds of their scheme — enough to carry the\n * image's tone without giving up a usable canvas. Whatever the palette lacks\n * stays at the theme defaults.\n *\n * @internal\n */\nexport function optionsFromImagePalette(\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const options: BuildThemeOptions = {}\n\n const light = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'light'),\n palette.muted ?? palette.darkMuted,\n palette.lightMuted ?? palette.muted ?? palette.lightVibrant,\n 'light',\n )\n const dark = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'dark'),\n palette.muted ?? palette.lightMuted,\n palette.darkMuted ?? palette.muted ?? palette.darkVibrant,\n 'dark',\n )\n\n if (light) options.light = light\n if (dark) options.dark = dark\n\n return options\n}\n\nfunction schemeFromPalette(\n accent: string | null,\n text: string | null,\n backgroundTint: string | null,\n scheme: 'light' | 'dark',\n): SchemeThemeOptions | null {\n const options: SchemeThemeOptions = {}\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (backgroundTint) {\n options.background = tint(DEFAULT_BACKGROUND[scheme], backgroundTint, BACKGROUND_TINT[scheme])\n }\n\n return Object.keys(options).length > 0 ? options : null\n}\n\n/**\n * Applies an image palette to existing theme options: the colors the palette\n * provides replace the current ones scheme by scheme, and whatever it does\n * not touch — like the contrast — stays as it is.\n *\n * @internal\n */\nexport function applyImagePalette(\n options: BuildThemeOptions,\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const derived = optionsFromImagePalette(palette, variant)\n const next: BuildThemeOptions = {...options}\n\n for (const scheme of SCHEMES) {\n if (derived[scheme]) next[scheme] = {...options[scheme], ...derived[scheme]}\n }\n\n return next\n}\n\n/**\n * The variant a theme is built around: the one whose swatch is the theme's\n * accent, or `null` when the accent comes from somewhere else.\n *\n * @internal\n */\nexport function currentImageVariant(\n options: BuildThemeOptions,\n palette: ImagePalette,\n): ImagePaletteVariant | null {\n const accent = options.light?.accent\n\n if (!accent) return null\n\n return (\n IMAGE_PALETTE_VARIANTS.find(\n (variant) => palette[variant] !== null && variantAccent(palette, variant, 'light') === accent,\n ) ?? null\n )\n}\n\n/**\n * How much a swatch has going for it as an accent: saturation first, then\n * closeness to mid lightness — the range where an accent reads best.\n */\nfunction interest(hex: string): number {\n const {s, l} = hexToHsl(hex)\n\n return 0.1 + 0.55 * s + 0.35 * (1 - Math.abs(l - 0.5) * 2)\n}\n\n/**\n * Picks a variant for the \"I'm feeling lucky\" button: a random one, weighted\n * by how interesting its swatch is as an accent, and never the one the theme\n * already uses while there are others to pick from.\n *\n * @internal\n */\nexport function pickLuckyVariant(\n palette: ImagePalette,\n options: {exclude?: ImagePaletteVariant | null; random?: () => number} = {},\n): ImagePaletteVariant | null {\n const {exclude = null, random = Math.random} = options\n let candidates = IMAGE_PALETTE_VARIANTS.filter((variant) => palette[variant] !== null)\n\n if (candidates.length > 1) {\n candidates = candidates.filter((variant) => variant !== exclude)\n }\n\n if (candidates.length === 0) return null\n\n const weights = candidates.map((variant) => interest(palette[variant]!))\n const total = weights.reduce((sum, weight) => sum + weight, 0)\n let pick = random() * total\n\n for (const [index, variant] of candidates.entries()) {\n pick -= weights[index]\n\n if (pick < 0) return variant\n }\n\n return candidates.at(-1) ?? null\n}\n\n/** A theme title for the image a palette came from, e.g. `sunset-beach.jpg` → `sunset beach` @internal */\nexport function titleFromFileName(name: string): string {\n return (\n name\n .replace(/\\.[^.]+$/, '')\n .replace(/[-_]+/g, ' ')\n .trim() || 'Image theme'\n )\n}\n","import {\n BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_BACKGROUND,\n DEFAULT_CONTRAST,\n deriveTextColor,\n ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\n\n/**\n * Reduces theme options to the minimal object that recreates them through\n * `buildTheme`: in each scheme the accent is dropped when it is the stock\n * one, the text color when it matches the one derived from the accent, and\n * the background and contrast when they match the defaults — and a scheme\n * with nothing left is dropped altogether. Options that boil down to the\n * stock Studio theme reduce to `null` — they need nothing from this package.\n *\n * @internal\n */\nexport function minimizeOptions(options: BuildThemeOptions): BuildThemeOptions | null {\n const resolved = resolveThemeOptions(options)\n const minimized: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const values = resolved[scheme]\n const kept: SchemeThemeOptions = {}\n\n if (values.accent !== DEFAULT_ACCENT) kept.accent = values.accent\n if (values.text !== deriveTextColor(values.accent)) kept.text = values.text\n if (values.background !== DEFAULT_BACKGROUND[scheme]) kept.background = values.background\n if (values.contrast !== DEFAULT_CONTRAST) kept.contrast = values.contrast\n\n if (Object.keys(kept).length > 0) minimized[scheme] = kept\n }\n\n return Object.keys(minimized).length === 0 ? null : minimized\n}\n\n/**\n * Whether two sets of theme options generate the same theme, comparing their\n * resolved forms so that derived and explicit values (and hex casing) don't\n * matter.\n *\n * @internal\n */\nexport function sameOptions(a: BuildThemeOptions, b: BuildThemeOptions): boolean {\n const left = resolveThemeOptions(a)\n const right = resolveThemeOptions(b)\n\n return SCHEMES.every((scheme) => sameScheme(left[scheme], right[scheme]))\n}\n\nfunction sameScheme(a: ResolvedSchemeOptions, b: ResolvedSchemeOptions): boolean {\n return (\n a.accent === b.accent &&\n a.text === b.text &&\n a.background === b.background &&\n a.contrast === b.contrast\n )\n}\n","import {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {ImagePalette} from './imagePalette'\nimport {sameOptions} from './options'\n\n/**\n * Where a theme in the themer list comes from: the theme the Studio config\n * was generated from, one of the presets defined in code, or one the user\n * added in the tool. Only custom themes are editable — the others are\n * duplicated into a custom theme first.\n */\ntype ThemerThemeSource = 'config' | 'preset' | 'custom'\n\n/** A theme the themer tool lists @internal */\nexport interface ThemerTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n source: ThemerThemeSource\n /** The palette of the image the theme's colors were taken from, for custom themes */\n palette?: ImagePalette\n}\n\n/** A theme the user added in the tool, as persisted @internal */\nexport interface CustomTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n /** The palette of the image the theme's colors were taken from */\n palette?: ImagePalette\n}\n\n/** The persisted themer state @internal */\nexport interface ThemerState {\n /**\n * The slug of the applied theme — `null` applies nothing on top of the\n * Studio's configured theme\n */\n active: string | null\n /** The themes the user added */\n custom: CustomTheme[]\n /** The slugs of the themes the user removed from the list */\n removed: string[]\n /**\n * The slugs of the themes in the order the user arranged them — themes not\n * in it (new presets, themes added since) follow in their default order\n */\n order: string[]\n}\n\n/** The slug of the theme the Studio config was generated from @internal */\nexport const CONFIG_SLUG = 'config'\n\n/** The title of the theme the Studio config was generated from */\nconst CONFIG_TITLE = 'Studio config'\n\n/** The title new themes start out with @internal */\nexport const UNTITLED_THEME = 'Untitled theme'\n\n/** @internal */\nexport const initialThemerState: ThemerState = {active: null, custom: [], removed: [], order: []}\n\n/** The themes the tool works with, derived from the persisted state @internal */\nexport interface ResolvedThemes {\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n}\n\n/**\n * Resolves the list of themes: the configured theme first, then the presets\n * (minus the ones that would only repeat the configured theme), then the\n * user's own themes — rearranged into the order the user dragged them into,\n * with themes that order does not know about kept in that default order after\n * the ones it does. Removed themes are set aside so they can be restored, and\n * the active theme falls back to the configured one when its slug no longer\n * resolves.\n *\n * @internal\n */\nexport function resolveThemes(state: ThemerState, baseOptions: BuildThemeOptions): ResolvedThemes {\n const config: ThemerTheme = {\n slug: CONFIG_SLUG,\n title: CONFIG_TITLE,\n options: baseOptions,\n source: 'config',\n }\n\n const all: ThemerTheme[] = [config]\n\n for (const preset of presets) {\n if (!sameOptions(preset.options, baseOptions)) {\n all.push({slug: preset.slug, title: preset.title, options: preset.options, source: 'preset'})\n }\n }\n\n for (const theme of state.custom) {\n all.push({\n slug: theme.slug,\n title: theme.title,\n options: theme.options,\n source: 'custom',\n ...(theme.palette ? {palette: theme.palette} : {}),\n })\n }\n\n const position = new Map(state.order.map((slug, index) => [slug, index]))\n const rank = (theme: ThemerTheme) => position.get(theme.slug) ?? state.order.length\n\n // A stable sort: the themes the order does not know about keep their default order\n all.sort((a, b) => rank(a) - rank(b))\n\n const removedSlugs = new Set(state.removed)\n const themes = all.filter((theme) => !removedSlugs.has(theme.slug))\n const removed = all.filter((theme) => removedSlugs.has(theme.slug))\n const active = themes.find((theme) => theme.slug === state.active) ?? config\n\n return {themes, removed, active}\n}\n\n/**\n * Creates a custom theme with a slug that cannot collide with the presets or\n * the configured theme.\n *\n * @internal\n */\nexport function createCustomTheme(\n title: string,\n options: BuildThemeOptions,\n palette?: ImagePalette,\n): CustomTheme {\n return {slug: `custom-${randomId()}`, title, options, ...(palette ? {palette} : {})}\n}\n\n/** The title of a duplicated theme @internal */\nexport function duplicateTitle(title: string): string {\n return `${displayTitle(title)} copy`\n}\n\n/** The title to show for a theme, falling back when the user cleared it @internal */\nexport function displayTitle(title: string): string {\n return title.trim() || UNTITLED_THEME\n}\n\nfunction randomId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`\n}\n"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ResizableSidebar.css.ts.vanilla-M9EuPu-q.js","names":[],"sources":["../src/tool/context.ts","../src/tool/imagePalette.ts","../src/tool/options.ts","../src/tool/themes.ts"],"sourcesContent":["import {createContext, useContext} from 'react'\n\nimport type {BuildThemeOptions} from '../theme/options'\nimport type {ThemerEvent} from './machine'\nimport type {ThemerTheme} from './themes'\n\n/**\n * The flow the sidebar is in: picking a theme from the list, editing one of\n * the user's own themes, or restoring removed ones.\n *\n * @internal\n */\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\n/** @internal */\nexport interface ThemerContextValue {\n /** The theme options the Studio's configured theme was generated from */\n baseOptions: BuildThemeOptions\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n /** Object URLs of the images themes took their palette from this session, by slug */\n images: Record<string, string>\n view: ThemerView\n /** Whether the themer sidebar is open */\n open: boolean\n /** Whether the sidebar is on its way to opening for the first time, pending while its code loads */\n loading: boolean\n /** Whether the Studio shows twice side by side, in light and dark */\n split: boolean\n /** Whether the Studio is on a small screen, where the sidebar covers it and the split preview stacks */\n mobile: boolean\n /** The height of the Studio navbar, which the sidebar's header matches — `null` until it has rendered */\n navbarHeight: number | null\n /** Sends an event to the themer machine */\n send: (event: ThemerEvent) => void\n}\n\n/** @internal */\nexport const ThemerContext = createContext<ThemerContextValue | null>(null)\n\n/** @internal */\nexport function useThemer(): ThemerContextValue {\n const context = useContext(ThemerContext)\n\n if (!context) {\n throw new Error('useThemer must be used within the `themerTool` plugin')\n }\n\n return context\n}\n","import {type ThemeColorSchemeKey} from '@sanity/ui/theme'\n\nimport {hexToHsl, hslToHex, relativeLuminance} from '../theme/hsl'\nimport {\n type BuildThemeOptions,\n DEFAULT_BACKGROUND,\n SCHEMES,\n type SchemeThemeOptions,\n} from '../theme/options'\n\n/**\n * The swatches of an image palette — the same set the Sanity image pipeline\n * derives for uploaded assets (`metadata.palette`), extracted on device here.\n *\n * @internal\n */\nexport type ImagePaletteKey =\n | 'dominant'\n | 'vibrant'\n | 'lightVibrant'\n | 'darkVibrant'\n | 'muted'\n | 'lightMuted'\n | 'darkMuted'\n\n/**\n * An image palette: a hex color per swatch, or `null` when the image has no\n * color that qualifies for it.\n *\n * @internal\n */\nexport type ImagePalette = Record<ImagePaletteKey, string | null>\n\n/** @internal */\nexport const IMAGE_PALETTE_KEYS: ImagePaletteKey[] = [\n 'vibrant',\n 'lightVibrant',\n 'darkVibrant',\n 'muted',\n 'lightMuted',\n 'darkMuted',\n 'dominant',\n]\n\n/**\n * The swatches a theme can be built around — every swatch but the dominant\n * one, which only repeats one of them.\n *\n * @internal\n */\nexport type ImagePaletteVariant = Exclude<ImagePaletteKey, 'dominant'>\n\n/** The variants in the order the tool offers them @internal */\nexport const IMAGE_PALETTE_VARIANTS: ImagePaletteVariant[] = [\n 'muted',\n 'vibrant',\n 'lightMuted',\n 'lightVibrant',\n 'darkMuted',\n 'darkVibrant',\n]\n\n/** @internal */\nexport const IMAGE_PALETTE_TITLES: Record<ImagePaletteKey, string> = {\n dominant: 'Dominant',\n vibrant: 'Vibrant',\n lightVibrant: 'Light vibrant',\n darkVibrant: 'Dark vibrant',\n muted: 'Muted',\n lightMuted: 'Light muted',\n darkMuted: 'Dark muted',\n}\n\n/** Pixels the way a canvas hands them out: rows of RGBA bytes @internal */\nexport interface PixelData {\n data: ArrayLike<number>\n width: number\n height: number\n}\n\ninterface Bucket {\n count: number\n r: number\n g: number\n b: number\n}\n\ninterface Candidate {\n hex: string\n population: number\n saturation: number\n lightness: number\n}\n\n/** What a swatch looks for: a lightness and saturation to aim at, within bounds */\ninterface Target {\n lightness: [target: number, min: number, max: number]\n saturation: [target: number, min: number, max: number]\n}\n\n/**\n * The swatch targets of the Vibrant algorithm (the one behind Sanity's image\n * palettes), in the order they get to claim colors.\n */\nconst TARGETS: Array<[key: Exclude<ImagePaletteKey, 'dominant'>, target: Target]> = [\n ['vibrant', {lightness: [0.5, 0.3, 0.7], saturation: [1, 0.35, 1]}],\n ['lightVibrant', {lightness: [0.74, 0.55, 1], saturation: [1, 0.35, 1]}],\n ['darkVibrant', {lightness: [0.26, 0, 0.45], saturation: [1, 0.35, 1]}],\n ['muted', {lightness: [0.5, 0.3, 0.7], saturation: [0.3, 0, 0.4]}],\n ['lightMuted', {lightness: [0.74, 0.55, 1], saturation: [0.3, 0, 0.4]}],\n ['darkMuted', {lightness: [0.26, 0, 0.45], saturation: [0.3, 0, 0.4]}],\n]\n\nconst SATURATION_WEIGHT = 3\nconst LIGHTNESS_WEIGHT = 6.5\nconst POPULATION_WEIGHT = 0.5\n\n/** Pixels this transparent are not part of the picture */\nconst MINIMUM_ALPHA = 125\n\n/** Near-black and near-white pixels are backdrop rather than color */\nconst MINIMUM_LIGHTNESS = 0.05\nconst MAXIMUM_LIGHTNESS = 0.95\n\n/** How many of the 8 bits per channel survive the quantization */\nconst QUANTIZE_BITS = 5\n\n/**\n * Extracts the color palette of an image from its pixels, the way the Vibrant\n * algorithm does: the colors are quantized into a coarse histogram, and each\n * swatch claims the color that comes closest to its target lightness and\n * saturation — weighted by how much of the image it covers — that no other\n * swatch has claimed yet. The dominant swatch is simply the most common\n * color. Runs anywhere pixels come from, so it can run on device.\n *\n * @internal\n */\nexport function extractImagePalette(pixels: PixelData): ImagePalette {\n const candidates = quantize(pixels)\n const palette: ImagePalette = {\n dominant: null,\n vibrant: null,\n lightVibrant: null,\n darkVibrant: null,\n muted: null,\n lightMuted: null,\n darkMuted: null,\n }\n\n if (candidates.length === 0) return palette\n\n const maximumPopulation = Math.max(...candidates.map((candidate) => candidate.population))\n const claimed = new Set<string>()\n\n for (const [key, target] of TARGETS) {\n let best: Candidate | null = null\n let bestScore = -Infinity\n\n for (const candidate of candidates) {\n if (claimed.has(candidate.hex) || !matchesTarget(candidate, target)) continue\n\n const score = scoreCandidate(candidate, target, maximumPopulation)\n\n if (score > bestScore) {\n best = candidate\n bestScore = score\n }\n }\n\n if (best) {\n palette[key] = best.hex\n claimed.add(best.hex)\n }\n }\n\n palette.dominant = candidates.reduce((most, candidate) =>\n candidate.population > most.population ? candidate : most,\n ).hex\n\n return palette\n}\n\n/**\n * Turns the pixels into a histogram of quantized colors, each averaged over\n * the pixels it stands for, leaving out transparent pixels and the near-black\n * and near-white ones that are backdrop rather than color.\n */\nfunction quantize(pixels: PixelData): Candidate[] {\n const {data} = pixels\n const buckets = new Map<number, Bucket>()\n const shift = 8 - QUANTIZE_BITS\n const length = Math.min(data.length, pixels.width * pixels.height * 4)\n\n for (let offset = 0; offset + 3 < length; offset += 4) {\n const r = data[offset]\n const g = data[offset + 1]\n const b = data[offset + 2]\n const a = data[offset + 3]\n\n if (a < MINIMUM_ALPHA) continue\n\n const lightness = (Math.max(r, g, b) + Math.min(r, g, b)) / 510\n\n if (lightness < MINIMUM_LIGHTNESS || lightness > MAXIMUM_LIGHTNESS) continue\n\n const key =\n ((r >> shift) << (QUANTIZE_BITS * 2)) | ((g >> shift) << QUANTIZE_BITS) | (b >> shift)\n const bucket = buckets.get(key)\n\n if (bucket) {\n bucket.count++\n bucket.r += r\n bucket.g += g\n bucket.b += b\n } else {\n buckets.set(key, {count: 1, r, g, b})\n }\n }\n\n const candidates: Candidate[] = []\n\n for (const bucket of buckets.values()) {\n const r = Math.round(bucket.r / bucket.count)\n const g = Math.round(bucket.g / bucket.count)\n const b = Math.round(bucket.b / bucket.count)\n const {saturation, lightness} = saturationAndLightness(r, g, b)\n\n candidates.push({hex: rgbToHex(r, g, b), population: bucket.count, saturation, lightness})\n }\n\n return candidates\n}\n\nfunction matchesTarget(candidate: Candidate, target: Target): boolean {\n return (\n candidate.lightness >= target.lightness[1] &&\n candidate.lightness <= target.lightness[2] &&\n candidate.saturation >= target.saturation[1] &&\n candidate.saturation <= target.saturation[2]\n )\n}\n\nfunction scoreCandidate(candidate: Candidate, target: Target, maximumPopulation: number): number {\n return (\n SATURATION_WEIGHT * (1 - Math.abs(candidate.saturation - target.saturation[0])) +\n LIGHTNESS_WEIGHT * (1 - Math.abs(candidate.lightness - target.lightness[0])) +\n POPULATION_WEIGHT * (candidate.population / maximumPopulation)\n )\n}\n\nfunction saturationAndLightness(r: number, g: number, b: number) {\n const max = Math.max(r, g, b) / 255\n const min = Math.min(r, g, b) / 255\n const lightness = (max + min) / 2\n const delta = max - min\n const saturation =\n delta === 0 ? 0 : lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n\n return {saturation, lightness}\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`\n}\n\n/** Reads `#rgb` and `#rrggbb` alike — stored palettes may hold either */\nfunction hexToRgb(hex: string): [number, number, number] {\n const expanded =\n hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1)\n\n return [\n parseInt(expanded.slice(0, 2), 16),\n parseInt(expanded.slice(2, 4), 16),\n parseInt(expanded.slice(4, 6), 16),\n ]\n}\n\n/** Mixes `amount` of `color` into `base` */\nfunction tint(base: string, color: string, amount: number): string {\n const [r1, g1, b1] = hexToRgb(base)\n const [r2, g2, b2] = hexToRgb(color)\n\n return rgbToHex(\n Math.round(r1 + (r2 - r1) * amount),\n Math.round(g1 + (g2 - g1) * amount),\n Math.round(b1 + (b2 - b1) * amount),\n )\n}\n\n/** How much of the palette tints each scheme's stock background */\nconst BACKGROUND_TINT = {light: 0.08, dark: 0.3}\n\n/**\n * The WCAG AA contrast for normal text — what a button label needs against\n * the button. Sanity UI labels solid buttons in white on the accent's 500\n * tint in the light scheme and in black on its 400 tint in the dark scheme\n * (lighter than the accent, so an accent that passes against black passes\n * there too).\n */\nconst MINIMUM_ACCENT_CONTRAST = 4.5\nconst BUTTON_LABEL: Record<ThemeColorSchemeKey, string> = {light: '#ffffff', dark: '#000000'}\nconst ACCENT_LIGHTNESS_STEP = 0.01\n\nfunction contrastRatio(a: string, b: string): number {\n const [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x)\n\n return (lighter + 0.05) / (darker + 0.05)\n}\n\n/**\n * Makes an accent readable as a button in the given scheme: a light scheme\n * accent is darkened until white text on it reaches AA contrast, a dark\n * scheme accent lightened until black text does — as far as the accent's\n * hue and saturation allow. Accents that already pass come back untouched.\n *\n * @internal\n */\nexport function readableAccent(hex: string, scheme: ThemeColorSchemeKey): string {\n const label = BUTTON_LABEL[scheme]\n const {h, s} = hexToHsl(hex)\n let {l} = hexToHsl(hex)\n let current = hex\n\n while (contrastRatio(current, label) < MINIMUM_ACCENT_CONTRAST) {\n const next = scheme === 'light' ? l - ACCENT_LIGHTNESS_STEP : l + ACCENT_LIGHTNESS_STEP\n\n if (next < 0 || next > 1) break\n\n l = next\n current = hslToHex({h, s, l})\n }\n\n return current\n}\n\n/** The accent a variant gives a scheme, made readable on a button */\nfunction variantAccent(\n palette: ImagePalette,\n variant: ImagePaletteVariant | null,\n scheme: ThemeColorSchemeKey,\n): string | null {\n const swatch =\n (variant ? palette[variant] : null) ??\n palette.vibrant ??\n (scheme === 'light' ? palette.darkVibrant : palette.lightVibrant) ??\n palette.dominant\n\n return swatch === null ? null : readableAccent(swatch, scheme)\n}\n\n/**\n * Derives theme options from an image palette: the accent of both schemes is\n * the swatch of the chosen variant — or, without one, the vibrant swatch,\n * each scheme falling back to the vibrant swatch of its own end of the scale —\n * adjusted until it is readable as a button. The muted swatch becomes the text color, and the light and dark muted\n * swatches tint the stock backgrounds of their scheme — enough to carry the\n * image's tone without giving up a usable canvas. Whatever the palette lacks\n * stays at the theme defaults.\n *\n * @internal\n */\nexport function optionsFromImagePalette(\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const options: BuildThemeOptions = {}\n\n const light = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'light'),\n palette.muted ?? palette.darkMuted,\n palette.lightMuted ?? palette.muted ?? palette.lightVibrant,\n 'light',\n )\n const dark = schemeFromPalette(\n variantAccent(palette, variant ?? null, 'dark'),\n palette.muted ?? palette.lightMuted,\n palette.darkMuted ?? palette.muted ?? palette.darkVibrant,\n 'dark',\n )\n\n if (light) options.light = light\n if (dark) options.dark = dark\n\n return options\n}\n\nfunction schemeFromPalette(\n accent: string | null,\n text: string | null,\n backgroundTint: string | null,\n scheme: 'light' | 'dark',\n): SchemeThemeOptions | null {\n const options: SchemeThemeOptions = {}\n\n if (accent) options.accent = accent\n if (text) options.text = text\n if (backgroundTint) {\n options.background = tint(DEFAULT_BACKGROUND[scheme], backgroundTint, BACKGROUND_TINT[scheme])\n }\n\n return Object.keys(options).length > 0 ? options : null\n}\n\n/**\n * Applies an image palette to existing theme options: the colors the palette\n * provides replace the current ones scheme by scheme, and whatever it does\n * not touch — like the contrast — stays as it is.\n *\n * @internal\n */\nexport function applyImagePalette(\n options: BuildThemeOptions,\n palette: ImagePalette,\n variant?: ImagePaletteVariant,\n): BuildThemeOptions {\n const derived = optionsFromImagePalette(palette, variant)\n const next: BuildThemeOptions = {...options}\n\n for (const scheme of SCHEMES) {\n if (derived[scheme]) next[scheme] = {...options[scheme], ...derived[scheme]}\n }\n\n return next\n}\n\n/**\n * The variant a theme is built around: the one whose swatch is the theme's\n * accent, or `null` when the accent comes from somewhere else.\n *\n * @internal\n */\nexport function currentImageVariant(\n options: BuildThemeOptions,\n palette: ImagePalette,\n): ImagePaletteVariant | null {\n const accent = options.light?.accent\n\n if (!accent) return null\n\n return (\n IMAGE_PALETTE_VARIANTS.find(\n (variant) => palette[variant] !== null && variantAccent(palette, variant, 'light') === accent,\n ) ?? null\n )\n}\n\n/**\n * How much a swatch has going for it as an accent: saturation first, then\n * closeness to mid lightness — the range where an accent reads best.\n */\nfunction interest(hex: string): number {\n const {s, l} = hexToHsl(hex)\n\n return 0.1 + 0.55 * s + 0.35 * (1 - Math.abs(l - 0.5) * 2)\n}\n\n/**\n * Picks a variant for the \"I'm feeling lucky\" button: a random one, weighted\n * by how interesting its swatch is as an accent, and never the one the theme\n * already uses while there are others to pick from.\n *\n * @internal\n */\nexport function pickLuckyVariant(\n palette: ImagePalette,\n options: {exclude?: ImagePaletteVariant | null; random?: () => number} = {},\n): ImagePaletteVariant | null {\n const {exclude = null, random = Math.random} = options\n let candidates = IMAGE_PALETTE_VARIANTS.filter((variant) => palette[variant] !== null)\n\n if (candidates.length > 1) {\n candidates = candidates.filter((variant) => variant !== exclude)\n }\n\n if (candidates.length === 0) return null\n\n const weights = candidates.map((variant) => interest(palette[variant]!))\n const total = weights.reduce((sum, weight) => sum + weight, 0)\n let pick = random() * total\n\n for (const [index, variant] of candidates.entries()) {\n pick -= weights[index]\n\n if (pick < 0) return variant\n }\n\n return candidates.at(-1) ?? null\n}\n\n/** A theme title for the image a palette came from, e.g. `sunset-beach.jpg` → `sunset beach` @internal */\nexport function titleFromFileName(name: string): string {\n return (\n name\n .replace(/\\.[^.]+$/, '')\n .replace(/[-_]+/g, ' ')\n .trim() || 'Image theme'\n )\n}\n","import {\n type BuildThemeOptions,\n DEFAULT_ACCENT,\n DEFAULT_BACKGROUND,\n DEFAULT_CONTRAST,\n deriveTextColor,\n type ResolvedSchemeOptions,\n resolveThemeOptions,\n SCHEMES,\n type SchemeThemeOptions,\n} from '../theme/options'\n\n/**\n * Reduces theme options to the minimal object that recreates them through\n * `buildTheme`: in each scheme the accent is dropped when it is the stock\n * one, the text color when it matches the one derived from the accent, and\n * the background and contrast when they match the defaults — and a scheme\n * with nothing left is dropped altogether. Options that boil down to the\n * stock Studio theme reduce to `null` — they need nothing from this package.\n *\n * @internal\n */\nexport function minimizeOptions(options: BuildThemeOptions): BuildThemeOptions | null {\n const resolved = resolveThemeOptions(options)\n const minimized: BuildThemeOptions = {}\n\n for (const scheme of SCHEMES) {\n const values = resolved[scheme]\n const kept: SchemeThemeOptions = {}\n\n if (values.accent !== DEFAULT_ACCENT) kept.accent = values.accent\n if (values.text !== deriveTextColor(values.accent)) kept.text = values.text\n if (values.background !== DEFAULT_BACKGROUND[scheme]) kept.background = values.background\n if (values.contrast !== DEFAULT_CONTRAST) kept.contrast = values.contrast\n\n if (Object.keys(kept).length > 0) minimized[scheme] = kept\n }\n\n return Object.keys(minimized).length === 0 ? null : minimized\n}\n\n/**\n * Whether two sets of theme options generate the same theme, comparing their\n * resolved forms so that derived and explicit values (and hex casing) don't\n * matter.\n *\n * @internal\n */\nexport function sameOptions(a: BuildThemeOptions, b: BuildThemeOptions): boolean {\n const left = resolveThemeOptions(a)\n const right = resolveThemeOptions(b)\n\n return SCHEMES.every((scheme) => sameScheme(left[scheme], right[scheme]))\n}\n\nfunction sameScheme(a: ResolvedSchemeOptions, b: ResolvedSchemeOptions): boolean {\n return (\n a.accent === b.accent &&\n a.text === b.text &&\n a.background === b.background &&\n a.contrast === b.contrast\n )\n}\n","import type {BuildThemeOptions} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport type {ImagePalette} from './imagePalette'\nimport {sameOptions} from './options'\n\n/**\n * Where a theme in the themer list comes from: the theme the Studio config\n * was generated from, one of the presets defined in code, or one the user\n * added in the tool. Only custom themes are editable — the others are\n * duplicated into a custom theme first.\n */\ntype ThemerThemeSource = 'config' | 'preset' | 'custom'\n\n/** A theme the themer tool lists @internal */\nexport interface ThemerTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n source: ThemerThemeSource\n /** The palette of the image the theme's colors were taken from, for custom themes */\n palette?: ImagePalette\n}\n\n/** A theme the user added in the tool, as persisted @internal */\nexport interface CustomTheme {\n slug: string\n title: string\n options: BuildThemeOptions\n /** The palette of the image the theme's colors were taken from */\n palette?: ImagePalette\n}\n\n/** The persisted themer state @internal */\nexport interface ThemerState {\n /**\n * The slug of the applied theme — `null` applies nothing on top of the\n * Studio's configured theme\n */\n active: string | null\n /** The themes the user added */\n custom: CustomTheme[]\n /** The slugs of the themes the user removed from the list */\n removed: string[]\n /**\n * The slugs of the themes in the order the user arranged them — themes not\n * in it (new presets, themes added since) follow in their default order\n */\n order: string[]\n}\n\n/** The slug of the theme the Studio config was generated from @internal */\nexport const CONFIG_SLUG = 'config'\n\n/** The title of the theme the Studio config was generated from */\nconst CONFIG_TITLE = 'Studio config'\n\n/** The title new themes start out with @internal */\nexport const UNTITLED_THEME = 'Untitled theme'\n\n/** @internal */\nexport const initialThemerState: ThemerState = {active: null, custom: [], removed: [], order: []}\n\n/** The themes the tool works with, derived from the persisted state @internal */\nexport interface ResolvedThemes {\n /** The themes to pick from, in list order */\n themes: ThemerTheme[]\n /** The removed themes, which can be restored */\n removed: ThemerTheme[]\n /** The applied theme */\n active: ThemerTheme\n}\n\n/**\n * Resolves the list of themes: the configured theme first, then the presets\n * (minus the ones that would only repeat the configured theme), then the\n * user's own themes — rearranged into the order the user dragged them into,\n * with themes that order does not know about kept in that default order after\n * the ones it does. Removed themes are set aside so they can be restored, and\n * the active theme falls back to the configured one when its slug no longer\n * resolves.\n *\n * @internal\n */\nexport function resolveThemes(state: ThemerState, baseOptions: BuildThemeOptions): ResolvedThemes {\n const config: ThemerTheme = {\n slug: CONFIG_SLUG,\n title: CONFIG_TITLE,\n options: baseOptions,\n source: 'config',\n }\n\n const all: ThemerTheme[] = [config]\n\n for (const preset of presets) {\n if (!sameOptions(preset.options, baseOptions)) {\n all.push({slug: preset.slug, title: preset.title, options: preset.options, source: 'preset'})\n }\n }\n\n for (const theme of state.custom) {\n all.push({\n slug: theme.slug,\n title: theme.title,\n options: theme.options,\n source: 'custom',\n ...(theme.palette ? {palette: theme.palette} : {}),\n })\n }\n\n const position = new Map(state.order.map((slug, index) => [slug, index]))\n const rank = (theme: ThemerTheme) => position.get(theme.slug) ?? state.order.length\n\n // A stable sort: the themes the order does not know about keep their default order\n all.sort((a, b) => rank(a) - rank(b))\n\n const removedSlugs = new Set(state.removed)\n const themes = all.filter((theme) => !removedSlugs.has(theme.slug))\n const removed = all.filter((theme) => removedSlugs.has(theme.slug))\n const active = themes.find((theme) => theme.slug === state.active) ?? config\n\n return {themes, removed, active}\n}\n\n/**\n * Creates a custom theme with a slug that cannot collide with the presets or\n * the configured theme.\n *\n * @internal\n */\nexport function createCustomTheme(\n title: string,\n options: BuildThemeOptions,\n palette?: ImagePalette,\n): CustomTheme {\n return {slug: `custom-${randomId()}`, title, options, ...(palette ? {palette} : {})}\n}\n\n/** The title of a duplicated theme @internal */\nexport function duplicateTitle(title: string): string {\n return `${displayTitle(title)} copy`\n}\n\n/** The title to show for a theme, falling back when the user cleared it @internal */\nexport function displayTitle(title: string): string {\n return title.trim() || UNTITLED_THEME\n}\n\nfunction randomId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`\n}\n"],"mappings":";;;AAkDA,MAAa,gBAAgB,cAAyC,IAAI;;AAG1E,SAAgB,YAAgC;CAC9C,IAAM,UAAU,WAAW,aAAa;CAExC,IAAI,CAAC,SACH,MAAU,MAAM,uDAAuD;CAGzE,OAAO;AACT;;AC3BA,MAAa,qBAAwC;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAWa,yBAAgD;CAC3D;CACA;CACA;CACA;CACA;CACA;AACF,GAGa,uBAAwD;CACnE,UAAU;CACV,SAAS;CACT,cAAc;CACd,aAAa;CACb,OAAO;CACP,YAAY;CACZ,WAAW;AACb,GAiCM,UAA8E;CAClF,CAAC,WAAW;EAAC,WAAW;GAAC;GAAK;GAAK;EAAG;EAAG,YAAY;GAAC;GAAG;GAAM;EAAC;CAAC,CAAC;CAClE,CAAC,gBAAgB;EAAC,WAAW;GAAC;GAAM;GAAM;EAAC;EAAG,YAAY;GAAC;GAAG;GAAM;EAAC;CAAC,CAAC;CACvE,CAAC,eAAe;EAAC,WAAW;GAAC;GAAM;GAAG;EAAI;EAAG,YAAY;GAAC;GAAG;GAAM;EAAC;CAAC,CAAC;CACtE,CAAC,SAAS;EAAC,WAAW;GAAC;GAAK;GAAK;EAAG;EAAG,YAAY;GAAC;GAAK;GAAG;EAAG;CAAC,CAAC;CACjE,CAAC,cAAc;EAAC,WAAW;GAAC;GAAM;GAAM;EAAC;EAAG,YAAY;GAAC;GAAK;GAAG;EAAG;CAAC,CAAC;CACtE,CAAC,aAAa;EAAC,WAAW;GAAC;GAAM;GAAG;EAAI;EAAG,YAAY;GAAC;GAAK;GAAG;EAAG;CAAC,CAAC;AACvE;;;;;;;;;;;AA0BA,SAAgB,oBAAoB,QAAiC;CACnE,IAAM,aAAa,SAAS,MAAM,GAC5B,UAAwB;EAC5B,UAAU;EACV,SAAS;EACT,cAAc;EACd,aAAa;EACb,OAAO;EACP,YAAY;EACZ,WAAW;CACb;CAEA,IAAI,WAAW,WAAW,GAAG,OAAO;CAEpC,IAAM,oBAAoB,KAAK,IAAI,GAAG,WAAW,KAAK,cAAc,UAAU,UAAU,CAAC,GACnF,0BAAU,IAAI,IAAY;CAEhC,KAAK,IAAM,CAAC,KAAK,WAAW,SAAS;EACnC,IAAI,OAAyB,MACzB,YAAY;EAEhB,KAAK,IAAM,aAAa,YAAY;GAClC,IAAI,QAAQ,IAAI,UAAU,GAAG,KAAK,CAAC,cAAc,WAAW,MAAM,GAAG;GAErE,IAAM,QAAQ,eAAe,WAAW,QAAQ,iBAAiB;GAEjE,AAAI,QAAQ,cACV,OAAO,WACP,YAAY;EAEhB;EAEA,AAAI,SACF,QAAQ,OAAO,KAAK,KACpB,QAAQ,IAAI,KAAK,GAAG;CAExB;CAMA,OAJA,QAAQ,WAAW,WAAW,QAAQ,MAAM,cAC1C,UAAU,aAAa,KAAK,aAAa,YAAY,IACvD,CAAC,CAAC,KAEK;AACT;;;;;;AAOA,SAAS,SAAS,QAAgC;CAChD,IAAM,EAAC,SAAQ,QACT,0BAAU,IAAI,IAAoB,GAElC,SAAS,KAAK,IAAI,KAAK,QAAQ,OAAO,QAAQ,OAAO,SAAS,CAAC;CAErE,KAAK,IAAI,SAAS,GAAG,SAAS,IAAI,QAAQ,UAAU,GAAG;EACrD,IAAM,IAAI,KAAK,SACT,IAAI,KAAK,SAAS,IAClB,IAAI,KAAK,SAAS;EAGxB,IAFU,KAAK,SAAS,KAEhB,KAAe;EAEvB,IAAM,aAAa,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,GAAG,CAAC,KAAK;EAE5D,IAAI,YAAY,OAAqB,YAAY,KAAmB;EAEpE,IAAM,MACF,KAAK,KAAW,KAAwB,KAAK,KAAU,IAAkB,KAAK,GAC5E,SAAS,QAAQ,IAAI,GAAG;EAE9B,AAAI,UACF,OAAO,SACP,OAAO,KAAK,GACZ,OAAO,KAAK,GACZ,OAAO,KAAK,KAEZ,QAAQ,IAAI,KAAK;GAAC,OAAO;GAAG;GAAG;GAAG;EAAC,CAAC;CAExC;CAEA,IAAM,aAA0B,CAAC;CAEjC,KAAK,IAAM,UAAU,QAAQ,OAAO,GAAG;EACrC,IAAM,IAAI,KAAK,MAAM,OAAO,IAAI,OAAO,KAAK,GACtC,IAAI,KAAK,MAAM,OAAO,IAAI,OAAO,KAAK,GACtC,IAAI,KAAK,MAAM,OAAO,IAAI,OAAO,KAAK,GACtC,EAAC,YAAY,cAAa,uBAAuB,GAAG,GAAG,CAAC;EAE9D,WAAW,KAAK;GAAC,KAAK,SAAS,GAAG,GAAG,CAAC;GAAG,YAAY,OAAO;GAAO;GAAY;EAAS,CAAC;CAC3F;CAEA,OAAO;AACT;AAEA,SAAS,cAAc,WAAsB,QAAyB;CACpE,OACE,UAAU,aAAa,OAAO,UAAU,MACxC,UAAU,aAAa,OAAO,UAAU,MACxC,UAAU,cAAc,OAAO,WAAW,MAC1C,UAAU,cAAc,OAAO,WAAW;AAE9C;AAEA,SAAS,eAAe,WAAsB,QAAgB,mBAAmC;CAC/F,OACE,KAAqB,IAAI,KAAK,IAAI,UAAU,aAAa,OAAO,WAAW,EAAE,KAC7E,OAAoB,IAAI,KAAK,IAAI,UAAU,YAAY,OAAO,UAAU,EAAE,KAC1E,MAAqB,UAAU,aAAa;AAEhD;AAEA,SAAS,uBAAuB,GAAW,GAAW,GAAW;CAC/D,IAAM,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI,KAC1B,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI,KAC1B,aAAa,MAAM,OAAO,GAC1B,QAAQ,MAAM;CAIpB,OAAO;EAAC,YAFN,UAAU,IAAI,IAAI,YAAY,KAAM,SAAS,IAAI,MAAM,OAAO,SAAS,MAAM;EAE3D;CAAS;AAC/B;AAEA,SAAS,SAAS,GAAW,GAAW,GAAmB;CACzD,OAAO,IAAI;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,YAAY,QAAQ,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AACtF;;AAGA,SAAS,SAAS,KAAuC;CACvD,IAAM,WACJ,IAAI,WAAW,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,MAAM,CAAC;CAE3F,OAAO;EACL,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;EACjC,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;EACjC,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;CACnC;AACF;;AAGA,SAAS,KAAK,MAAc,OAAe,QAAwB;CACjE,IAAM,CAAC,IAAI,IAAI,MAAM,SAAS,IAAI,GAC5B,CAAC,IAAI,IAAI,MAAM,SAAS,KAAK;CAEnC,OAAO,SACL,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,GAClC,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,GAClC,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,CACpC;AACF;;AAGA,MAAM,kBAAkB;CAAC,OAAO;CAAM,MAAM;AAAG,GAUzC,eAAoD;CAAC,OAAO;CAAW,MAAM;AAAS,GACtF,wBAAwB;AAE9B,SAAS,cAAc,GAAW,GAAmB;CACnD,IAAM,CAAC,SAAS,UAAU,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAE3F,QAAQ,UAAU,QAAS,SAAS;AACtC;;;;;;;;;AAUA,SAAgB,eAAe,KAAa,QAAqC;CAC/E,IAAM,QAAQ,aAAa,SACrB,EAAC,GAAG,MAAK,SAAS,GAAG,GACvB,EAAC,MAAK,SAAS,GAAG,GAClB,UAAU;CAEd,OAAO,cAAc,SAAS,KAAK,IAAI,MAAyB;EAC9D,IAAM,OAAO,WAAW,UAAU,IAAI,wBAAwB,IAAI;EAElE,IAAI,OAAO,KAAK,OAAO,GAAG;EAG1B,AADA,IAAI,MACJ,UAAU,SAAS;GAAC;GAAG;GAAG;EAAC,CAAC;CAC9B;CAEA,OAAO;AACT;;AAGA,SAAS,cACP,SACA,SACA,QACe;CACf,IAAM,UACH,UAAU,QAAQ,WAAW,SAC9B,QAAQ,YACP,WAAW,UAAU,QAAQ,cAAc,QAAQ,iBACpD,QAAQ;CAEV,OAAO,WAAW,OAAO,OAAO,eAAe,QAAQ,MAAM;AAC/D;;;;;;;;;;;;AAaA,SAAgB,wBACd,SACA,SACmB;CACnB,IAAM,UAA6B,CAAC,GAE9B,QAAQ,kBACZ,cAAc,SAAS,WAAW,MAAM,OAAO,GAC/C,QAAQ,SAAS,QAAQ,WACzB,QAAQ,cAAc,QAAQ,SAAS,QAAQ,cAC/C,OACF,GACM,OAAO,kBACX,cAAc,SAAS,WAAW,MAAM,MAAM,GAC9C,QAAQ,SAAS,QAAQ,YACzB,QAAQ,aAAa,QAAQ,SAAS,QAAQ,aAC9C,MACF;CAKA,OAHI,UAAO,QAAQ,QAAQ,QACvB,SAAM,QAAQ,OAAO,OAElB;AACT;AAEA,SAAS,kBACP,QACA,MACA,gBACA,QAC2B;CAC3B,IAAM,UAA8B,CAAC;CAQrC,OANI,WAAQ,QAAQ,SAAS,SACzB,SAAM,QAAQ,OAAO,OACrB,mBACF,QAAQ,aAAa,KAAK,mBAAmB,SAAS,gBAAgB,gBAAgB,OAAO,IAGxF,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,UAAU;AACrD;;;;;;;;AASA,SAAgB,kBACd,SACA,SACA,SACmB;CACnB,IAAM,UAAU,wBAAwB,SAAS,OAAO,GAClD,OAA0B,EAAC,GAAG,QAAO;CAE3C,KAAK,IAAM,UAAU,SACnB,AAAI,QAAQ,YAAS,KAAK,UAAU;EAAC,GAAG,QAAQ;EAAS,GAAG,QAAQ;CAAO;CAG7E,OAAO;AACT;;;;;;;AAQA,SAAgB,oBACd,SACA,SAC4B;CAC5B,IAAM,SAAS,QAAQ,OAAO;CAI9B,OAFK,SAGH,uBAAuB,MACpB,YAAY,QAAQ,aAAa,QAAQ,cAAc,SAAS,SAAS,OAAO,MAAM,MACzF,KAAK,OALa;AAOtB;;;;;AAMA,SAAS,SAAS,KAAqB;CACrC,IAAM,EAAC,GAAG,MAAK,SAAS,GAAG;CAE3B,OAAO,KAAM,MAAO,IAAI,OAAQ,IAAI,KAAK,IAAI,IAAI,EAAG,IAAI;AAC1D;;;;;;;;AASA,SAAgB,iBACd,SACA,UAAyE,CAAC,GAC9C;CAC5B,IAAM,EAAC,UAAU,MAAM,SAAS,KAAK,WAAU,SAC3C,aAAa,uBAAuB,QAAQ,YAAY,QAAQ,aAAa,IAAI;CAMrF,IAJI,WAAW,SAAS,MACtB,aAAa,WAAW,QAAQ,YAAY,YAAY,OAAO,IAG7D,WAAW,WAAW,GAAG,OAAO;CAEpC,IAAM,UAAU,WAAW,KAAK,YAAY,SAAS,QAAQ,QAAS,CAAC,GACjE,QAAQ,QAAQ,QAAQ,KAAK,WAAW,MAAM,QAAQ,CAAC,GACzD,OAAO,OAAO,IAAI;CAEtB,KAAK,IAAM,CAAC,OAAO,YAAY,WAAW,QAAQ,GAGhD,IAFA,QAAQ,QAAQ,QAEZ,OAAO,GAAG,OAAO;CAGvB,OAAO,WAAW,GAAG,EAAE,KAAK;AAC9B;;AAGA,SAAgB,kBAAkB,MAAsB;CACtD,OACE,KACG,QAAQ,YAAY,EAAE,CAAC,CACvB,QAAQ,UAAU,GAAG,CAAC,CACtB,KAAK,KAAK;AAEjB;;;;;;;;;;;AC3dA,SAAgB,gBAAgB,SAAsD;CACpF,IAAM,WAAW,oBAAoB,OAAO,GACtC,YAA+B,CAAC;CAEtC,KAAK,IAAM,UAAU,SAAS;EAC5B,IAAM,SAAS,SAAS,SAClB,OAA2B,CAAC;EAOlC,AALI,OAAO,WAAW,mBAAgB,KAAK,SAAS,OAAO,SACvD,OAAO,SAAS,gBAAgB,OAAO,MAAM,MAAG,KAAK,OAAO,OAAO,OACnE,OAAO,eAAe,mBAAmB,YAAS,KAAK,aAAa,OAAO,aAC3E,OAAO,aAAA,OAA+B,KAAK,WAAW,OAAO,WAE7D,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,MAAG,UAAU,UAAU;CACxD;CAEA,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,WAAW,IAAI,OAAO;AACtD;;;;;;;;AASA,SAAgB,YAAY,GAAsB,GAA+B;CAC/E,IAAM,OAAO,oBAAoB,CAAC,GAC5B,QAAQ,oBAAoB,CAAC;CAEnC,OAAO,QAAQ,OAAO,WAAW,WAAW,KAAK,SAAS,MAAM,OAAO,CAAC;AAC1E;AAEA,SAAS,WAAW,GAA0B,GAAmC;CAC/E,OACE,EAAE,WAAW,EAAE,UACf,EAAE,SAAS,EAAE,QACb,EAAE,eAAe,EAAE,cACnB,EAAE,aAAa,EAAE;AAErB;;ACXA,MAAa,cAAc,UAMd,iBAAiB,kBAGjB,qBAAkC;CAAC,QAAQ;CAAM,QAAQ,CAAC;CAAG,SAAS,CAAC;CAAG,OAAO,CAAC;AAAC;;;;;;;;;;;;AAuBhG,SAAgB,cAAc,OAAoB,aAAgD;CAChG,IAAM,SAAsB;EAC1B,MAAM;EACN,OAAO;EACP,SAAS;EACT,QAAQ;CACV,GAEM,MAAqB,CAAC,MAAM;CAElC,KAAK,IAAM,UAAU,SACnB,AAAK,YAAY,OAAO,SAAS,WAAW,KAC1C,IAAI,KAAK;EAAC,MAAM,OAAO;EAAM,OAAO,OAAO;EAAO,SAAS,OAAO;EAAS,QAAQ;CAAQ,CAAC;CAIhG,KAAK,IAAM,SAAS,MAAM,QACxB,IAAI,KAAK;EACP,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,SAAS,MAAM;EACf,QAAQ;EACR,GAAI,MAAM,UAAU,EAAC,SAAS,MAAM,QAAO,IAAI,CAAC;CAClD,CAAC;CAGH,IAAM,WAAW,IAAI,IAAI,MAAM,MAAM,KAAK,MAAM,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,GAClE,QAAQ,UAAuB,SAAS,IAAI,MAAM,IAAI,KAAK,MAAM,MAAM;CAG7E,IAAI,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;CAEpC,IAAM,eAAe,IAAI,IAAI,MAAM,OAAO,GACpC,SAAS,IAAI,QAAQ,UAAU,CAAC,aAAa,IAAI,MAAM,IAAI,CAAC;CAIlE,OAAO;EAAC;EAAQ,SAHA,IAAI,QAAQ,UAAU,aAAa,IAAI,MAAM,IAAI,CAGjD;EAAS,QAFV,OAAO,MAAM,UAAU,MAAM,SAAS,MAAM,MAAM,KAAK;CAEvC;AACjC;;;;;;;AAQA,SAAgB,kBACd,OACA,SACA,SACa;CACb,OAAO;EAAC,MAAM,UAAU,SAAS;EAAK;EAAO;EAAS,GAAI,UAAU,EAAC,QAAO,IAAI,CAAC;CAAE;AACrF;;AAGA,SAAgB,eAAe,OAAuB;CACpD,OAAO,GAAG,aAAa,KAAK,EAAE;AAChC;;AAGA,SAAgB,aAAa,OAAuB;CAClD,OAAO,MAAM,KAAK,KAAA;AACpB;AAEA,SAAS,WAAmB;CAK1B,OAJI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,aACzD,OAAO,WAAW,IAGpB,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;AAC5E"}
\ No newline at end of file Index: package/dist/tool.d.ts.map
===================================================================
--- package/dist/tool.d.ts.map
+++ package/dist/tool.d.ts.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"tool.d.ts","names":[],"sources":["../src/tool/plugin.tsx"],"mappings":";;;;;;;;;UAciB;;;;;;;;;;;;;;;EAef,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+BE,6BAAU,cAAA"}
\ No newline at end of file
+{"version":3,"file":"tool.d.ts","names":[],"sources":["../src/tool/plugin.tsx"],"mappings":";;;;;;;;;UAciB;;;;;;;;;;;;;;;EAef,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BE,6BAAU,cAAA"}
\ No newline at end of file Index: package/dist/tool.js.map
===================================================================
--- package/dist/tool.js.map
+++ package/dist/tool.js.map
@@ -1,1 +1,1 @@
-{"version":3,"file":"tool.js","names":["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 BuildThemeOptions,\n MAXIMUM_CONTRAST,\n MINIMUM_CONTRAST,\n SCHEMES,\n SchemeThemeOptions,\n} from '../theme/options'\nimport {presets} from '../theme/presets'\nimport {IMAGE_PALETTE_KEYS, ImagePalette} from './imagePalette'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n CustomTheme,\n displayTitle,\n initialThemerState,\n 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, SnapshotFrom, stateIn} from 'xstate'\n\nimport {BuildThemeOptions} from '../theme/options'\nimport {ImagePalette} from './imagePalette'\nimport {writeStoredState} from './storage'\nimport {\n CONFIG_SLUG,\n createCustomTheme,\n duplicateTitle,\n resolveThemes,\n 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 {BuildThemeOptions} from '../theme/options'\nimport {ThemerContext, ThemerContextValue, ThemerView} from './context'\nimport {selectStoredState, ThemerInput, themerMachine, ThemerSnapshot} from './machine'\nimport {readStoredState} from './storage'\nimport {resolveThemes, 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 {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 {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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"}
\ No newline at end of file
+{"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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"}
\ No newline at end of file Index: package/README.md
===================================================================
--- package/README.md
+++ package/README.md
@@ -82,5 +82,5 @@
```
## License
-MIT © Sanity.io — see [LICENSE](https://github.com/sanity-io/ui/blob/main/LICENSE)
+MIT © Sanity.io — see [LICENSE](https://github.com/sanity-io/plugins/blob/main/LICENSE) Index: package/dist/index.d.ts
===================================================================
--- package/dist/index.d.ts
+++ package/dist/index.d.ts
@@ -32,9 +32,9 @@
* ```
*
* @public
*/
-export declare function buildPalette(options?: BuildThemeOptions): GeneratedColorPalettes;
+declare function buildPalette(options?: BuildThemeOptions): GeneratedColorPalettes;
/**
* Generates a Studio theme from a handful of colors per color scheme, by
* replacing the color palette that the `buildTheme` from `@sanity/ui/theme`
* otherwise fills with `@sanity/color`: in each scheme the accent color
@@ -72,9 +72,9 @@
* returns the exact same colors as `buildTheme()` from `@sanity/ui/theme`.
*
* @public
*/
-export declare function buildTheme(options?: BuildThemeOptions): RootTheme;
+declare function buildTheme(options?: BuildThemeOptions): RootTheme;
/**
* A preset theme: a named set of {@link BuildThemeOptions} ready to pass to
* `buildTheme`.
*
@@ -100,7 +100,7 @@
* ```
*
* @public
*/
-export declare const presets: ThemePreset[];
-export type { BuildThemeOptions, GeneratedColorPalette, GeneratedColorPalettes, SchemeThemeOptions, ThemePreset };
+declare const presets: ThemePreset[];
+export { type BuildThemeOptions, type GeneratedColorPalette, type GeneratedColorPalettes, type SchemeThemeOptions, type ThemePreset, buildPalette, buildTheme, presets };
//# sourceMappingURL=index.d.ts.map
\ No newline at end of file Index: package/dist/legacy.d.ts
===================================================================
--- package/dist/legacy.d.ts
+++ package/dist/legacy.d.ts
@@ -4,77 +4,78 @@
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const buildThemeFromUrl: typeof themerLegacy.buildThemeFromUrl;
+declare const buildThemeFromUrl: typeof themerLegacy.buildThemeFromUrl;
/**
* @deprecated Import `createTheme` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const createTheme: typeof themerLegacy.createTheme;
+declare const createTheme: typeof themerLegacy.createTheme;
/**
* @deprecated Import `parseHuesFromUrl` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const parseHuesFromUrl: typeof themerLegacy.parseHuesFromUrl;
+declare const parseHuesFromUrl: typeof themerLegacy.parseHuesFromUrl;
/**
* @deprecated Import `hues` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const hues: typeof themerLegacy.hues;
+declare const hues: typeof themerLegacy.hues;
/**
* @deprecated Import `theme` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const theme: typeof themerLegacy.theme;
+declare const theme: typeof themerLegacy.theme;
/**
* @deprecated Import `presets` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` re-exports it unchanged for now and will be removed
* in `@sanity/[email protected]`.
* @public
*/
-export declare const presets: typeof themerLegacy.presets;
+declare const presets: typeof themerLegacy.presets;
/**
* @deprecated Import `Hue` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type Hue = themerLegacy.Hue;
+type Hue = themerLegacy.Hue;
/**
* @deprecated Import `HueMidPoint` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type HueMidPoint = themerLegacy.HueMidPoint;
+type HueMidPoint = themerLegacy.HueMidPoint;
/**
* @deprecated Import `Hues` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type Hues = themerLegacy.Hues;
+type Hues = themerLegacy.Hues;
/**
* @deprecated Import `LegacyTheme` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type LegacyTheme = themerLegacy.LegacyTheme;
+type LegacyTheme = themerLegacy.LegacyTheme;
/**
* @deprecated Import `PartialHues` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type PartialHues = themerLegacy.PartialHues;
+type PartialHues = themerLegacy.PartialHues;
/**
* @deprecated Import `ThemePreset` from `@sanity/themer-legacy` instead.
* `@sanity/themer/legacy` will be removed in `@sanity/[email protected]`.
* @public
*/
-export type ThemePreset = themerLegacy.ThemePreset;
+type ThemePreset = themerLegacy.ThemePreset;
+export { Hue, HueMidPoint, Hues, LegacyTheme, PartialHues, ThemePreset, buildThemeFromUrl, createTheme, hues, parseHuesFromUrl, presets, theme };
//# sourceMappingURL=legacy.d.ts.map
\ No newline at end of file Index: package/dist/tool.d.ts
===================================================================
--- package/dist/tool.d.ts
+++ package/dist/tool.d.ts
@@ -51,7 +51,7 @@
* notice — use at your own risk.
*
* @alpha
*/
-export declare const themerTool: import("sanity").Plugin<void | ThemerToolOptions>;
-export type { ThemerToolOptions };
+declare const themerTool: import("sanity").Plugin<void | ThemerToolOptions>;
+export { type ThemerToolOptions, themerTool };
//# sourceMappingURL=tool.d.ts.map
\ No newline at end of file