@sanity/themer

0.8.00.8.1
dist/ResizableSidebar.css.ts.vanilla-M9EuPu-q.js.map
~dist/ResizableSidebar.css.ts.vanilla-M9EuPu-q.js.mapModified
+1−1
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":"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"}
\ No newline at end of file