@sanity/themer 0.3.16 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tool.js CHANGED
@@ -1,20 +1,33 @@
1
- import { a as DEFAULT_BACKGROUND_DARK, c as resolveThemeOptions, i as DEFAULT_ACCENT, l as isColor, n as buildTheme, o as DEFAULT_BACKGROUND_LIGHT, r as buildPalette, s as deriveTextColor, t as presets } from "./presets-By3s-D-S.js";
2
- import { COLOR_TINTS } from "@sanity/color";
1
+ import { a as DEFAULT_BACKGROUND, c as resolveThemeOptions, d as hslToHex, f as relativeLuminance, i as DEFAULT_ACCENT, l as isColor, n as buildTheme, o as SCHEMES, r as buildPalette, s as deriveTextColor, t as presets, u as hexToHsl } from "./presets-CqBgS3A-.js";
3
2
  import { c } from "react-compiler-runtime";
4
- import { definePlugin } from "sanity";
5
- import { Box, Button, Card, Flex, Grid, Layer, Stack, Text, ThemeProvider } from "@sanity/ui";
6
- import { createContext, useContext, useEffect, useState } from "react";
7
- import { ClipboardIcon } from "@sanity/icons/Clipboard";
3
+ import { definePlugin, useColorSchemeValue } from "sanity";
4
+ import { Badge, Box, Button, Card, Dialog, Flex, Layer, Stack, Text, TextInput, ThemeProvider, useRootTheme, useTheme_v2 } from "@sanity/ui";
5
+ import { useActor, useSelector } from "@xstate/react";
6
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
7
+ import { assign, not, setup } from "xstate";
8
+ import { styled } from "styled-components";
9
+ import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
8
10
  import { CloseIcon } from "@sanity/icons/Close";
11
+ import { CodeBlockIcon } from "@sanity/icons/CodeBlock";
12
+ import { SplitVerticalIcon } from "@sanity/icons/SplitVertical";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
14
  import { ResetIcon } from "@sanity/icons/Reset";
10
- import { Code } from "@sanity/ui/code";
15
+ import { TrashIcon } from "@sanity/icons/Trash";
16
+ import { SparklesIcon } from "@sanity/icons/Sparkles";
17
+ import { Tooltip } from "@sanity/ui/tooltip";
18
+ import { ImageIcon } from "@sanity/icons/Image";
11
19
  import { useToast } from "@sanity/ui/toast";
20
+ import { AddIcon } from "@sanity/icons/Add";
21
+ import { RestoreIcon } from "@sanity/icons/Restore";
22
+ import { CopyIcon } from "@sanity/icons/Copy";
23
+ import { EditIcon } from "@sanity/icons/Edit";
24
+ import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
25
+ import { Menu, MenuButton, MenuDivider, MenuItem } from "@sanity/ui/menu";
26
+ import { ClipboardIcon } from "@sanity/icons/Clipboard";
27
+ import { Code } from "@sanity/ui/code";
12
28
  import { registerLanguage } from "react-refractor";
13
29
  import typescript from "refractor/typescript";
14
- import { styled } from "styled-components";
15
- import { jsx, jsxs } from "react/jsx-runtime";
16
30
  import { ColorWheelIcon } from "@sanity/icons/ColorWheel";
17
- import { Tooltip } from "@sanity/ui/tooltip";
18
31
  /** @internal */
19
32
  const ThemerContext = createContext(null);
20
33
  /** @internal */
@@ -25,18 +38,21 @@ function useThemer() {
25
38
  }
26
39
  /**
27
40
  * Reduces theme options to the minimal object that recreates them through
28
- * `buildTheme`: the text color is dropped when it matches the one derived
29
- * from the accent, and backgrounds and contrast are dropped when they match
30
- * the defaults. Options that boil down to the stock Studio theme reduce to
31
- * `null` — they need nothing from this package.
41
+ * `buildTheme`: in each scheme the accent is dropped when it is the stock
42
+ * one, the text color when it matches the one derived from the accent, and
43
+ * the background and contrast when they match the defaults — and a scheme
44
+ * with nothing left is dropped altogether. Options that boil down to the
45
+ * stock Studio theme reduce to `null` — they need nothing from this package.
32
46
  *
33
47
  * @internal
34
48
  */
35
49
  function minimizeOptions(options) {
36
- let resolved = resolveThemeOptions(options), minimized = { accent: resolved.accent };
37
- resolved.text !== deriveTextColor(resolved.accent) && (minimized.text = resolved.text);
38
- let dark = resolved.background.dark === DEFAULT_BACKGROUND_DARK ? null : resolved.background.dark, light = resolved.background.light === DEFAULT_BACKGROUND_LIGHT ? null : resolved.background.light;
39
- return (dark !== null || light !== null) && (minimized.background = {}, dark !== null && (minimized.background.dark = dark), light !== null && (minimized.background.light = light)), resolved.contrast !== 85 && (minimized.contrast = resolved.contrast), minimized.accent === DEFAULT_ACCENT && Object.keys(minimized).length === 1 ? null : minimized;
50
+ let resolved = resolveThemeOptions(options), minimized = {};
51
+ for (let scheme of SCHEMES) {
52
+ let values = resolved[scheme], kept = {};
53
+ values.accent !== DEFAULT_ACCENT && (kept.accent = values.accent), values.text !== deriveTextColor(values.accent) && (kept.text = values.text), values.background !== DEFAULT_BACKGROUND[scheme] && (kept.background = values.background), values.contrast !== 85 && (kept.contrast = values.contrast), Object.keys(kept).length > 0 && (minimized[scheme] = kept);
54
+ }
55
+ return Object.keys(minimized).length === 0 ? null : minimized;
40
56
  }
41
57
  /**
42
58
  * Whether two sets of theme options generate the same theme, comparing their
@@ -47,11 +63,1922 @@ function minimizeOptions(options) {
47
63
  */
48
64
  function sameOptions(a, b) {
49
65
  let left = resolveThemeOptions(a), right = resolveThemeOptions(b);
50
- return left.accent === right.accent && left.text === right.text && left.background.dark === right.background.dark && left.background.light === right.background.light && left.contrast === right.contrast;
66
+ return SCHEMES.every((scheme) => sameScheme(left[scheme], right[scheme]));
67
+ }
68
+ function sameScheme(a, b) {
69
+ return a.accent === b.accent && a.text === b.text && a.background === b.background && a.contrast === b.contrast;
70
+ }
71
+ /** The slug of the theme the Studio config was generated from @internal */
72
+ const initialThemerState = {
73
+ active: null,
74
+ custom: [],
75
+ removed: []
76
+ };
77
+ /**
78
+ * Resolves the list of themes: the configured theme first, then the presets
79
+ * (minus the ones that would only repeat the configured theme), then the
80
+ * user's own themes. Removed themes are set aside so they can be restored,
81
+ * and the active theme falls back to the configured one when its slug no
82
+ * longer resolves.
83
+ *
84
+ * @internal
85
+ */
86
+ function resolveThemes(state, baseOptions) {
87
+ let config = {
88
+ slug: "config",
89
+ title: "Studio config",
90
+ options: baseOptions,
91
+ source: "config"
92
+ }, all = [config];
93
+ for (let preset of presets) sameOptions(preset.options, baseOptions) || all.push({
94
+ slug: preset.slug,
95
+ title: preset.title,
96
+ options: preset.options,
97
+ source: "preset"
98
+ });
99
+ for (let theme of state.custom) all.push({
100
+ slug: theme.slug,
101
+ title: theme.title,
102
+ options: theme.options,
103
+ source: "custom",
104
+ ...theme.palette ? { palette: theme.palette } : {}
105
+ });
106
+ let removedSlugs = new Set(state.removed), themes = all.filter((theme) => !removedSlugs.has(theme.slug));
107
+ return {
108
+ themes,
109
+ removed: all.filter((theme) => removedSlugs.has(theme.slug)),
110
+ active: themes.find((theme) => theme.slug === state.active) ?? config
111
+ };
112
+ }
113
+ /**
114
+ * Creates a custom theme with a slug that cannot collide with the presets or
115
+ * the configured theme.
116
+ *
117
+ * @internal
118
+ */
119
+ function createCustomTheme(title, options, palette) {
120
+ return {
121
+ slug: `custom-${randomId()}`,
122
+ title,
123
+ options,
124
+ ...palette ? { palette } : {}
125
+ };
126
+ }
127
+ /** The title of a duplicated theme @internal */
128
+ function duplicateTitle(title) {
129
+ return `${displayTitle(title)} copy`;
130
+ }
131
+ /** The title to show for a theme, falling back when the user cleared it @internal */
132
+ function displayTitle(title) {
133
+ return title.trim() || "Untitled theme";
134
+ }
135
+ function randomId() {
136
+ return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
137
+ }
138
+ function themesOf(context) {
139
+ return resolveThemes(context, context.baseOptions);
140
+ }
141
+ /**
142
+ * The state of the themer tool: three parallel regions, whether the
143
+ * `sidebar` is open, which `flow` it is in — picking a theme from the `list`,
144
+ * `edit`ing one of the user's own themes, or restoring `removed` ones — and
145
+ * whether the `preview` shows the Studio once (`single`) or in light and dark
146
+ * side by side (`split`). The context carries the persisted state (the
147
+ * applied theme, the user's themes and what was removed) alongside what the
148
+ * flows need.
149
+ *
150
+ * Theme operations are handled in every flow, and the flows leave on their
151
+ * own when they lose their subject: the editor when its theme is removed or
152
+ * deleted, and the removed view when its last theme is restored or deleted.
153
+ *
154
+ * @internal
155
+ */
156
+ const themerMachine = setup({
157
+ types: {
158
+ context: {},
159
+ events: {},
160
+ input: {}
161
+ },
162
+ guards: {
163
+ isCustomTheme: ({ context }, params) => themesOf(context).themes.some((theme) => theme.slug === params.slug && theme.source === "custom"),
164
+ isEditingListedTheme: ({ context }) => context.editing !== null && themesOf(context).themes.some((theme) => theme.slug === context.editing?.slug && theme.source === "custom"),
165
+ hasRemovedThemes: ({ context }) => themesOf(context).removed.length > 0
166
+ },
167
+ actions: {
168
+ pick: assign((_, params) => ({ active: params.slug === "config" ? null : params.slug })),
169
+ add: assign(({ context }, params) => {
170
+ let theme = createCustomTheme(params.title ?? "Untitled theme", params.options ?? themesOf(context).active.options, params.palette);
171
+ return {
172
+ active: theme.slug,
173
+ custom: [...context.custom, theme],
174
+ editing: {
175
+ slug: theme.slug,
176
+ focusTitle: params.title === void 0
177
+ },
178
+ images: params.imageUrl ? {
179
+ ...context.images,
180
+ [theme.slug]: params.imageUrl
181
+ } : context.images
182
+ };
183
+ }),
184
+ duplicate: assign(({ context }, params) => {
185
+ let { themes, removed } = themesOf(context), source = [...themes, ...removed].find((theme) => theme.slug === params.slug);
186
+ if (!source) return {};
187
+ let theme = createCustomTheme(duplicateTitle(source.title), source.options, source.palette);
188
+ return {
189
+ active: theme.slug,
190
+ custom: [...context.custom, theme],
191
+ editing: {
192
+ slug: theme.slug,
193
+ focusTitle: !0
194
+ }
195
+ };
196
+ }),
197
+ startEditing: assign((_, params) => ({
198
+ active: params.slug,
199
+ editing: {
200
+ slug: params.slug,
201
+ focusTitle: !1
202
+ }
203
+ })),
204
+ stopEditing: assign({ editing: null }),
205
+ update: assign(({ context }, params) => ({
206
+ custom: context.custom.map((theme) => theme.slug === params.slug ? {
207
+ ...theme,
208
+ title: params.title ?? theme.title,
209
+ options: params.options ?? theme.options,
210
+ ...params.palette ? { palette: params.palette } : {}
211
+ } : theme),
212
+ images: params.imageUrl && context.custom.some((theme) => theme.slug === params.slug) ? {
213
+ ...context.images,
214
+ [params.slug]: params.imageUrl
215
+ } : context.images
216
+ })),
217
+ remove: assign(({ context }, params) => params.slug === "config" || context.removed.includes(params.slug) ? {} : {
218
+ active: context.active === params.slug ? null : context.active,
219
+ removed: [...context.removed, params.slug]
220
+ }),
221
+ restore: assign(({ context }, params) => ({ removed: context.removed.filter((slug) => slug !== params.slug) })),
222
+ delete: assign(({ context }, params) => {
223
+ if (!context.custom.some((theme) => theme.slug === params.slug)) return {};
224
+ let { [params.slug]: _deleted, ...images } = context.images;
225
+ return {
226
+ active: context.active === params.slug ? null : context.active,
227
+ custom: context.custom.filter((theme) => theme.slug !== params.slug),
228
+ removed: context.removed.filter((slug) => slug !== params.slug),
229
+ images
230
+ };
231
+ })
232
+ }
233
+ }).createMachine({
234
+ id: "themer",
235
+ context: ({ input }) => ({
236
+ ...input.stored,
237
+ baseOptions: input.baseOptions,
238
+ editing: null,
239
+ images: {}
240
+ }),
241
+ type: "parallel",
242
+ states: {
243
+ sidebar: {
244
+ initial: "closed",
245
+ states: {
246
+ closed: { on: { "sidebar.toggle": "open" } },
247
+ open: { on: {
248
+ "sidebar.toggle": "closed",
249
+ "sidebar.close": "closed"
250
+ } }
251
+ }
252
+ },
253
+ preview: {
254
+ initial: "single",
255
+ states: {
256
+ single: { on: { "preview.toggle": "split" } },
257
+ split: { on: { "preview.toggle": "single" } }
258
+ }
259
+ },
260
+ flow: {
261
+ initial: "list",
262
+ on: {
263
+ "theme.pick": { actions: {
264
+ type: "pick",
265
+ params: ({ event }) => ({ slug: event.slug })
266
+ } },
267
+ "theme.add": {
268
+ target: ".edit",
269
+ actions: {
270
+ type: "add",
271
+ params: ({ event }) => ({
272
+ title: event.title,
273
+ options: event.options,
274
+ palette: event.palette,
275
+ imageUrl: event.imageUrl
276
+ })
277
+ }
278
+ },
279
+ "theme.duplicate": {
280
+ target: ".edit",
281
+ actions: {
282
+ type: "duplicate",
283
+ params: ({ event }) => ({ slug: event.slug })
284
+ }
285
+ },
286
+ "theme.edit": {
287
+ target: ".edit",
288
+ guard: {
289
+ type: "isCustomTheme",
290
+ params: ({ event }) => ({ slug: event.slug })
291
+ },
292
+ actions: {
293
+ type: "startEditing",
294
+ params: ({ event }) => ({ slug: event.slug })
295
+ }
296
+ },
297
+ "theme.update": { actions: {
298
+ type: "update",
299
+ params: ({ event }) => ({
300
+ slug: event.slug,
301
+ title: event.title,
302
+ options: event.options,
303
+ palette: event.palette,
304
+ imageUrl: event.imageUrl
305
+ })
306
+ } },
307
+ "theme.remove": { actions: {
308
+ type: "remove",
309
+ params: ({ event }) => ({ slug: event.slug })
310
+ } },
311
+ "theme.restore": { actions: {
312
+ type: "restore",
313
+ params: ({ event }) => ({ slug: event.slug })
314
+ } },
315
+ "theme.delete": { actions: {
316
+ type: "delete",
317
+ params: ({ event }) => ({ slug: event.slug })
318
+ } },
319
+ "flow.list": ".list",
320
+ "flow.removed": {
321
+ target: ".removed",
322
+ guard: "hasRemovedThemes"
323
+ }
324
+ },
325
+ states: {
326
+ list: {},
327
+ edit: {
328
+ exit: "stopEditing",
329
+ always: {
330
+ guard: not("isEditingListedTheme"),
331
+ target: "list"
332
+ }
333
+ },
334
+ removed: { always: {
335
+ guard: not("hasRemovedThemes"),
336
+ target: "list"
337
+ } }
338
+ }
339
+ }
340
+ }
341
+ });
342
+ /** The part of the machine context that is persisted between sessions @internal */
343
+ function selectStoredState(snapshot) {
344
+ let { active, custom, removed } = snapshot.context;
345
+ return {
346
+ active,
347
+ custom,
348
+ removed
349
+ };
350
+ }
351
+ const Root$2 = styled(Box).withConfig({
352
+ displayName: "Root",
353
+ componentId: "sc-6lslk9-0"
354
+ })`scrollbar-gutter:stable;`, ScrollAreaInsetContext = createContext({
355
+ left: 0,
356
+ right: 0,
357
+ bottom: 0
358
+ });
359
+ /**
360
+ * The scrolling body of a sidebar flow. It always reserves the scrollbar's
361
+ * space, so the content does not shift when the list grows or shrinks past
362
+ * the point where it needs to scroll — and it takes that space out of its
363
+ * right padding, so the content stays centered rather than hugging the left.
364
+ * With overlay scrollbars there is no gutter and the padding stays even.
365
+ *
366
+ * @internal
367
+ */
368
+ function ScrollArea(props) {
369
+ let $ = c(15), { children, padding } = props, { space } = useTheme_v2(), ref = useRef(null), [gutter, setGutter] = useState(0), t0, t1;
370
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => {
371
+ let element = ref.current;
372
+ if (!element) return;
373
+ let measure = () => setGutter(element.offsetWidth - element.clientWidth);
374
+ measure();
375
+ let observer = new ResizeObserver(measure);
376
+ return observer.observe(element), () => observer.disconnect();
377
+ }, t1 = [], $[0] = t0, $[1] = t1) : (t0 = $[0], t1 = $[1]), useEffect(t0, t1);
378
+ let t2 = space[padding], t3 = Math.max(0, space[padding] - gutter), t4 = space[padding], t5;
379
+ $[2] !== t2 || $[3] !== t3 || $[4] !== t4 ? (t5 = {
380
+ left: t2,
381
+ right: t3,
382
+ bottom: t4
383
+ }, $[2] = t2, $[3] = t3, $[4] = t4, $[5] = t5) : t5 = $[5];
384
+ let inset = t5, t6;
385
+ $[6] === inset.right ? t6 = $[7] : (t6 = { paddingRight: inset.right }, $[6] = inset.right, $[7] = t6);
386
+ let t7;
387
+ $[8] !== children || $[9] !== inset ? (t7 = /* @__PURE__ */ jsx(ScrollAreaInsetContext.Provider, {
388
+ value: inset,
389
+ children
390
+ }), $[8] = children, $[9] = inset, $[10] = t7) : t7 = $[10];
391
+ let t8;
392
+ return $[11] !== padding || $[12] !== t6 || $[13] !== t7 ? (t8 = /* @__PURE__ */ jsx(Root$2, {
393
+ flex: 1,
394
+ overflow: "auto",
395
+ padding,
396
+ ref,
397
+ style: t6,
398
+ children: t7
399
+ }), $[11] = padding, $[12] = t6, $[13] = t7, $[14] = t8) : t8 = $[14], t8;
400
+ }
401
+ /**
402
+ * Content that runs edge to edge in a scroll area — up to the scrollbar —
403
+ * instead of sitting inside its padding. It goes last: it runs down to the
404
+ * bottom edge too.
405
+ *
406
+ * @internal
407
+ */
408
+ function ScrollAreaBleed(props) {
409
+ let $ = c(7), { left, right, bottom } = useContext(ScrollAreaInsetContext), t0 = -left, t1 = -right, t2 = -bottom, t3;
410
+ $[0] !== t0 || $[1] !== t1 || $[2] !== t2 ? (t3 = {
411
+ marginLeft: t0,
412
+ marginRight: t1,
413
+ marginBottom: t2
414
+ }, $[0] = t0, $[1] = t1, $[2] = t2, $[3] = t3) : t3 = $[3];
415
+ let t4;
416
+ return $[4] !== props.children || $[5] !== t3 ? (t4 = /* @__PURE__ */ jsx("div", {
417
+ style: t3,
418
+ children: props.children
419
+ }), $[4] = props.children, $[5] = t3, $[6] = t4) : t4 = $[6], t4;
420
+ }
421
+ /**
422
+ * The dark scheme covers the right side of the preview behind a diagonal cut,
423
+ * like the "Auto" appearance preview in macOS System Settings shows both
424
+ * schemes at once.
425
+ */
426
+ const Root$1 = styled.div.withConfig({
427
+ displayName: "Root",
428
+ componentId: "sc-w55yz9-0"
429
+ })`position:relative;aspect-ratio:16 / 9;overflow:hidden;`, LIST_ROWS = [
430
+ "62%",
431
+ "46%",
432
+ "70%",
433
+ "52%"
434
+ ], FIELDS = ["32%", "24%"];
435
+ /**
436
+ * A tiny, lo-fi mockup of a Studio in the given theme — the navbar over the
437
+ * structure tool with a list of documents on the left and a document form on
438
+ * the right — in both color schemes at once: the light scheme on the left and
439
+ * the dark scheme on the right, split diagonally. It only paints colors the
440
+ * theme options change (backgrounds, text, borders, the accent) so that a
441
+ * glance shows what the theme does to the Studio — with few and bold enough
442
+ * elements to still read when scaled down to a thumbnail.
443
+ *
444
+ * @internal
445
+ */
446
+ function ThemePreview(props) {
447
+ let $ = c(11), { options } = props, t0;
448
+ $[0] === options ? t0 = $[1] : (t0 = buildTheme(options), $[0] = options, $[1] = t0);
449
+ let theme = t0, t1;
450
+ $[2] === Symbol.for("react.memo_cache_sentinel") ? (t1 = /* @__PURE__ */ jsx(MockStudio, {}), $[2] = t1) : t1 = $[2];
451
+ let t2;
452
+ $[3] === theme ? t2 = $[4] : (t2 = /* @__PURE__ */ jsx(ThemeProvider, {
453
+ scheme: "light",
454
+ theme,
455
+ tone: "default",
456
+ children: t1
457
+ }), $[3] = theme, $[4] = t2);
458
+ let t3;
459
+ $[5] === Symbol.for("react.memo_cache_sentinel") ? (t3 = /* @__PURE__ */ jsx(MockStudio, { style: { clipPath: "polygon(58% 0, 100% 0, 100% 100%, 42% 100%)" } }), $[5] = t3) : t3 = $[5];
460
+ let t4;
461
+ $[6] === theme ? t4 = $[7] : (t4 = /* @__PURE__ */ jsx(ThemeProvider, {
462
+ scheme: "dark",
463
+ theme,
464
+ tone: "default",
465
+ children: t3
466
+ }), $[6] = theme, $[7] = t4);
467
+ let t5;
468
+ return $[8] !== t2 || $[9] !== t4 ? (t5 = /* @__PURE__ */ jsxs(Root$1, {
469
+ "aria-hidden": !0,
470
+ children: [t2, t4]
471
+ }), $[8] = t2, $[9] = t4, $[10] = t5) : t5 = $[10], t5;
472
+ }
473
+ function MockStudio(props) {
474
+ let $ = c(53), { color } = useTheme_v2(), primary = color.button.default.primary.enabled, selected = color.selectable.default.selected, input = color.input.default.enabled, border = `1px solid ${color.border}`, line = _temp$4, t0;
475
+ $[0] !== color.bg || $[1] !== props.style ? (t0 = {
476
+ position: "absolute",
477
+ inset: 0,
478
+ display: "flex",
479
+ flexDirection: "column",
480
+ background: color.bg,
481
+ ...props.style
482
+ }, $[0] = color.bg, $[1] = props.style, $[2] = t0) : t0 = $[2];
483
+ let t1;
484
+ $[3] === border ? t1 = $[4] : (t1 = {
485
+ flex: "none",
486
+ display: "flex",
487
+ alignItems: "center",
488
+ gap: 5,
489
+ height: "18%",
490
+ padding: "0 5%",
491
+ borderBottom: border
492
+ }, $[3] = border, $[4] = t1);
493
+ let t2;
494
+ $[5] === primary.bg ? t2 = $[6] : (t2 = /* @__PURE__ */ jsx("div", { style: {
495
+ flex: "none",
496
+ width: 8,
497
+ height: 8,
498
+ borderRadius: 2,
499
+ background: primary.bg
500
+ } }), $[5] = primary.bg, $[6] = t2);
501
+ let t3;
502
+ $[7] === color.fg ? t3 = $[8] : (t3 = /* @__PURE__ */ jsx("div", { style: line("22%", color.fg) }), $[7] = color.fg, $[8] = t3);
503
+ let t4;
504
+ $[9] === Symbol.for("react.memo_cache_sentinel") ? (t4 = /* @__PURE__ */ jsx("div", { style: { flex: 1 } }), $[9] = t4) : t4 = $[9];
505
+ let t5;
506
+ $[10] === color.avatar.blue.bg ? t5 = $[11] : (t5 = /* @__PURE__ */ jsx("div", { style: {
507
+ flex: "none",
508
+ width: 8,
509
+ height: 8,
510
+ borderRadius: "50%",
511
+ background: color.avatar.blue.bg
512
+ } }), $[10] = color.avatar.blue.bg, $[11] = t5);
513
+ let t6;
514
+ $[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t5 ? (t6 = /* @__PURE__ */ jsxs("div", {
515
+ style: t1,
516
+ children: [
517
+ t2,
518
+ t3,
519
+ t4,
520
+ t5
521
+ ]
522
+ }), $[12] = t1, $[13] = t2, $[14] = t3, $[15] = t5, $[16] = t6) : t6 = $[16];
523
+ let t7;
524
+ $[17] === Symbol.for("react.memo_cache_sentinel") ? (t7 = {
525
+ flex: 1,
526
+ display: "flex",
527
+ minHeight: 0
528
+ }, $[17] = t7) : t7 = $[17];
529
+ let t8;
530
+ $[18] === border ? t8 = $[19] : (t8 = {
531
+ flex: "none",
532
+ display: "flex",
533
+ flexDirection: "column",
534
+ width: "36%",
535
+ borderRight: border
536
+ }, $[18] = border, $[19] = t8);
537
+ let t9;
538
+ $[20] !== color.muted || $[21] !== selected ? (t9 = LIST_ROWS.map((width_0, index) => {
539
+ let isSelected = index === 1;
540
+ return /* @__PURE__ */ jsx("div", {
541
+ style: {
542
+ flex: "none",
543
+ display: "flex",
544
+ alignItems: "center",
545
+ height: "18%",
546
+ padding: "0 9%",
547
+ background: isSelected ? selected.bg : void 0
548
+ },
549
+ children: /* @__PURE__ */ jsx("div", { style: line(width_0, isSelected ? selected.fg : color.muted.fg) })
550
+ }, width_0);
551
+ }), $[20] = color.muted, $[21] = selected, $[22] = t9) : t9 = $[22];
552
+ let t10;
553
+ $[23] !== t8 || $[24] !== t9 ? (t10 = /* @__PURE__ */ jsx("div", {
554
+ style: t8,
555
+ children: t9
556
+ }), $[23] = t8, $[24] = t9, $[25] = t10) : t10 = $[25];
557
+ let t11, t12;
558
+ $[26] === Symbol.for("react.memo_cache_sentinel") ? (t11 = {
559
+ flex: 1,
560
+ display: "flex",
561
+ flexDirection: "column",
562
+ minWidth: 0
563
+ }, t12 = {
564
+ flex: 1,
565
+ display: "flex",
566
+ flexDirection: "column",
567
+ gap: 7,
568
+ padding: "9% 8% 0",
569
+ minHeight: 0,
570
+ overflow: "hidden"
571
+ }, $[26] = t11, $[27] = t12) : (t11 = $[26], t12 = $[27]);
572
+ let t13;
573
+ $[28] === color.fg ? t13 = $[29] : (t13 = /* @__PURE__ */ jsx("div", { style: line("45%", color.fg) }), $[28] = color.fg, $[29] = t13);
574
+ let t14;
575
+ $[30] !== color.muted || $[31] !== input ? (t14 = FIELDS.map((width_1) => /* @__PURE__ */ jsxs("div", {
576
+ style: {
577
+ flex: "none",
578
+ display: "flex",
579
+ flexDirection: "column",
580
+ gap: 3
581
+ },
582
+ children: [/* @__PURE__ */ jsx("div", { style: line(width_1, color.muted.fg) }), /* @__PURE__ */ jsx("div", { style: {
583
+ height: 12,
584
+ borderRadius: 3,
585
+ background: input.bg,
586
+ boxShadow: `inset 0 0 0 1px ${input.border}`
587
+ } })]
588
+ }, width_1)), $[30] = color.muted, $[31] = input, $[32] = t14) : t14 = $[32];
589
+ let t15;
590
+ $[33] !== t13 || $[34] !== t14 ? (t15 = /* @__PURE__ */ jsxs("div", {
591
+ style: t12,
592
+ children: [t13, t14]
593
+ }), $[33] = t13, $[34] = t14, $[35] = t15) : t15 = $[35];
594
+ let t16;
595
+ $[36] === border ? t16 = $[37] : (t16 = {
596
+ flex: "none",
597
+ display: "flex",
598
+ alignItems: "center",
599
+ justifyContent: "flex-end",
600
+ height: "20%",
601
+ padding: "0 8%",
602
+ borderTop: border
603
+ }, $[36] = border, $[37] = t16);
604
+ let t17;
605
+ $[38] === primary.bg ? t17 = $[39] : (t17 = /* @__PURE__ */ jsx("div", { style: {
606
+ width: "32%",
607
+ height: "50%",
608
+ borderRadius: 3,
609
+ background: primary.bg
610
+ } }), $[38] = primary.bg, $[39] = t17);
611
+ let t18;
612
+ $[40] !== t16 || $[41] !== t17 ? (t18 = /* @__PURE__ */ jsx("div", {
613
+ style: t16,
614
+ children: t17
615
+ }), $[40] = t16, $[41] = t17, $[42] = t18) : t18 = $[42];
616
+ let t19;
617
+ $[43] !== t15 || $[44] !== t18 ? (t19 = /* @__PURE__ */ jsxs("div", {
618
+ style: t11,
619
+ children: [t15, t18]
620
+ }), $[43] = t15, $[44] = t18, $[45] = t19) : t19 = $[45];
621
+ let t20;
622
+ $[46] !== t10 || $[47] !== t19 ? (t20 = /* @__PURE__ */ jsxs("div", {
623
+ style: t7,
624
+ children: [t10, t19]
625
+ }), $[46] = t10, $[47] = t19, $[48] = t20) : t20 = $[48];
626
+ let t21;
627
+ return $[49] !== t0 || $[50] !== t20 || $[51] !== t6 ? (t21 = /* @__PURE__ */ jsxs("div", {
628
+ style: t0,
629
+ children: [t6, t20]
630
+ }), $[49] = t0, $[50] = t20, $[51] = t6, $[52] = t21) : t21 = $[52], t21;
631
+ }
632
+ function _temp$4(width, background) {
633
+ return {
634
+ flex: "none",
635
+ width,
636
+ height: 4,
637
+ borderRadius: 2,
638
+ background
639
+ };
640
+ }
641
+ /**
642
+ * The floating preview card of a theme: the lo-fi studio mockup on a raised,
643
+ * rounded card, like the appearance previews in macOS System Settings.
644
+ *
645
+ * @internal
646
+ */
647
+ function ThemeThumbnail(props) {
648
+ let $ = c(2), t0;
649
+ return $[0] === props.options ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(Card, {
650
+ overflow: "hidden",
651
+ radius: 3,
652
+ shadow: 1,
653
+ children: /* @__PURE__ */ jsx(ThemePreview, { options: props.options })
654
+ }), $[0] = props.options, $[1] = t0), t0;
655
+ }
656
+ /**
657
+ * The flow for restoring removed themes: the presets and custom themes the
658
+ * user took out of the list, each with a button to put it back — and, for
659
+ * custom themes, one to delete it for good. The machine leaves the flow once
660
+ * the last one is gone.
661
+ *
662
+ * @internal
663
+ */
664
+ function RemovedThemes() {
665
+ let $ = c(7), { removed, send } = useThemer(), t0;
666
+ if ($[0] !== removed || $[1] !== send) {
667
+ let t1;
668
+ $[3] === send ? t1 = $[4] : (t1 = (theme) => /* @__PURE__ */ jsxs(Stack, {
669
+ gap: 3,
670
+ children: [
671
+ /* @__PURE__ */ jsx(Box, {
672
+ style: { opacity: .6 },
673
+ children: /* @__PURE__ */ jsx(ThemeThumbnail, { options: theme.options })
674
+ }),
675
+ /* @__PURE__ */ jsx(Text, {
676
+ align: "center",
677
+ size: 1,
678
+ textOverflow: "ellipsis",
679
+ children: displayTitle(theme.title)
680
+ }),
681
+ /* @__PURE__ */ jsxs(Flex, {
682
+ gap: 2,
683
+ justify: "center",
684
+ children: [/* @__PURE__ */ jsx(Button, {
685
+ fontSize: 1,
686
+ mode: "ghost",
687
+ onClick: () => send({
688
+ type: "theme.restore",
689
+ slug: theme.slug
690
+ }),
691
+ padding: 2,
692
+ text: "Restore"
693
+ }), theme.source === "custom" && /* @__PURE__ */ jsx(Button, {
694
+ fontSize: 1,
695
+ mode: "ghost",
696
+ onClick: () => send({
697
+ type: "theme.delete",
698
+ slug: theme.slug
699
+ }),
700
+ padding: 2,
701
+ text: "Delete",
702
+ tone: "critical"
703
+ })]
704
+ })
705
+ ]
706
+ }, theme.slug), $[3] = send, $[4] = t1), t0 = removed.map(t1), $[0] = removed, $[1] = send, $[2] = t0;
707
+ } else t0 = $[2];
708
+ let t1;
709
+ return $[5] === t0 ? t1 = $[6] : (t1 = /* @__PURE__ */ jsx(ScrollArea, {
710
+ padding: 3,
711
+ children: /* @__PURE__ */ jsx(Stack, {
712
+ gap: 5,
713
+ children: t0
714
+ })
715
+ }), $[5] = t0, $[6] = t1), t1;
716
+ }
717
+ /** @internal */
718
+ const IMAGE_PALETTE_KEYS = [
719
+ "vibrant",
720
+ "lightVibrant",
721
+ "darkVibrant",
722
+ "muted",
723
+ "lightMuted",
724
+ "darkMuted",
725
+ "dominant"
726
+ ], IMAGE_PALETTE_VARIANTS = [
727
+ "muted",
728
+ "vibrant",
729
+ "lightMuted",
730
+ "lightVibrant",
731
+ "darkMuted",
732
+ "darkVibrant"
733
+ ], IMAGE_PALETTE_TITLES = {
734
+ dominant: "Dominant",
735
+ vibrant: "Vibrant",
736
+ lightVibrant: "Light vibrant",
737
+ darkVibrant: "Dark vibrant",
738
+ muted: "Muted",
739
+ lightMuted: "Light muted",
740
+ darkMuted: "Dark muted"
741
+ }, TARGETS = [
742
+ ["vibrant", {
743
+ lightness: [
744
+ .5,
745
+ .3,
746
+ .7
747
+ ],
748
+ saturation: [
749
+ 1,
750
+ .35,
751
+ 1
752
+ ]
753
+ }],
754
+ ["lightVibrant", {
755
+ lightness: [
756
+ .74,
757
+ .55,
758
+ 1
759
+ ],
760
+ saturation: [
761
+ 1,
762
+ .35,
763
+ 1
764
+ ]
765
+ }],
766
+ ["darkVibrant", {
767
+ lightness: [
768
+ .26,
769
+ 0,
770
+ .45
771
+ ],
772
+ saturation: [
773
+ 1,
774
+ .35,
775
+ 1
776
+ ]
777
+ }],
778
+ ["muted", {
779
+ lightness: [
780
+ .5,
781
+ .3,
782
+ .7
783
+ ],
784
+ saturation: [
785
+ .3,
786
+ 0,
787
+ .4
788
+ ]
789
+ }],
790
+ ["lightMuted", {
791
+ lightness: [
792
+ .74,
793
+ .55,
794
+ 1
795
+ ],
796
+ saturation: [
797
+ .3,
798
+ 0,
799
+ .4
800
+ ]
801
+ }],
802
+ ["darkMuted", {
803
+ lightness: [
804
+ .26,
805
+ 0,
806
+ .45
807
+ ],
808
+ saturation: [
809
+ .3,
810
+ 0,
811
+ .4
812
+ ]
813
+ }]
814
+ ];
815
+ /**
816
+ * Extracts the color palette of an image from its pixels, the way the Vibrant
817
+ * algorithm does: the colors are quantized into a coarse histogram, and each
818
+ * swatch claims the color that comes closest to its target lightness and
819
+ * saturation — weighted by how much of the image it covers — that no other
820
+ * swatch has claimed yet. The dominant swatch is simply the most common
821
+ * color. Runs anywhere pixels come from, so it can run on device.
822
+ *
823
+ * @internal
824
+ */
825
+ function extractImagePalette(pixels) {
826
+ let candidates = quantize(pixels), palette = {
827
+ dominant: null,
828
+ vibrant: null,
829
+ lightVibrant: null,
830
+ darkVibrant: null,
831
+ muted: null,
832
+ lightMuted: null,
833
+ darkMuted: null
834
+ };
835
+ if (candidates.length === 0) return palette;
836
+ let maximumPopulation = Math.max(...candidates.map((candidate) => candidate.population)), claimed = /* @__PURE__ */ new Set();
837
+ for (let [key, target] of TARGETS) {
838
+ let best = null, bestScore = -Infinity;
839
+ for (let candidate of candidates) {
840
+ if (claimed.has(candidate.hex) || !matchesTarget(candidate, target)) continue;
841
+ let score = scoreCandidate(candidate, target, maximumPopulation);
842
+ score > bestScore && (best = candidate, bestScore = score);
843
+ }
844
+ best && (palette[key] = best.hex, claimed.add(best.hex));
845
+ }
846
+ return palette.dominant = candidates.reduce((most, candidate) => candidate.population > most.population ? candidate : most).hex, palette;
847
+ }
848
+ /**
849
+ * Turns the pixels into a histogram of quantized colors, each averaged over
850
+ * the pixels it stands for, leaving out transparent pixels and the near-black
851
+ * and near-white ones that are backdrop rather than color.
852
+ */
853
+ function quantize(pixels) {
854
+ let { data } = pixels, buckets = /* @__PURE__ */ new Map(), length = Math.min(data.length, pixels.width * pixels.height * 4);
855
+ for (let offset = 0; offset + 3 < length; offset += 4) {
856
+ let r = data[offset], g = data[offset + 1], b = data[offset + 2];
857
+ if (data[offset + 3] < 125) continue;
858
+ let lightness = (Math.max(r, g, b) + Math.min(r, g, b)) / 510;
859
+ if (lightness < .05 || lightness > .95) continue;
860
+ let key = r >> 3 << 10 | g >> 3 << 5 | b >> 3, bucket = buckets.get(key);
861
+ bucket ? (bucket.count++, bucket.r += r, bucket.g += g, bucket.b += b) : buckets.set(key, {
862
+ count: 1,
863
+ r,
864
+ g,
865
+ b
866
+ });
867
+ }
868
+ let candidates = [];
869
+ for (let bucket of buckets.values()) {
870
+ let r = Math.round(bucket.r / bucket.count), g = Math.round(bucket.g / bucket.count), b = Math.round(bucket.b / bucket.count), { saturation, lightness } = saturationAndLightness(r, g, b);
871
+ candidates.push({
872
+ hex: rgbToHex(r, g, b),
873
+ population: bucket.count,
874
+ saturation,
875
+ lightness
876
+ });
877
+ }
878
+ return candidates;
879
+ }
880
+ function matchesTarget(candidate, target) {
881
+ return candidate.lightness >= target.lightness[1] && candidate.lightness <= target.lightness[2] && candidate.saturation >= target.saturation[1] && candidate.saturation <= target.saturation[2];
882
+ }
883
+ function scoreCandidate(candidate, target, maximumPopulation) {
884
+ return 3 * (1 - Math.abs(candidate.saturation - target.saturation[0])) + 6.5 * (1 - Math.abs(candidate.lightness - target.lightness[0])) + .5 * (candidate.population / maximumPopulation);
885
+ }
886
+ function saturationAndLightness(r, g, b) {
887
+ let max = Math.max(r, g, b) / 255, min = Math.min(r, g, b) / 255, lightness = (max + min) / 2, delta = max - min;
888
+ return {
889
+ saturation: delta === 0 ? 0 : lightness > .5 ? delta / (2 - max - min) : delta / (max + min),
890
+ lightness
891
+ };
892
+ }
893
+ function rgbToHex(r, g, b) {
894
+ return `#${[
895
+ r,
896
+ g,
897
+ b
898
+ ].map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
899
+ }
900
+ /** Reads `#rgb` and `#rrggbb` alike — stored palettes may hold either */
901
+ function hexToRgb(hex) {
902
+ let expanded = hex.length === 4 ? `${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex.slice(1);
903
+ return [
904
+ parseInt(expanded.slice(0, 2), 16),
905
+ parseInt(expanded.slice(2, 4), 16),
906
+ parseInt(expanded.slice(4, 6), 16)
907
+ ];
908
+ }
909
+ /** Mixes `amount` of `color` into `base` */
910
+ function tint(base, color, amount) {
911
+ let [r1, g1, b1] = hexToRgb(base), [r2, g2, b2] = hexToRgb(color);
912
+ return rgbToHex(Math.round(r1 + (r2 - r1) * amount), Math.round(g1 + (g2 - g1) * amount), Math.round(b1 + (b2 - b1) * amount));
913
+ }
914
+ /** How much of the palette tints each scheme's stock background */
915
+ const BACKGROUND_TINT = {
916
+ light: .08,
917
+ dark: .3
918
+ }, BUTTON_LABEL = {
919
+ light: "#ffffff",
920
+ dark: "#000000"
921
+ }, ACCENT_LIGHTNESS_STEP = .01;
922
+ function contrastRatio(a, b) {
923
+ let [lighter, darker] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x);
924
+ return (lighter + .05) / (darker + .05);
925
+ }
926
+ /**
927
+ * Makes an accent readable as a button in the given scheme: a light scheme
928
+ * accent is darkened until white text on it reaches AA contrast, a dark
929
+ * scheme accent lightened until black text does — as far as the accent's
930
+ * hue and saturation allow. Accents that already pass come back untouched.
931
+ *
932
+ * @internal
933
+ */
934
+ function readableAccent(hex, scheme) {
935
+ let label = BUTTON_LABEL[scheme], { h, s } = hexToHsl(hex), { l } = hexToHsl(hex), current = hex;
936
+ for (; contrastRatio(current, label) < 4.5;) {
937
+ let next = scheme === "light" ? l - ACCENT_LIGHTNESS_STEP : l + ACCENT_LIGHTNESS_STEP;
938
+ if (next < 0 || next > 1) break;
939
+ l = next, current = hslToHex({
940
+ h,
941
+ s,
942
+ l
943
+ });
944
+ }
945
+ return current;
946
+ }
947
+ /** The accent a variant gives a scheme, made readable on a button */
948
+ function variantAccent(palette, variant, scheme) {
949
+ let swatch = (variant ? palette[variant] : null) ?? palette.vibrant ?? (scheme === "light" ? palette.darkVibrant : palette.lightVibrant) ?? palette.dominant;
950
+ return swatch === null ? null : readableAccent(swatch, scheme);
951
+ }
952
+ /**
953
+ * Derives theme options from an image palette: the accent of both schemes is
954
+ * the swatch of the chosen variant — or, without one, the vibrant swatch,
955
+ * each scheme falling back to the vibrant swatch of its own end of the scale —
956
+ * adjusted until it is readable as a button. The muted swatch becomes the text color, and the light and dark muted
957
+ * swatches tint the stock backgrounds of their scheme — enough to carry the
958
+ * image's tone without giving up a usable canvas. Whatever the palette lacks
959
+ * stays at the theme defaults.
960
+ *
961
+ * @internal
962
+ */
963
+ function optionsFromImagePalette(palette, variant) {
964
+ let options = {}, light = schemeFromPalette(variantAccent(palette, variant ?? null, "light"), palette.muted ?? palette.darkMuted, palette.lightMuted ?? palette.muted ?? palette.lightVibrant, "light"), dark = schemeFromPalette(variantAccent(palette, variant ?? null, "dark"), palette.muted ?? palette.lightMuted, palette.darkMuted ?? palette.muted ?? palette.darkVibrant, "dark");
965
+ return light && (options.light = light), dark && (options.dark = dark), options;
966
+ }
967
+ function schemeFromPalette(accent, text, backgroundTint, scheme) {
968
+ let options = {};
969
+ return accent && (options.accent = accent), text && (options.text = text), backgroundTint && (options.background = tint(DEFAULT_BACKGROUND[scheme], backgroundTint, BACKGROUND_TINT[scheme])), Object.keys(options).length > 0 ? options : null;
970
+ }
971
+ /**
972
+ * Applies an image palette to existing theme options: the colors the palette
973
+ * provides replace the current ones scheme by scheme, and whatever it does
974
+ * not touch — like the contrast — stays as it is.
975
+ *
976
+ * @internal
977
+ */
978
+ function applyImagePalette(options, palette, variant) {
979
+ let derived = optionsFromImagePalette(palette, variant), next = { ...options };
980
+ for (let scheme of SCHEMES) derived[scheme] && (next[scheme] = {
981
+ ...options[scheme],
982
+ ...derived[scheme]
983
+ });
984
+ return next;
985
+ }
986
+ /**
987
+ * The variant a theme is built around: the one whose swatch is the theme's
988
+ * accent, or `null` when the accent comes from somewhere else.
989
+ *
990
+ * @internal
991
+ */
992
+ function currentImageVariant(options, palette) {
993
+ let accent = options.light?.accent;
994
+ return accent ? IMAGE_PALETTE_VARIANTS.find((variant) => palette[variant] !== null && variantAccent(palette, variant, "light") === accent) ?? null : null;
995
+ }
996
+ /**
997
+ * How much a swatch has going for it as an accent: saturation first, then
998
+ * closeness to mid lightness — the range where an accent reads best.
999
+ */
1000
+ function interest(hex) {
1001
+ let { s, l } = hexToHsl(hex);
1002
+ return .1 + .55 * s + .35 * (1 - Math.abs(l - .5) * 2);
1003
+ }
1004
+ /**
1005
+ * Picks a variant for the "I'm feeling lucky" button: a random one, weighted
1006
+ * by how interesting its swatch is as an accent, and never the one the theme
1007
+ * already uses while there are others to pick from.
1008
+ *
1009
+ * @internal
1010
+ */
1011
+ function pickLuckyVariant(palette, options = {}) {
1012
+ let { exclude = null, random = Math.random } = options, candidates = IMAGE_PALETTE_VARIANTS.filter((variant) => palette[variant] !== null);
1013
+ if (candidates.length > 1 && (candidates = candidates.filter((variant) => variant !== exclude)), candidates.length === 0) return null;
1014
+ let weights = candidates.map((variant) => interest(palette[variant])), total = weights.reduce((sum, weight) => sum + weight, 0), pick = random() * total;
1015
+ for (let [index, variant] of candidates.entries()) if (pick -= weights[index], pick < 0) return variant;
1016
+ return candidates.at(-1) ?? null;
1017
+ }
1018
+ /** A theme title for the image a palette came from, e.g. `sunset-beach.jpg` → `sunset beach` @internal */
1019
+ function titleFromFileName(name) {
1020
+ return name.replace(/\.[^.]+$/, "").replace(/[-_]+/g, " ").trim() || "Image theme";
1021
+ }
1022
+ /**
1023
+ * A button with an animated tooltip instead of a `title` attribute. Icon-only
1024
+ * buttons take the tooltip as their accessible name.
1025
+ *
1026
+ * @internal
1027
+ */
1028
+ function TooltipButton(props) {
1029
+ let $ = c(11), buttonProps, tooltip;
1030
+ $[0] === props ? (buttonProps = $[1], tooltip = $[2]) : ({tooltip, ...buttonProps} = props, $[0] = props, $[1] = buttonProps, $[2] = tooltip);
1031
+ let t0;
1032
+ $[3] === tooltip ? t0 = $[4] : (t0 = /* @__PURE__ */ jsx(Text, {
1033
+ size: 1,
1034
+ children: tooltip
1035
+ }), $[3] = tooltip, $[4] = t0);
1036
+ let t1 = buttonProps.text === void 0 ? tooltip : void 0, t2;
1037
+ $[5] !== buttonProps || $[6] !== t1 ? (t2 = /* @__PURE__ */ jsx(Button, {
1038
+ "aria-label": t1,
1039
+ ...buttonProps
1040
+ }), $[5] = buttonProps, $[6] = t1, $[7] = t2) : t2 = $[7];
1041
+ let t3;
1042
+ return $[8] !== t0 || $[9] !== t2 ? (t3 = /* @__PURE__ */ jsx(Tooltip, {
1043
+ animate: !0,
1044
+ content: t0,
1045
+ placement: "bottom",
1046
+ portal: !0,
1047
+ children: t2
1048
+ }), $[8] = t0, $[9] = t2, $[10] = t3) : t3 = $[10], t3;
1049
+ }
1050
+ /**
1051
+ * A button that opens the file picker for an image, and hands the chosen file
1052
+ * over — the picker is a hidden file input, so nothing is uploaded anywhere.
1053
+ *
1054
+ * @internal
1055
+ */
1056
+ function ImageFileButton(props) {
1057
+ let $ = c(11), buttonProps, onFile;
1058
+ $[0] === props ? (buttonProps = $[1], onFile = $[2]) : ({onFile, ...buttonProps} = props, $[0] = props, $[1] = buttonProps, $[2] = onFile);
1059
+ let inputRef = useRef(null), t0;
1060
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => inputRef.current?.click(), $[3] = t0) : t0 = $[3];
1061
+ let t1;
1062
+ $[4] === buttonProps ? t1 = $[5] : (t1 = /* @__PURE__ */ jsx(TooltipButton, {
1063
+ ...buttonProps,
1064
+ icon: ImageIcon,
1065
+ onClick: t0
1066
+ }), $[4] = buttonProps, $[5] = t1);
1067
+ let t2;
1068
+ $[6] === onFile ? t2 = $[7] : (t2 = /* @__PURE__ */ jsx("input", {
1069
+ accept: "image/*",
1070
+ hidden: !0,
1071
+ onChange: (event) => {
1072
+ let file = event.currentTarget.files?.[0];
1073
+ event.currentTarget.value = "", file && onFile(file);
1074
+ },
1075
+ ref: inputRef,
1076
+ type: "file"
1077
+ }), $[6] = onFile, $[7] = t2);
1078
+ let t3;
1079
+ return $[8] !== t1 || $[9] !== t2 ? (t3 = /* @__PURE__ */ jsxs(Fragment, { children: [t1, t2] }), $[8] = t1, $[9] = t2, $[10] = t3) : t3 = $[10], t3;
1080
+ }
1081
+ /**
1082
+ * Reads the pixels of an image file on device: the image is decoded, drawn
1083
+ * onto a small canvas and read back, so nothing leaves the browser.
1084
+ *
1085
+ * @internal
1086
+ */
1087
+ async function readImagePixels(file) {
1088
+ let image = await decodeImage(file);
1089
+ try {
1090
+ let scale = Math.min(1, 96 / Math.max(image.width, image.height)), width = Math.max(1, Math.round(image.width * scale)), height = Math.max(1, Math.round(image.height * scale)), canvas = document.createElement("canvas");
1091
+ canvas.width = width, canvas.height = height;
1092
+ let context = canvas.getContext("2d", { willReadFrequently: !0 });
1093
+ if (!context) throw Error("Could not draw the image");
1094
+ return context.drawImage(image, 0, 0, width, height), {
1095
+ data: context.getImageData(0, 0, width, height).data,
1096
+ width,
1097
+ height
1098
+ };
1099
+ } finally {
1100
+ typeof ImageBitmap < "u" && image instanceof ImageBitmap && image.close();
1101
+ }
1102
+ }
1103
+ async function decodeImage(file) {
1104
+ if (typeof createImageBitmap == "function") try {
1105
+ return await createImageBitmap(file);
1106
+ } catch {}
1107
+ let url = URL.createObjectURL(file);
1108
+ try {
1109
+ return await new Promise((resolve, reject) => {
1110
+ let image = new Image();
1111
+ image.addEventListener("load", () => resolve(image), { once: !0 }), image.addEventListener("error", () => reject(/* @__PURE__ */ Error("Could not decode the image")), { once: !0 }), image.src = url;
1112
+ });
1113
+ } finally {
1114
+ URL.revokeObjectURL(url);
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Extracts the palette of image files on device and hands it to `onPalette`,
1119
+ * along with the file it came from. Failures end up as a toast.
1120
+ *
1121
+ * @internal
1122
+ */
1123
+ function useImagePalette(onPalette) {
1124
+ let $ = c(10), toast = useToast(), [busy, setBusy] = useState(!1), onPaletteRef = useRef(onPalette), mountedRef = useRef(!0), latestPickRef = useRef(0), t0, t1;
1125
+ $[0] === onPalette ? (t0 = $[1], t1 = $[2]) : (t0 = () => {
1126
+ onPaletteRef.current = onPalette;
1127
+ }, t1 = [onPalette], $[0] = onPalette, $[1] = t0, $[2] = t1), useEffect(t0, t1);
1128
+ let t2, t3;
1129
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t2 = () => (mountedRef.current = !0, () => {
1130
+ mountedRef.current = !1;
1131
+ }), t3 = [], $[3] = t2, $[4] = t3) : (t2 = $[3], t3 = $[4]), useEffect(t2, t3);
1132
+ let t4;
1133
+ $[5] === toast ? t4 = $[6] : (t4 = (file) => {
1134
+ let pick = latestPickRef.current += 1;
1135
+ setBusy(!0);
1136
+ let isCurrent = () => mountedRef.current && latestPickRef.current === pick, extract = async () => {
1137
+ let palette = extractImagePalette(await readImagePixels(file));
1138
+ isCurrent() && (Object.values(palette).every(_temp$3) ? toast.push({
1139
+ status: "warning",
1140
+ title: "Found no colors in that image"
1141
+ }) : onPaletteRef.current(palette, file));
1142
+ };
1143
+ extract().catch(() => {
1144
+ isCurrent() && toast.push({
1145
+ status: "error",
1146
+ title: "Could not read that image"
1147
+ });
1148
+ }).finally(() => {
1149
+ isCurrent() && setBusy(!1);
1150
+ });
1151
+ }, $[5] = toast, $[6] = t4);
1152
+ let pickImage = t4, t5;
1153
+ return $[7] !== busy || $[8] !== pickImage ? (t5 = {
1154
+ busy,
1155
+ pickImage
1156
+ }, $[7] = busy, $[8] = pickImage, $[9] = t5) : t5 = $[9], t5;
1157
+ }
1158
+ function _temp$3(swatch) {
1159
+ return swatch === null;
1160
+ }
1161
+ /** The swatches on display: the vibrant family over the muted one */
1162
+ const TILES = [
1163
+ "vibrant",
1164
+ "lightVibrant",
1165
+ "darkVibrant",
1166
+ "muted",
1167
+ "lightMuted",
1168
+ "darkMuted"
1169
+ ], PaletteGrid = styled.div.withConfig({
1170
+ displayName: "PaletteGrid",
1171
+ componentId: "sc-j2tqre-0"
1172
+ })`display:grid;grid-template-columns:repeat(3,${24}px);grid-auto-rows:${24}px;gap:${4}px;&[data-with-image='true']{grid-template-columns:${52}px repeat(3,${24}px);}`, ImageTile = styled.img.withConfig({
1173
+ displayName: "ImageTile",
1174
+ componentId: "sc-j2tqre-1"
1175
+ })`display:block;grid-row:span 2;width:${52}px;height:${52}px;border-radius:3px;object-fit:cover;box-shadow:inset 0 0 0 1px var(--card-border-color);`, Tile = styled.span.withConfig({
1176
+ displayName: "Tile",
1177
+ componentId: "sc-j2tqre-2"
1178
+ })`display:block;border-radius:3px;box-shadow:inset 0 0 0 1px var(--card-border-color);&[data-empty='true']{background:repeating-linear-gradient( -45deg,transparent 0 3px,var(--card-border-color) 3px 4px );}`, VariantRow = styled.div.withConfig({
1179
+ displayName: "VariantRow",
1180
+ componentId: "sc-j2tqre-3"
1181
+ })`display:flex;gap:${8}px;margin:0 -${12}px;padding:0 ${12}px;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-left:${12}px;scrollbar-width:thin;`, VariantButton = styled.button.withConfig({
1182
+ displayName: "VariantButton",
1183
+ componentId: "sc-j2tqre-4"
1184
+ })`appearance:none;flex:none;display:block;box-sizing:border-box;width:calc(var(--variant-width) + 8px);margin:0;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:center;cursor:pointer;scroll-snap-align:start;&:focus{outline:none;}`, VariantFrame = styled.span.withConfig({
1185
+ displayName: "VariantFrame",
1186
+ componentId: "sc-j2tqre-5"
1187
+ })`display:block;box-sizing:border-box;width:calc(var(--variant-width) + 8px);height:calc(var(--variant-width) * 9 / 16 + 8px);padding:4px;border-radius:7px;overflow:hidden;transition:box-shadow 100ms;${VariantButton}:hover &{box-shadow:inset 0 0 0 2px var(--card-border-color);}${VariantButton}[aria-pressed='true'] &{box-shadow:inset 0 0 0 2px var(--card-focus-ring-color);}${VariantButton}:focus-visible &{outline:2px solid var(--card-focus-ring-color);outline-offset:-2px;}`, VariantScale = styled.span.withConfig({
1188
+ displayName: "VariantScale",
1189
+ componentId: "sc-j2tqre-6"
1190
+ })`display:block;width:${144}px;transform:scale(var(--variant-scale));transform-origin:top left;`;
1191
+ /**
1192
+ * Takes the colors of a theme from an image, on device: the image is read
1193
+ * into a canvas and its palette extracted right there — nothing is uploaded.
1194
+ * Shows the image (while the session lasts) next to its palette, offers a
1195
+ * theme variant built around each swatch as a small preview, and a lucky
1196
+ * button that picks an interesting one.
1197
+ *
1198
+ * @internal
1199
+ */
1200
+ function ImagePaletteSection(props) {
1201
+ let $ = c(38), { imageUrl, onPalette, onVariant, onLucky, options, palette } = props, { busy, pickImage } = useImagePalette(onPalette), [dragging, setDragging] = useState(!1), dragDepth = useRef(0), t0;
1202
+ $[0] !== options || $[1] !== palette ? (t0 = palette ? currentImageVariant(options, palette) : null, $[0] = options, $[1] = palette, $[2] = t0) : t0 = $[2];
1203
+ let current = t0, rowRef = useRef(null), [variantWidth, setVariantWidth] = useState(58), t1;
1204
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t1 = () => {
1205
+ let row = rowRef.current;
1206
+ if (!row) return;
1207
+ let measure = () => {
1208
+ let visible = row.clientWidth - 12 - 16;
1209
+ setVariantWidth(Math.max(24, Math.floor(visible / 2.5) - 8));
1210
+ };
1211
+ measure();
1212
+ let observer = new ResizeObserver(measure);
1213
+ return observer.observe(row), () => observer.disconnect();
1214
+ }, $[3] = t1) : t1 = $[3];
1215
+ let t2;
1216
+ $[4] === palette ? t2 = $[5] : (t2 = [palette], $[4] = palette, $[5] = t2), useEffect(t1, t2);
1217
+ let t3;
1218
+ bb0: {
1219
+ if (!palette) {
1220
+ let t4;
1221
+ $[6] === Symbol.for("react.memo_cache_sentinel") ? (t4 = [], $[6] = t4) : t4 = $[6], t3 = t4;
1222
+ break bb0;
1223
+ }
1224
+ let t4;
1225
+ $[7] !== options || $[8] !== palette ? (t4 = IMAGE_PALETTE_VARIANTS.flatMap((variant) => palette[variant] === null ? [] : [{
1226
+ variant,
1227
+ options: applyImagePalette(options, palette, variant)
1228
+ }]), $[7] = options, $[8] = palette, $[9] = t4) : t4 = $[9], t3 = t4;
1229
+ }
1230
+ let variants = t3, t4, t5;
1231
+ $[10] === Symbol.for("react.memo_cache_sentinel") ? (t4 = (event) => {
1232
+ event.preventDefault(), dragDepth.current += 1, setDragging(!0);
1233
+ }, t5 = () => {
1234
+ dragDepth.current = Math.max(0, dragDepth.current - 1), dragDepth.current === 0 && setDragging(!1);
1235
+ }, $[10] = t4, $[11] = t5) : (t4 = $[10], t5 = $[11]);
1236
+ let t6;
1237
+ $[12] === pickImage ? t6 = $[13] : (t6 = (event_1) => {
1238
+ event_1.preventDefault(), dragDepth.current = 0, setDragging(!1);
1239
+ let file = event_1.dataTransfer.files[0];
1240
+ file && pickImage(file);
1241
+ }, $[12] = pickImage, $[13] = t6);
1242
+ let t7 = dragging ? "primary" : "default", t8;
1243
+ $[14] === Symbol.for("react.memo_cache_sentinel") ? (t8 = /* @__PURE__ */ jsx(Box, {
1244
+ flex: 1,
1245
+ children: /* @__PURE__ */ jsx(Text, {
1246
+ size: 1,
1247
+ weight: "medium",
1248
+ children: "Image palette"
1249
+ })
1250
+ }), $[14] = t8) : t8 = $[14];
1251
+ let t9;
1252
+ $[15] !== busy || $[16] !== palette || $[17] !== pickImage ? (t9 = palette && /* @__PURE__ */ jsx(ImageFileButton, {
1253
+ loading: busy,
1254
+ mode: "bleed",
1255
+ onFile: pickImage,
1256
+ padding: 2,
1257
+ tooltip: "Pick another image"
1258
+ }), $[15] = busy, $[16] = palette, $[17] = pickImage, $[18] = t9) : t9 = $[18];
1259
+ let t10;
1260
+ $[19] === t9 ? t10 = $[20] : (t10 = /* @__PURE__ */ jsxs(Flex, {
1261
+ align: "center",
1262
+ gap: 2,
1263
+ children: [t8, t9]
1264
+ }), $[19] = t9, $[20] = t10);
1265
+ let t11;
1266
+ $[21] !== busy || $[22] !== current || $[23] !== imageUrl || $[24] !== onLucky || $[25] !== onVariant || $[26] !== palette || $[27] !== pickImage || $[28] !== variantWidth || $[29] !== variants ? (t11 = palette ? /* @__PURE__ */ jsxs(Fragment, { children: [
1267
+ /* @__PURE__ */ jsxs(PaletteGrid, {
1268
+ "data-with-image": imageUrl !== void 0,
1269
+ children: [imageUrl && /* @__PURE__ */ jsx(ImageTile, {
1270
+ alt: "",
1271
+ src: imageUrl
1272
+ }), TILES.map((key) => {
1273
+ let hex = palette[key], title = IMAGE_PALETTE_TITLES[key];
1274
+ return /* @__PURE__ */ jsx(Tooltip, {
1275
+ animate: !0,
1276
+ content: /* @__PURE__ */ jsx(Text, {
1277
+ size: 1,
1278
+ children: hex ? `${title} · ${hex}` : `No ${title.toLowerCase()} color`
1279
+ }),
1280
+ placement: "bottom",
1281
+ portal: !0,
1282
+ children: /* @__PURE__ */ jsx(Tile, {
1283
+ "data-empty": hex === null,
1284
+ style: { background: hex ?? void 0 }
1285
+ })
1286
+ }, key);
1287
+ })]
1288
+ }),
1289
+ /* @__PURE__ */ jsxs(Stack, {
1290
+ gap: 2,
1291
+ children: [/* @__PURE__ */ jsx(Text, {
1292
+ muted: !0,
1293
+ size: 0,
1294
+ children: "Pick the swatch to build the theme around"
1295
+ }), /* @__PURE__ */ jsx(VariantRow, {
1296
+ ref: rowRef,
1297
+ style: {
1298
+ "--variant-width": `${variantWidth}px`,
1299
+ "--variant-scale": variantWidth / 144
1300
+ },
1301
+ children: variants.map((t12) => {
1302
+ let { variant: variant_0, options: variantOptions } = t12;
1303
+ return /* @__PURE__ */ jsxs(VariantButton, {
1304
+ "aria-pressed": variant_0 === current,
1305
+ onClick: () => onVariant(variant_0),
1306
+ type: "button",
1307
+ children: [/* @__PURE__ */ jsx(VariantFrame, { children: /* @__PURE__ */ jsx(VariantScale, { children: /* @__PURE__ */ jsx(ThemeThumbnail, { options: variantOptions }) }) }), /* @__PURE__ */ jsx(Box, {
1308
+ paddingTop: 1,
1309
+ children: /* @__PURE__ */ jsx(Text, {
1310
+ align: "center",
1311
+ size: 0,
1312
+ textOverflow: "ellipsis",
1313
+ weight: variant_0 === current ? "medium" : "regular",
1314
+ children: IMAGE_PALETTE_TITLES[variant_0]
1315
+ })
1316
+ })]
1317
+ }, variant_0);
1318
+ })
1319
+ })]
1320
+ }),
1321
+ /* @__PURE__ */ jsx(TooltipButton, {
1322
+ icon: SparklesIcon,
1323
+ mode: "ghost",
1324
+ onClick: onLucky,
1325
+ text: "I'm feeling lucky",
1326
+ tooltip: "Build the theme around a swatch that looks interesting",
1327
+ width: "fill"
1328
+ })
1329
+ ] }) : /* @__PURE__ */ jsxs(Stack, {
1330
+ gap: 3,
1331
+ children: [/* @__PURE__ */ jsx(Text, {
1332
+ muted: !0,
1333
+ size: 0,
1334
+ children: "Take the accent, text and background colors from an image — read on device, nothing is uploaded."
1335
+ }), /* @__PURE__ */ jsx(ImageFileButton, {
1336
+ loading: busy,
1337
+ mode: "ghost",
1338
+ onFile: pickImage,
1339
+ text: "Pick an image",
1340
+ tooltip: "Take the colors from an image on your device",
1341
+ width: "fill"
1342
+ })]
1343
+ }), $[21] = busy, $[22] = current, $[23] = imageUrl, $[24] = onLucky, $[25] = onVariant, $[26] = palette, $[27] = pickImage, $[28] = variantWidth, $[29] = variants, $[30] = t11) : t11 = $[30];
1344
+ let t12;
1345
+ $[31] !== t10 || $[32] !== t11 ? (t12 = /* @__PURE__ */ jsxs(Stack, {
1346
+ gap: 3,
1347
+ children: [t10, t11]
1348
+ }), $[31] = t10, $[32] = t11, $[33] = t12) : t12 = $[33];
1349
+ let t13;
1350
+ return $[34] !== t12 || $[35] !== t6 || $[36] !== t7 ? (t13 = /* @__PURE__ */ jsx(Card, {
1351
+ border: !0,
1352
+ onDragEnter: t4,
1353
+ onDragLeave: t5,
1354
+ onDragOver: _temp$2,
1355
+ onDrop: t6,
1356
+ padding: 3,
1357
+ radius: 3,
1358
+ tone: t7,
1359
+ children: t12
1360
+ }), $[34] = t12, $[35] = t6, $[36] = t7, $[37] = t13) : t13 = $[37], t13;
1361
+ }
1362
+ function _temp$2(event_0) {
1363
+ event_0.preventDefault();
1364
+ }
1365
+ const SCHEME_TITLES = {
1366
+ dark: "Dark mode",
1367
+ light: "Light mode"
1368
+ }, SPLIT_HINTS = {
1369
+ dark: "Shown on the right",
1370
+ light: "Shown on the left"
1371
+ }, SCHEME_OPTION_KEYS = [
1372
+ "accent",
1373
+ "text",
1374
+ "background",
1375
+ "contrast"
1376
+ ], Swatch = styled.input.withConfig({
1377
+ displayName: "Swatch",
1378
+ componentId: "sc-rahyxu-0"
1379
+ })`box-sizing:border-box;flex:none;width:33px;height:33px;padding:0;border:1px solid var(--card-border-color);border-radius:4px;background:none;cursor:pointer;&::-webkit-color-swatch-wrapper{padding:0;}&::-webkit-color-swatch{border:none;border-radius:3px;}&::-moz-color-swatch{border:none;border-radius:3px;}`, Range = styled.input.withConfig({
1380
+ displayName: "Range",
1381
+ componentId: "sc-rahyxu-1"
1382
+ })`display:block;width:100%;margin:0;accent-color:var(--card-focus-ring-color);`;
1383
+ /**
1384
+ * The flow for editing one of the user's own themes: its title, a live
1385
+ * preview, and a card per color scheme with the accent/text/background
1386
+ * pickers and the contrast slider of that scheme. Every change applies to the
1387
+ * whole Studio right away — in the scheme the Studio is showing, or in both
1388
+ * side by side in the split preview.
1389
+ *
1390
+ * @internal
1391
+ */
1392
+ function ThemeEditor(props) {
1393
+ let $ = c(41), { focusTitle, slug } = props, { themes, images, split, send } = useThemer(), t0;
1394
+ if ($[0] !== slug || $[1] !== themes) {
1395
+ let t1;
1396
+ $[3] === slug ? t1 = $[4] : (t1 = (candidate) => candidate.slug === slug && candidate.source === "custom", $[3] = slug, $[4] = t1), t0 = themes.find(t1), $[0] = slug, $[1] = themes, $[2] = t0;
1397
+ } else t0 = $[2];
1398
+ let theme = t0;
1399
+ if (!theme) return null;
1400
+ let t1;
1401
+ $[5] !== send || $[6] !== slug || $[7] !== theme.options || $[8] !== theme.palette ? (t1 = (variant) => {
1402
+ theme.palette && variant !== null && send({
1403
+ type: "theme.update",
1404
+ slug,
1405
+ options: applyImagePalette(theme.options, theme.palette, variant)
1406
+ });
1407
+ }, $[5] = send, $[6] = slug, $[7] = theme.options, $[8] = theme.palette, $[9] = t1) : t1 = $[9];
1408
+ let applyVariant = t1, t2 = images[slug], t3;
1409
+ $[10] === send ? t3 = $[11] : (t3 = () => send({ type: "flow.list" }), $[10] = send, $[11] = t3);
1410
+ let t4;
1411
+ $[12] !== applyVariant || $[13] !== theme.options || $[14] !== theme.palette ? (t4 = () => {
1412
+ theme.palette && applyVariant(pickLuckyVariant(theme.palette, { exclude: currentImageVariant(theme.options, theme.palette) }));
1413
+ }, $[12] = applyVariant, $[13] = theme.options, $[14] = theme.palette, $[15] = t4) : t4 = $[15];
1414
+ let t5;
1415
+ $[16] !== send || $[17] !== slug ? (t5 = (options) => send({
1416
+ type: "theme.update",
1417
+ slug,
1418
+ options
1419
+ }), $[16] = send, $[17] = slug, $[18] = t5) : t5 = $[18];
1420
+ let t6;
1421
+ $[19] !== send || $[20] !== slug || $[21] !== theme.options ? (t6 = (palette, file) => send({
1422
+ type: "theme.update",
1423
+ slug,
1424
+ options: applyImagePalette(theme.options, palette),
1425
+ palette,
1426
+ imageUrl: URL.createObjectURL(file)
1427
+ }), $[19] = send, $[20] = slug, $[21] = theme.options, $[22] = t6) : t6 = $[22];
1428
+ let t7, t8;
1429
+ $[23] !== send || $[24] !== slug ? (t7 = () => send({
1430
+ type: "theme.remove",
1431
+ slug
1432
+ }), t8 = (title) => send({
1433
+ type: "theme.update",
1434
+ slug,
1435
+ title
1436
+ }), $[23] = send, $[24] = slug, $[25] = t7, $[26] = t8) : (t7 = $[25], t8 = $[26]);
1437
+ let t9;
1438
+ return $[27] !== applyVariant || $[28] !== focusTitle || $[29] !== split || $[30] !== t2 || $[31] !== t3 || $[32] !== t4 || $[33] !== t5 || $[34] !== t6 || $[35] !== t7 || $[36] !== t8 || $[37] !== theme.options || $[38] !== theme.palette || $[39] !== theme.title ? (t9 = /* @__PURE__ */ jsx(ThemeEditorForm, {
1439
+ focusTitle,
1440
+ imageUrl: t2,
1441
+ onDone: t3,
1442
+ onLucky: t4,
1443
+ onOptionsChange: t5,
1444
+ onPalette: t6,
1445
+ onRemove: t7,
1446
+ onTitleChange: t8,
1447
+ onVariant: applyVariant,
1448
+ options: theme.options,
1449
+ palette: theme.palette,
1450
+ split,
1451
+ title: theme.title
1452
+ }), $[27] = applyVariant, $[28] = focusTitle, $[29] = split, $[30] = t2, $[31] = t3, $[32] = t4, $[33] = t5, $[34] = t6, $[35] = t7, $[36] = t8, $[37] = theme.options, $[38] = theme.palette, $[39] = theme.title, $[40] = t9) : t9 = $[40], t9;
1453
+ }
1454
+ function ThemeEditorForm(props) {
1455
+ let $ = c(49), { focusTitle, imageUrl, onDone, onLucky, onOptionsChange, onPalette, onRemove, onTitleChange, onVariant, options, palette, split, title } = props, studioScheme = useColorSchemeValue(), t0;
1456
+ $[0] === options ? t0 = $[1] : (t0 = resolveThemeOptions(options), $[0] = options, $[1] = t0);
1457
+ let resolved = t0, t1;
1458
+ $[2] === options ? t1 = $[3] : (t1 = buildPalette(options), $[2] = options, $[3] = t1);
1459
+ let palettes = t1, t2;
1460
+ $[4] !== onOptionsChange || $[5] !== options ? (t2 = (scheme, changes) => {
1461
+ let values = {
1462
+ ...options[scheme],
1463
+ ...changes
1464
+ };
1465
+ for (let key of SCHEME_OPTION_KEYS) values[key] === void 0 && delete values[key];
1466
+ let next = { ...options };
1467
+ Object.keys(values).length === 0 ? delete next[scheme] : next[scheme] = values, onOptionsChange(next);
1468
+ }, $[4] = onOptionsChange, $[5] = options, $[6] = t2) : t2 = $[6];
1469
+ let patchScheme = t2, t3;
1470
+ $[7] === options ? t3 = $[8] : (t3 = /* @__PURE__ */ jsx(ThemeThumbnail, { options }), $[7] = options, $[8] = t3);
1471
+ let t4;
1472
+ $[9] === Symbol.for("react.memo_cache_sentinel") ? (t4 = /* @__PURE__ */ jsx(Text, {
1473
+ size: 1,
1474
+ weight: "medium",
1475
+ children: "Title"
1476
+ }), $[9] = t4) : t4 = $[9];
1477
+ let t5;
1478
+ $[10] === onTitleChange ? t5 = $[11] : (t5 = (event) => onTitleChange(event.currentTarget.value), $[10] = onTitleChange, $[11] = t5);
1479
+ let t6 = focusTitle ? _temp$1 : void 0, t7;
1480
+ $[12] !== focusTitle || $[13] !== t5 || $[14] !== t6 || $[15] !== title ? (t7 = /* @__PURE__ */ jsxs(Stack, {
1481
+ gap: 2,
1482
+ children: [t4, /* @__PURE__ */ jsx(TextInput, {
1483
+ autoFocus: focusTitle,
1484
+ fontSize: 1,
1485
+ onChange: t5,
1486
+ onFocus: t6,
1487
+ padding: 2,
1488
+ placeholder: "Untitled theme",
1489
+ value: title
1490
+ })]
1491
+ }), $[12] = focusTitle, $[13] = t5, $[14] = t6, $[15] = title, $[16] = t7) : t7 = $[16];
1492
+ let t8;
1493
+ $[17] !== imageUrl || $[18] !== onLucky || $[19] !== onPalette || $[20] !== onVariant || $[21] !== options || $[22] !== palette ? (t8 = /* @__PURE__ */ jsx(ImagePaletteSection, {
1494
+ imageUrl,
1495
+ onLucky,
1496
+ onPalette,
1497
+ onVariant,
1498
+ options,
1499
+ palette
1500
+ }), $[17] = imageUrl, $[18] = onLucky, $[19] = onPalette, $[20] = onVariant, $[21] = options, $[22] = palette, $[23] = t8) : t8 = $[23];
1501
+ let t9;
1502
+ $[24] !== options || $[25] !== palettes || $[26] !== patchScheme || $[27] !== resolved || $[28] !== split || $[29] !== studioScheme ? (t9 = SCHEMES.map((scheme_0) => /* @__PURE__ */ jsx(SchemeCard, {
1503
+ active: split || scheme_0 === studioScheme,
1504
+ onChange: (changes_0) => patchScheme(scheme_0, changes_0),
1505
+ options: options[scheme_0] ?? {},
1506
+ palette: palettes[scheme_0],
1507
+ resolved: resolved[scheme_0],
1508
+ scheme: scheme_0,
1509
+ split
1510
+ }, scheme_0)), $[24] = options, $[25] = palettes, $[26] = patchScheme, $[27] = resolved, $[28] = split, $[29] = studioScheme, $[30] = t9) : t9 = $[30];
1511
+ let t10;
1512
+ $[31] === t9 ? t10 = $[32] : (t10 = /* @__PURE__ */ jsx(ScrollAreaBleed, { children: t9 }), $[31] = t9, $[32] = t10);
1513
+ let t11;
1514
+ $[33] !== t10 || $[34] !== t3 || $[35] !== t7 || $[36] !== t8 ? (t11 = /* @__PURE__ */ jsx(ScrollArea, {
1515
+ padding: 3,
1516
+ children: /* @__PURE__ */ jsxs(Stack, {
1517
+ gap: 4,
1518
+ children: [
1519
+ t3,
1520
+ t7,
1521
+ t8,
1522
+ t10
1523
+ ]
1524
+ })
1525
+ }), $[33] = t10, $[34] = t3, $[35] = t7, $[36] = t8, $[37] = t11) : t11 = $[37];
1526
+ let t12;
1527
+ $[38] === onRemove ? t12 = $[39] : (t12 = /* @__PURE__ */ jsx(TooltipButton, {
1528
+ icon: TrashIcon,
1529
+ mode: "ghost",
1530
+ onClick: onRemove,
1531
+ text: "Remove",
1532
+ tone: "critical",
1533
+ tooltip: "Remove the theme — it can be restored until it is deleted"
1534
+ }), $[38] = onRemove, $[39] = t12);
1535
+ let t13;
1536
+ $[40] === Symbol.for("react.memo_cache_sentinel") ? (t13 = /* @__PURE__ */ jsx(Box, { flex: 1 }), $[40] = t13) : t13 = $[40];
1537
+ let t14;
1538
+ $[41] === onDone ? t14 = $[42] : (t14 = /* @__PURE__ */ jsx(Button, {
1539
+ mode: "ghost",
1540
+ onClick: onDone,
1541
+ text: "Done"
1542
+ }), $[41] = onDone, $[42] = t14);
1543
+ let t15;
1544
+ $[43] !== t12 || $[44] !== t14 ? (t15 = /* @__PURE__ */ jsx(Card, {
1545
+ borderTop: !0,
1546
+ padding: 3,
1547
+ children: /* @__PURE__ */ jsxs(Flex, {
1548
+ gap: 2,
1549
+ children: [
1550
+ t12,
1551
+ t13,
1552
+ t14
1553
+ ]
1554
+ })
1555
+ }), $[43] = t12, $[44] = t14, $[45] = t15) : t15 = $[45];
1556
+ let t16;
1557
+ return $[46] !== t11 || $[47] !== t15 ? (t16 = /* @__PURE__ */ jsxs(Fragment, { children: [t11, t15] }), $[46] = t11, $[47] = t15, $[48] = t16) : t16 = $[48], t16;
1558
+ }
1559
+ function _temp$1(event_0) {
1560
+ return event_0.currentTarget.select();
1561
+ }
1562
+ /**
1563
+ * The colors of one scheme. The cards span the sidebar edge to edge, one
1564
+ * after the other with a border between them, in the sidebar's own scheme —
1565
+ * the swatches show the colors they edit. The scheme the Studio is showing is
1566
+ * marked as active: its changes show up in the Studio right away, the other
1567
+ * scheme's only once the appearance setting switches to it — or both are
1568
+ * active, in the split preview.
1569
+ */
1570
+ function SchemeCard(props) {
1571
+ let $ = c(66), { active, onChange, options, palette, resolved, scheme, split } = props, name = SCHEME_TITLES[scheme], t0;
1572
+ $[0] === name ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(Box, {
1573
+ flex: 1,
1574
+ children: /* @__PURE__ */ jsx(Text, {
1575
+ size: 1,
1576
+ weight: "medium",
1577
+ children: name
1578
+ })
1579
+ }), $[0] = name, $[1] = t0);
1580
+ let t1;
1581
+ $[2] === active ? t1 = $[3] : (t1 = active && /* @__PURE__ */ jsx(Badge, {
1582
+ fontSize: 0,
1583
+ tone: "primary",
1584
+ children: "Active"
1585
+ }), $[2] = active, $[3] = t1);
1586
+ let t2;
1587
+ $[4] !== t0 || $[5] !== t1 ? (t2 = /* @__PURE__ */ jsxs(Flex, {
1588
+ align: "center",
1589
+ gap: 2,
1590
+ children: [t0, t1]
1591
+ }), $[4] = t0, $[5] = t1, $[6] = t2) : t2 = $[6];
1592
+ let t3 = split ? SPLIT_HINTS[scheme] : active ? "Shown in the Studio right now" : `Shown once the Studio appearance is ${scheme}`, t4;
1593
+ $[7] === t3 ? t4 = $[8] : (t4 = /* @__PURE__ */ jsx(Text, {
1594
+ muted: !0,
1595
+ size: 0,
1596
+ children: t3
1597
+ }), $[7] = t3, $[8] = t4);
1598
+ let t5;
1599
+ $[9] !== t2 || $[10] !== t4 ? (t5 = /* @__PURE__ */ jsxs(Stack, {
1600
+ gap: 2,
1601
+ children: [t2, t4]
1602
+ }), $[9] = t2, $[10] = t4, $[11] = t5) : t5 = $[11];
1603
+ let t6 = options.accent === void 0, t7 = `${name} accent`, t8, t9;
1604
+ $[12] === onChange ? (t8 = $[13], t9 = $[14]) : (t8 = (accent) => onChange({ accent }), t9 = () => onChange({ accent: void 0 }), $[12] = onChange, $[13] = t8, $[14] = t9);
1605
+ let t10;
1606
+ $[15] !== resolved.accent || $[16] !== t6 || $[17] !== t7 || $[18] !== t8 || $[19] !== t9 ? (t10 = /* @__PURE__ */ jsx(ColorRow, {
1607
+ auto: t6,
1608
+ autoLabel: "default",
1609
+ label: t7,
1610
+ onChange: t8,
1611
+ onClear: t9,
1612
+ title: "Accent",
1613
+ value: resolved.accent
1614
+ }), $[15] = resolved.accent, $[16] = t6, $[17] = t7, $[18] = t8, $[19] = t9, $[20] = t10) : t10 = $[20];
1615
+ let t11 = options.text === void 0, t12 = `${name} text`, t13, t14;
1616
+ $[21] === onChange ? (t13 = $[22], t14 = $[23]) : (t13 = (text) => onChange({ text }), t14 = () => onChange({ text: void 0 }), $[21] = onChange, $[22] = t13, $[23] = t14);
1617
+ let t15;
1618
+ $[24] !== resolved.text || $[25] !== t11 || $[26] !== t12 || $[27] !== t13 || $[28] !== t14 ? (t15 = /* @__PURE__ */ jsx(ColorRow, {
1619
+ auto: t11,
1620
+ label: t12,
1621
+ onChange: t13,
1622
+ onClear: t14,
1623
+ title: "Text",
1624
+ value: resolved.text
1625
+ }), $[24] = resolved.text, $[25] = t11, $[26] = t12, $[27] = t13, $[28] = t14, $[29] = t15) : t15 = $[29];
1626
+ let t16 = scheme === "dark" ? palette.black : palette.white, t17 = options.background === void 0, t18 = `${name} background`, t19, t20;
1627
+ $[30] === onChange ? (t19 = $[31], t20 = $[32]) : (t19 = (background) => onChange({ background }), t20 = () => onChange({ background: void 0 }), $[30] = onChange, $[31] = t19, $[32] = t20);
1628
+ let t21;
1629
+ $[33] !== resolved.background || $[34] !== t16 || $[35] !== t17 || $[36] !== t18 || $[37] !== t19 || $[38] !== t20 ? (t21 = /* @__PURE__ */ jsx(ColorRow, {
1630
+ adjusted: t16,
1631
+ auto: t17,
1632
+ label: t18,
1633
+ onChange: t19,
1634
+ onClear: t20,
1635
+ title: "Background",
1636
+ value: resolved.background
1637
+ }), $[33] = resolved.background, $[34] = t16, $[35] = t17, $[36] = t18, $[37] = t19, $[38] = t20, $[39] = t21) : t21 = $[39];
1638
+ let t22;
1639
+ $[40] === Symbol.for("react.memo_cache_sentinel") ? (t22 = /* @__PURE__ */ jsx(Text, {
1640
+ size: 1,
1641
+ children: "Contrast"
1642
+ }), $[40] = t22) : t22 = $[40];
1643
+ let t23 = options.contrast === void 0 ? " · auto" : "", t24;
1644
+ $[41] !== resolved.contrast || $[42] !== t23 ? (t24 = /* @__PURE__ */ jsxs(Stack, {
1645
+ flex: 1,
1646
+ gap: 2,
1647
+ children: [t22, /* @__PURE__ */ jsxs(Text, {
1648
+ muted: !0,
1649
+ size: 0,
1650
+ children: [resolved.contrast, t23]
1651
+ })]
1652
+ }), $[41] = resolved.contrast, $[42] = t23, $[43] = t24) : t24 = $[43];
1653
+ let t25;
1654
+ $[44] !== onChange || $[45] !== options.contrast ? (t25 = options.contrast !== void 0 && /* @__PURE__ */ jsx(TooltipButton, {
1655
+ icon: ResetIcon,
1656
+ mode: "bleed",
1657
+ onClick: () => onChange({ contrast: void 0 }),
1658
+ padding: 2,
1659
+ tooltip: "Reset to auto"
1660
+ }), $[44] = onChange, $[45] = options.contrast, $[46] = t25) : t25 = $[46];
1661
+ let t26;
1662
+ $[47] !== t24 || $[48] !== t25 ? (t26 = /* @__PURE__ */ jsxs(Flex, {
1663
+ align: "center",
1664
+ gap: 2,
1665
+ children: [t24, t25]
1666
+ }), $[47] = t24, $[48] = t25, $[49] = t26) : t26 = $[49];
1667
+ let t27 = `${name} contrast`, t28;
1668
+ $[50] === onChange ? t28 = $[51] : (t28 = (event) => onChange({ contrast: Number(event.currentTarget.value) }), $[50] = onChange, $[51] = t28);
1669
+ let t29;
1670
+ $[52] !== resolved.contrast || $[53] !== t27 || $[54] !== t28 ? (t29 = /* @__PURE__ */ jsx(Range, {
1671
+ "aria-label": t27,
1672
+ max: 100,
1673
+ min: 15,
1674
+ onChange: t28,
1675
+ step: 1,
1676
+ type: "range",
1677
+ value: resolved.contrast
1678
+ }), $[52] = resolved.contrast, $[53] = t27, $[54] = t28, $[55] = t29) : t29 = $[55];
1679
+ let t30;
1680
+ $[56] === Symbol.for("react.memo_cache_sentinel") ? (t30 = /* @__PURE__ */ jsx(Text, {
1681
+ muted: !0,
1682
+ size: 0,
1683
+ children: "100 keeps text and borders neutral — lower values blend in the accent"
1684
+ }), $[56] = t30) : t30 = $[56];
1685
+ let t31;
1686
+ $[57] !== t26 || $[58] !== t29 ? (t31 = /* @__PURE__ */ jsxs(Stack, {
1687
+ gap: 3,
1688
+ children: [
1689
+ t26,
1690
+ t29,
1691
+ t30
1692
+ ]
1693
+ }), $[57] = t26, $[58] = t29, $[59] = t31) : t31 = $[59];
1694
+ let t32;
1695
+ return $[60] !== t10 || $[61] !== t15 || $[62] !== t21 || $[63] !== t31 || $[64] !== t5 ? (t32 = /* @__PURE__ */ jsx(Card, {
1696
+ borderTop: !0,
1697
+ padding: 3,
1698
+ children: /* @__PURE__ */ jsxs(Stack, {
1699
+ gap: 4,
1700
+ children: [
1701
+ t5,
1702
+ t10,
1703
+ t15,
1704
+ t21,
1705
+ t31
1706
+ ]
1707
+ })
1708
+ }), $[60] = t10, $[61] = t15, $[62] = t21, $[63] = t31, $[64] = t5, $[65] = t32) : t32 = $[65], t32;
1709
+ }
1710
+ /** One color of a scheme: a swatch with its value and an optional reset button */
1711
+ function ColorRow(props) {
1712
+ let $ = c(24), { adjusted, auto, autoLabel: t0, label, onChange, onClear, title, value } = props, autoLabel = t0 === void 0 ? "auto" : t0, t1;
1713
+ $[0] === onChange ? t1 = $[1] : (t1 = (event) => onChange(event.currentTarget.value), $[0] = onChange, $[1] = t1);
1714
+ let t2;
1715
+ $[2] !== label || $[3] !== t1 || $[4] !== value ? (t2 = /* @__PURE__ */ jsx(Swatch, {
1716
+ "aria-label": label,
1717
+ onChange: t1,
1718
+ type: "color",
1719
+ value
1720
+ }), $[2] = label, $[3] = t1, $[4] = value, $[5] = t2) : t2 = $[5];
1721
+ let t3;
1722
+ $[6] === Symbol.for("react.memo_cache_sentinel") ? (t3 = { minWidth: 0 }, $[6] = t3) : t3 = $[6];
1723
+ let t4;
1724
+ $[7] === title ? t4 = $[8] : (t4 = /* @__PURE__ */ jsx(Text, {
1725
+ size: 1,
1726
+ children: title
1727
+ }), $[7] = title, $[8] = t4);
1728
+ let t5 = adjusted !== void 0 && adjusted !== value ? ` → ${adjusted}` : "", t6 = auto ? ` · ${autoLabel}` : "", t7;
1729
+ $[9] !== t5 || $[10] !== t6 || $[11] !== value ? (t7 = /* @__PURE__ */ jsxs(Text, {
1730
+ muted: !0,
1731
+ size: 0,
1732
+ textOverflow: "ellipsis",
1733
+ children: [
1734
+ value,
1735
+ t5,
1736
+ t6
1737
+ ]
1738
+ }), $[9] = t5, $[10] = t6, $[11] = value, $[12] = t7) : t7 = $[12];
1739
+ let t8;
1740
+ $[13] !== t4 || $[14] !== t7 ? (t8 = /* @__PURE__ */ jsxs(Stack, {
1741
+ flex: 1,
1742
+ gap: 2,
1743
+ style: t3,
1744
+ children: [t4, t7]
1745
+ }), $[13] = t4, $[14] = t7, $[15] = t8) : t8 = $[15];
1746
+ let t9;
1747
+ $[16] !== auto || $[17] !== autoLabel || $[18] !== onClear ? (t9 = onClear && !auto && /* @__PURE__ */ jsx(TooltipButton, {
1748
+ icon: ResetIcon,
1749
+ mode: "bleed",
1750
+ onClick: onClear,
1751
+ padding: 2,
1752
+ tooltip: `Reset to ${autoLabel}`
1753
+ }), $[16] = auto, $[17] = autoLabel, $[18] = onClear, $[19] = t9) : t9 = $[19];
1754
+ let t10;
1755
+ return $[20] !== t2 || $[21] !== t8 || $[22] !== t9 ? (t10 = /* @__PURE__ */ jsxs(Flex, {
1756
+ align: "center",
1757
+ gap: 2,
1758
+ children: [
1759
+ t2,
1760
+ t8,
1761
+ t9
1762
+ ]
1763
+ }), $[20] = t2, $[21] = t8, $[22] = t9, $[23] = t10) : t10 = $[23], t10;
1764
+ }
1765
+ const Root = styled.div.withConfig({
1766
+ displayName: "Root",
1767
+ componentId: "sc-krwbzw-0"
1768
+ })`position:relative;`, PickButton = styled.button.withConfig({
1769
+ displayName: "PickButton",
1770
+ componentId: "sc-krwbzw-1"
1771
+ })`appearance:none;display:block;box-sizing:border-box;width:100%;margin:0;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:center;cursor:pointer;&:focus{outline:none;}`, Frame$1 = styled.span.withConfig({
1772
+ displayName: "Frame",
1773
+ componentId: "sc-krwbzw-2"
1774
+ })`display:block;padding:4px;border-radius:10px;transition:box-shadow 100ms;${PickButton}:hover &{box-shadow:inset 0 0 0 2px var(--card-border-color);}${PickButton}[aria-pressed='true'] &{box-shadow:inset 0 0 0 2px var(--card-focus-ring-color);}${PickButton}:focus-visible &{outline:2px solid var(--card-focus-ring-color);outline-offset:-2px;}`, MenuSlot = styled.div.withConfig({
1775
+ displayName: "MenuSlot",
1776
+ componentId: "sc-krwbzw-3"
1777
+ })`position:absolute;top:10px;right:10px;opacity:0;transition:opacity 100ms;${Root}:hover &,${Root}:focus-within &,&[data-visible='true']{opacity:1;}`;
1778
+ /**
1779
+ * One theme in the list: the floating preview with the title below it, like
1780
+ * an appearance option in macOS System Settings. Clicking it applies the
1781
+ * theme to the whole Studio; its menu holds the edit, duplicate and remove
1782
+ * flows that apply to the theme.
1783
+ *
1784
+ * @internal
1785
+ */
1786
+ function ThemeCard(props) {
1787
+ let $ = c(52), { active, theme } = props, { send } = useThemer(), { scheme } = useRootTheme(), [menuOpen, setMenuOpen] = useState(!1), t0;
1788
+ $[0] === theme.title ? t0 = $[1] : (t0 = displayTitle(theme.title), $[0] = theme.title, $[1] = t0);
1789
+ let title = t0, t1;
1790
+ $[2] !== send || $[3] !== theme.slug ? (t1 = () => send({
1791
+ type: "theme.pick",
1792
+ slug: theme.slug
1793
+ }), $[2] = send, $[3] = theme.slug, $[4] = t1) : t1 = $[4];
1794
+ let t2;
1795
+ $[5] === theme.options ? t2 = $[6] : (t2 = /* @__PURE__ */ jsx(Frame$1, { children: /* @__PURE__ */ jsx(ThemeThumbnail, { options: theme.options }) }), $[5] = theme.options, $[6] = t2);
1796
+ let t3 = active ? "medium" : "regular", t4;
1797
+ $[7] !== t3 || $[8] !== title ? (t4 = /* @__PURE__ */ jsx(Box, {
1798
+ as: "span",
1799
+ display: "block",
1800
+ paddingTop: 2,
1801
+ paddingX: 2,
1802
+ children: /* @__PURE__ */ jsx(Text, {
1803
+ align: "center",
1804
+ as: "span",
1805
+ size: 1,
1806
+ textOverflow: "ellipsis",
1807
+ weight: t3,
1808
+ children: title
1809
+ })
1810
+ }), $[7] = t3, $[8] = title, $[9] = t4) : t4 = $[9];
1811
+ let t5;
1812
+ $[10] !== active || $[11] !== t1 || $[12] !== t2 || $[13] !== t4 ? (t5 = /* @__PURE__ */ jsxs(PickButton, {
1813
+ "aria-pressed": active,
1814
+ onClick: t1,
1815
+ type: "button",
1816
+ children: [t2, t4]
1817
+ }), $[10] = active, $[11] = t1, $[12] = t2, $[13] = t4, $[14] = t5) : t5 = $[14];
1818
+ let t6;
1819
+ $[15] === Symbol.for("react.memo_cache_sentinel") ? (t6 = /* @__PURE__ */ jsx(Text, {
1820
+ size: 1,
1821
+ children: "Show more"
1822
+ }), $[15] = t6) : t6 = $[15];
1823
+ let t7;
1824
+ $[16] === Symbol.for("react.memo_cache_sentinel") ? (t7 = { display: "inline-block" }, $[16] = t7) : t7 = $[16];
1825
+ let t8 = `Show more for ${title}`, t9;
1826
+ $[17] === t8 ? t9 = $[18] : (t9 = /* @__PURE__ */ jsx(Button, {
1827
+ "aria-label": t8,
1828
+ fontSize: 1,
1829
+ icon: EllipsisHorizontalIcon,
1830
+ mode: "ghost",
1831
+ padding: 1,
1832
+ radius: 2
1833
+ }), $[17] = t8, $[18] = t9);
1834
+ let t10 = `themer-theme-${theme.slug}`, t11;
1835
+ $[19] !== send || $[20] !== theme.slug || $[21] !== theme.source ? (t11 = theme.source === "custom" && /* @__PURE__ */ jsx(MenuItem, {
1836
+ icon: EditIcon,
1837
+ onClick: () => send({
1838
+ type: "theme.edit",
1839
+ slug: theme.slug
1840
+ }),
1841
+ text: "Edit"
1842
+ }), $[19] = send, $[20] = theme.slug, $[21] = theme.source, $[22] = t11) : t11 = $[22];
1843
+ let t12;
1844
+ $[23] !== send || $[24] !== theme.slug ? (t12 = () => send({
1845
+ type: "theme.duplicate",
1846
+ slug: theme.slug
1847
+ }), $[23] = send, $[24] = theme.slug, $[25] = t12) : t12 = $[25];
1848
+ let t13 = theme.source === "custom" ? "Duplicate" : "Duplicate to edit", t14;
1849
+ $[26] !== t12 || $[27] !== t13 ? (t14 = /* @__PURE__ */ jsx(MenuItem, {
1850
+ icon: CopyIcon,
1851
+ onClick: t12,
1852
+ text: t13
1853
+ }), $[26] = t12, $[27] = t13, $[28] = t14) : t14 = $[28];
1854
+ let t15;
1855
+ $[29] !== send || $[30] !== theme.slug || $[31] !== theme.source ? (t15 = theme.source !== "config" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MenuDivider, {}), /* @__PURE__ */ jsx(MenuItem, {
1856
+ icon: TrashIcon,
1857
+ onClick: () => send({
1858
+ type: "theme.remove",
1859
+ slug: theme.slug
1860
+ }),
1861
+ text: "Remove",
1862
+ tone: "critical"
1863
+ })] }), $[29] = send, $[30] = theme.slug, $[31] = theme.source, $[32] = t15) : t15 = $[32];
1864
+ let t16;
1865
+ $[33] !== t11 || $[34] !== t14 || $[35] !== t15 ? (t16 = /* @__PURE__ */ jsxs(Menu, { children: [
1866
+ t11,
1867
+ t14,
1868
+ t15
1869
+ ] }), $[33] = t11, $[34] = t14, $[35] = t15, $[36] = t16) : t16 = $[36];
1870
+ let t17, t18;
1871
+ $[37] === Symbol.for("react.memo_cache_sentinel") ? (t17 = () => setMenuOpen(!1), t18 = () => setMenuOpen(!0), $[37] = t17, $[38] = t18) : (t17 = $[37], t18 = $[38]);
1872
+ let t19;
1873
+ $[39] === scheme ? t19 = $[40] : (t19 = {
1874
+ animate: !0,
1875
+ placement: "bottom-end",
1876
+ portal: !0,
1877
+ scheme
1878
+ }, $[39] = scheme, $[40] = t19);
1879
+ let t20;
1880
+ $[41] !== t10 || $[42] !== t16 || $[43] !== t19 || $[44] !== t9 ? (t20 = /* @__PURE__ */ jsx(Tooltip, {
1881
+ animate: !0,
1882
+ content: t6,
1883
+ placement: "bottom",
1884
+ portal: !0,
1885
+ children: /* @__PURE__ */ jsx(Card, {
1886
+ radius: 2,
1887
+ scheme: "dark",
1888
+ style: t7,
1889
+ children: /* @__PURE__ */ jsx(MenuButton, {
1890
+ button: t9,
1891
+ id: t10,
1892
+ menu: t16,
1893
+ onClose: t17,
1894
+ onOpen: t18,
1895
+ popover: t19
1896
+ })
1897
+ })
1898
+ }), $[41] = t10, $[42] = t16, $[43] = t19, $[44] = t9, $[45] = t20) : t20 = $[45];
1899
+ let t21;
1900
+ $[46] !== menuOpen || $[47] !== t20 ? (t21 = /* @__PURE__ */ jsx(MenuSlot, {
1901
+ "data-visible": menuOpen,
1902
+ children: t20
1903
+ }), $[46] = menuOpen, $[47] = t20, $[48] = t21) : t21 = $[48];
1904
+ let t22;
1905
+ return $[49] !== t21 || $[50] !== t5 ? (t22 = /* @__PURE__ */ jsxs(Root, { children: [t5, t21] }), $[49] = t21, $[50] = t5, $[51] = t22) : t22 = $[51], t22;
1906
+ }
1907
+ /**
1908
+ * The flow for picking a theme: one column of theme cards — the configured
1909
+ * theme, the presets and the user's own themes — with the entry points to
1910
+ * the add and restore flows below.
1911
+ *
1912
+ * @internal
1913
+ */
1914
+ function ThemeList() {
1915
+ let $ = c(24), { themes, removed, active, send } = useThemer(), t0;
1916
+ $[0] === send ? t0 = $[1] : (t0 = (palette, file) => send({
1917
+ type: "theme.add",
1918
+ title: titleFromFileName(file.name),
1919
+ options: optionsFromImagePalette(palette),
1920
+ palette,
1921
+ imageUrl: URL.createObjectURL(file)
1922
+ }), $[0] = send, $[1] = t0);
1923
+ let { busy, pickImage } = useImagePalette(t0), t1;
1924
+ if ($[2] !== active || $[3] !== themes) {
1925
+ let t2;
1926
+ $[5] === active ? t2 = $[6] : (t2 = (theme) => /* @__PURE__ */ jsx(ThemeCard, {
1927
+ active: theme.slug === active.slug,
1928
+ theme
1929
+ }, theme.slug), $[5] = active, $[6] = t2), t1 = themes.map(t2), $[2] = active, $[3] = themes, $[4] = t1;
1930
+ } else t1 = $[4];
1931
+ let t2;
1932
+ $[7] !== removed.length || $[8] !== send ? (t2 = removed.length > 0 && /* @__PURE__ */ jsx(Button, {
1933
+ icon: RestoreIcon,
1934
+ mode: "bleed",
1935
+ onClick: () => send({ type: "flow.removed" }),
1936
+ text: `Show removed (${removed.length})`,
1937
+ width: "fill"
1938
+ }), $[7] = removed.length, $[8] = send, $[9] = t2) : t2 = $[9];
1939
+ let t3;
1940
+ $[10] !== t1 || $[11] !== t2 ? (t3 = /* @__PURE__ */ jsx(ScrollArea, {
1941
+ padding: 3,
1942
+ children: /* @__PURE__ */ jsxs(Stack, {
1943
+ gap: 4,
1944
+ children: [t1, t2]
1945
+ })
1946
+ }), $[10] = t1, $[11] = t2, $[12] = t3) : t3 = $[12];
1947
+ let t4;
1948
+ $[13] === send ? t4 = $[14] : (t4 = /* @__PURE__ */ jsx(Box, {
1949
+ flex: 1,
1950
+ children: /* @__PURE__ */ jsx(TooltipButton, {
1951
+ icon: AddIcon,
1952
+ mode: "ghost",
1953
+ onClick: () => send({ type: "theme.add" }),
1954
+ text: "Add theme",
1955
+ tooltip: "Add a theme based on the applied one",
1956
+ width: "fill"
1957
+ })
1958
+ }), $[13] = send, $[14] = t4);
1959
+ let t5;
1960
+ $[15] !== busy || $[16] !== pickImage ? (t5 = /* @__PURE__ */ jsx(ImageFileButton, {
1961
+ loading: busy,
1962
+ mode: "ghost",
1963
+ onFile: pickImage,
1964
+ tooltip: "Add a theme from the colors of an image"
1965
+ }), $[15] = busy, $[16] = pickImage, $[17] = t5) : t5 = $[17];
1966
+ let t6;
1967
+ $[18] !== t4 || $[19] !== t5 ? (t6 = /* @__PURE__ */ jsx(Card, {
1968
+ borderTop: !0,
1969
+ padding: 3,
1970
+ children: /* @__PURE__ */ jsxs(Flex, {
1971
+ gap: 2,
1972
+ children: [t4, t5]
1973
+ })
1974
+ }), $[18] = t4, $[19] = t5, $[20] = t6) : t6 = $[20];
1975
+ let t7;
1976
+ return $[21] !== t3 || $[22] !== t6 ? (t7 = /* @__PURE__ */ jsxs(Fragment, { children: [t3, t6] }), $[21] = t3, $[22] = t6, $[23] = t7) : t7 = $[23], t7;
51
1977
  }
52
1978
  /**
53
1979
  * Serializes theme options into the `buildTheme` call to paste into
54
- * `sanity.config.ts`, keeping only what differs from the derived defaults.
1980
+ * `sanity.config.ts`, keeping only what differs from the derived defaults in
1981
+ * each scheme.
55
1982
  *
56
1983
  * Options that boil down to the stock Studio theme need nothing from this
57
1984
  * package — so they serialize to a bare `buildTheme()` from `@sanity/ui/theme`
@@ -62,77 +1989,35 @@ function sameOptions(a, b) {
62
1989
  function createThemeSnippet(options) {
63
1990
  let minimized = minimizeOptions(options);
64
1991
  if (minimized === null) return "import {buildTheme} from '@sanity/ui/theme'\n\nexport const theme = buildTheme()\n";
65
- let entries = [` accent: '${minimized.accent}',`];
66
- if (minimized.text !== void 0 && entries.push(` text: '${minimized.text}',`), minimized.background) {
67
- let parts = [];
68
- minimized.background.dark !== void 0 && parts.push(`dark: '${minimized.background.dark}'`), minimized.background.light !== void 0 && parts.push(`light: '${minimized.background.light}'`), entries.push(` background: {${parts.join(", ")}},`);
1992
+ let lines = [];
1993
+ for (let scheme of SCHEMES) {
1994
+ let values = minimized[scheme];
1995
+ values && lines.push(serializeScheme(scheme, values));
69
1996
  }
70
- return minimized.contrast !== void 0 && entries.push(` contrast: ${minimized.contrast},`), `import {buildTheme} from '@sanity/themer'\n\nexport const theme = buildTheme({\n${entries.join("\n")}\n})\n`;
1997
+ return `import {buildTheme} from '@sanity/themer'\n\nexport const theme = buildTheme({\n${lines.join("\n")}\n})\n`;
1998
+ }
1999
+ /** A scheme with up to two options fits on one line, more spread out */
2000
+ function serializeScheme(scheme, values) {
2001
+ let entries = [];
2002
+ return values.accent !== void 0 && entries.push(`accent: '${values.accent}'`), values.text !== void 0 && entries.push(`text: '${values.text}'`), values.background !== void 0 && entries.push(`background: '${values.background}'`), values.contrast !== void 0 && entries.push(`contrast: ${values.contrast}`), entries.length <= 2 ? ` ${scheme}: {${entries.join(", ")}},` : ` ${scheme}: {\n${entries.map((entry) => ` ${entry},`).join("\n")}\n },`;
71
2003
  }
72
2004
  registerLanguage(typescript);
73
2005
  /**
74
- * `<input type="color">` paints the color into a shadow-DOM swatch that brings
75
- * its own border and padding, which then sits inside ours as a second border.
76
- * Stripping that chrome leaves the themed border as the only one.
77
- */
78
- const Swatch = styled.input.withConfig({
79
- displayName: "Swatch",
80
- componentId: "sc-7kf8xr-0"
81
- })`box-sizing:border-box;flex:none;width:33px;height:33px;padding:0;border:1px solid var(--card-border-color);border-radius:4px;background:none;cursor:pointer;&::-webkit-color-swatch-wrapper{padding:0;}&::-webkit-color-swatch{border:none;border-radius:3px;}&::-moz-color-swatch{border:none;border-radius:3px;}`, Range = styled.input.withConfig({
82
- displayName: "Range",
83
- componentId: "sc-7kf8xr-1"
84
- })`display:block;width:100%;margin:0;accent-color:var(--card-focus-ring-color);`;
85
- /**
86
- * The themer sidebar: presets, the accent/text/background pickers and the
87
- * contrast slider that generate the previewed `buildTheme` theme, plus the
88
- * `buildTheme` snippet to make it permanent.
2006
+ * Shows the `buildTheme` snippet that makes a theme permanent, in a dialog so
2007
+ * that it does not take up sidebar space or make the sidebar scroll as the
2008
+ * snippet changes shape with the theme.
89
2009
  *
90
2010
  * @internal
91
2011
  */
92
- function ThemerSidebar() {
93
- let $ = c(117), { baseOptions, options, setOptions, setOpen } = useThemer(), toast = useToast(), active = options ?? baseOptions, t0;
94
- $[0] === active ? t0 = $[1] : (t0 = resolveThemeOptions(active), $[0] = active, $[1] = t0);
95
- let resolved = t0, t1;
96
- $[2] === active ? t1 = $[3] : (t1 = buildPalette(active), $[2] = active, $[3] = t1);
97
- let palette = t1, t2;
98
- $[4] === active ? t2 = $[5] : (t2 = presets.find((preset) => sameOptions(preset.options, active))?.slug ?? "", $[4] = active, $[5] = t2);
99
- let activePresetSlug = t2, t3;
100
- $[6] === active ? t3 = $[7] : (t3 = createThemeSnippet(active), $[6] = active, $[7] = t3);
101
- let snippet = t3, t4;
102
- $[8] !== active || $[9] !== setOptions ? (t4 = (changes) => {
103
- setOptions({
104
- ...active,
105
- ...changes
106
- });
107
- }, $[8] = active, $[9] = setOptions, $[10] = t4) : t4 = $[10];
108
- let patch = t4, t5;
109
- $[11] !== active || $[12] !== setOptions ? (t5 = (changes_0) => {
110
- setOptions({
111
- ...active,
112
- background: {
113
- ...active.background,
114
- ...changes_0
115
- }
116
- });
117
- }, $[11] = active, $[12] = setOptions, $[13] = t5) : t5 = $[13];
118
- let patchBackground = t5, t6;
119
- $[14] !== active || $[15] !== setOptions ? (t6 = (field) => {
120
- let next = { ...active };
121
- delete next[field], setOptions(next);
122
- }, $[14] = active, $[15] = setOptions, $[16] = t6) : t6 = $[16];
123
- let clearField = t6, t7;
124
- $[17] !== active || $[18] !== setOptions ? (t7 = (key) => {
125
- let background = { ...active.background };
126
- delete background[key];
127
- let next_0 = { ...active };
128
- background.dark === void 0 && background.light === void 0 ? delete next_0.background : next_0.background = background, setOptions(next_0);
129
- }, $[17] = active, $[18] = setOptions, $[19] = t7) : t7 = $[19];
130
- let clearBackground = t7, t8;
131
- $[20] !== snippet || $[21] !== toast ? (t8 = async () => {
2012
+ function ThemeSnippetDialog(props) {
2013
+ let $ = c(23), { onClose, theme } = props, toast = useToast(), t0;
2014
+ $[0] === theme.options ? t0 = $[1] : (t0 = createThemeSnippet(theme.options), $[0] = theme.options, $[1] = t0);
2015
+ let snippet = t0, t1;
2016
+ $[2] !== snippet || $[3] !== toast ? (t1 = async () => {
132
2017
  try {
133
2018
  await navigator.clipboard.writeText(snippet), toast.push({
134
2019
  status: "success",
135
- title: "Copied theme to the clipboard"
2020
+ title: "Copied the theme to the clipboard"
136
2021
  });
137
2022
  } catch {
138
2023
  toast.push({
@@ -140,515 +2025,567 @@ function ThemerSidebar() {
140
2025
  title: "Could not copy the theme"
141
2026
  });
142
2027
  }
143
- }, $[20] = snippet, $[21] = toast, $[22] = t8) : t8 = $[22];
144
- let handleCopy = t8, t9;
145
- $[23] === Symbol.for("react.memo_cache_sentinel") ? (t9 = /* @__PURE__ */ jsx(Box, {
146
- flex: 1,
147
- paddingLeft: 1,
148
- children: /* @__PURE__ */ jsx(Text, {
149
- size: 1,
150
- weight: "semibold",
151
- children: "Themer"
152
- })
153
- }), $[23] = t9) : t9 = $[23];
154
- let t10;
155
- $[24] === setOpen ? t10 = $[25] : (t10 = /* @__PURE__ */ jsx(Card, {
156
- borderBottom: !0,
2028
+ }, $[2] = snippet, $[3] = toast, $[4] = t1) : t1 = $[4];
2029
+ let handleCopy = t1, t2;
2030
+ $[5] === handleCopy ? t2 = $[6] : (t2 = /* @__PURE__ */ jsx(Flex, {
2031
+ gap: 2,
2032
+ justify: "flex-end",
157
2033
  padding: 3,
158
- children: /* @__PURE__ */ jsxs(Flex, {
159
- align: "center",
160
- gap: 2,
161
- children: [t9, /* @__PURE__ */ jsx(Button, {
162
- icon: CloseIcon,
163
- mode: "bleed",
164
- onClick: () => setOpen(!1),
165
- padding: 2,
166
- title: "Close themer"
167
- })]
2034
+ children: /* @__PURE__ */ jsx(Button, {
2035
+ icon: ClipboardIcon,
2036
+ mode: "ghost",
2037
+ onClick: () => void handleCopy(),
2038
+ text: "Copy"
168
2039
  })
169
- }), $[24] = setOpen, $[25] = t10);
170
- let t11;
171
- $[26] === Symbol.for("react.memo_cache_sentinel") ? (t11 = /* @__PURE__ */ jsx(Text, {
172
- size: 1,
173
- weight: "medium",
174
- children: "Presets"
175
- }), $[26] = t11) : t11 = $[26];
176
- let t12;
177
- $[27] !== activePresetSlug || $[28] !== setOptions ? (t12 = presets.map((preset_0) => /* @__PURE__ */ jsx(PresetButton, {
178
- active: preset_0.slug === activePresetSlug,
179
- onClick: () => setOptions(preset_0.options),
180
- preset: preset_0
181
- }, preset_0.slug)), $[27] = activePresetSlug, $[28] = setOptions, $[29] = t12) : t12 = $[29];
182
- let t13;
183
- $[30] === t12 ? t13 = $[31] : (t13 = /* @__PURE__ */ jsxs(Stack, {
184
- gap: 3,
185
- children: [t11, /* @__PURE__ */ jsx(Grid, {
186
- gap: 2,
187
- gridTemplateColumns: 2,
188
- children: t12
189
- })]
190
- }), $[30] = t12, $[31] = t13);
191
- let t14;
192
- $[32] === Symbol.for("react.memo_cache_sentinel") ? (t14 = /* @__PURE__ */ jsx(Text, {
193
- size: 1,
194
- weight: "medium",
195
- children: "Colors"
196
- }), $[32] = t14) : t14 = $[32];
197
- let t15;
198
- $[33] === patch ? t15 = $[34] : (t15 = (accent) => patch({ accent }), $[33] = patch, $[34] = t15);
199
- let t16;
200
- $[35] !== palette.blue || $[36] !== resolved.accent || $[37] !== t15 ? (t16 = /* @__PURE__ */ jsx(ColorRow, {
201
- onChange: t15,
202
- tints: palette.blue,
203
- title: "Accent",
204
- value: resolved.accent
205
- }), $[35] = palette.blue, $[36] = resolved.accent, $[37] = t15, $[38] = t16) : t16 = $[38];
206
- let t17 = active.text === void 0, t18;
207
- $[39] === patch ? t18 = $[40] : (t18 = (text) => patch({ text }), $[39] = patch, $[40] = t18);
208
- let t19;
209
- $[41] === clearField ? t19 = $[42] : (t19 = () => clearField("text"), $[41] = clearField, $[42] = t19);
210
- let t20;
211
- $[43] !== palette.gray || $[44] !== resolved.text || $[45] !== t17 || $[46] !== t18 || $[47] !== t19 ? (t20 = /* @__PURE__ */ jsx(ColorRow, {
212
- auto: t17,
213
- onChange: t18,
214
- onClear: t19,
215
- tints: palette.gray,
216
- title: "Text",
217
- value: resolved.text
218
- }), $[43] = palette.gray, $[44] = resolved.text, $[45] = t17, $[46] = t18, $[47] = t19, $[48] = t20) : t20 = $[48];
219
- let t21 = active.background?.dark === void 0, t22;
220
- $[49] === patchBackground ? t22 = $[50] : (t22 = (dark) => patchBackground({ dark }), $[49] = patchBackground, $[50] = t22);
221
- let t23;
222
- $[51] === clearBackground ? t23 = $[52] : (t23 = () => clearBackground("dark"), $[51] = clearBackground, $[52] = t23);
223
- let t24;
224
- $[53] !== palette.black || $[54] !== resolved.background.dark || $[55] !== t21 || $[56] !== t22 || $[57] !== t23 ? (t24 = /* @__PURE__ */ jsx(ColorRow, {
225
- adjusted: palette.black,
226
- auto: t21,
227
- onChange: t22,
228
- onClear: t23,
229
- title: "Background · dark",
230
- value: resolved.background.dark
231
- }), $[53] = palette.black, $[54] = resolved.background.dark, $[55] = t21, $[56] = t22, $[57] = t23, $[58] = t24) : t24 = $[58];
232
- let t25 = active.background?.light === void 0, t26;
233
- $[59] === patchBackground ? t26 = $[60] : (t26 = (light) => patchBackground({ light }), $[59] = patchBackground, $[60] = t26);
234
- let t27;
235
- $[61] === clearBackground ? t27 = $[62] : (t27 = () => clearBackground("light"), $[61] = clearBackground, $[62] = t27);
236
- let t28;
237
- $[63] !== palette.white || $[64] !== resolved.background.light || $[65] !== t25 || $[66] !== t26 || $[67] !== t27 ? (t28 = /* @__PURE__ */ jsx(ColorRow, {
238
- adjusted: palette.white,
239
- auto: t25,
240
- onChange: t26,
241
- onClear: t27,
242
- title: "Background · light",
243
- value: resolved.background.light
244
- }), $[63] = palette.white, $[64] = resolved.background.light, $[65] = t25, $[66] = t26, $[67] = t27, $[68] = t28) : t28 = $[68];
245
- let t29;
246
- $[69] !== t16 || $[70] !== t20 || $[71] !== t24 || $[72] !== t28 ? (t29 = /* @__PURE__ */ jsxs(Stack, {
247
- gap: 3,
248
- children: [t14, /* @__PURE__ */ jsxs(Stack, {
249
- gap: 4,
250
- children: [
251
- t16,
252
- t20,
253
- t24,
254
- t28
255
- ]
256
- })]
257
- }), $[69] = t16, $[70] = t20, $[71] = t24, $[72] = t28, $[73] = t29) : t29 = $[73];
258
- let t30;
259
- $[74] === Symbol.for("react.memo_cache_sentinel") ? (t30 = /* @__PURE__ */ jsx(Text, {
260
- size: 1,
261
- weight: "medium",
262
- children: "Contrast"
263
- }), $[74] = t30) : t30 = $[74];
264
- let t31 = active.contrast === void 0 ? " · auto" : "", t32;
265
- $[75] !== resolved.contrast || $[76] !== t31 ? (t32 = /* @__PURE__ */ jsxs(Stack, {
266
- flex: 1,
267
- gap: 2,
268
- children: [t30, /* @__PURE__ */ jsxs(Text, {
269
- muted: !0,
270
- size: 0,
271
- children: [resolved.contrast, t31]
272
- })]
273
- }), $[75] = resolved.contrast, $[76] = t31, $[77] = t32) : t32 = $[77];
274
- let t33;
275
- $[78] !== active.contrast || $[79] !== clearField ? (t33 = active.contrast !== void 0 && /* @__PURE__ */ jsx(Button, {
276
- icon: ResetIcon,
277
- mode: "bleed",
278
- onClick: () => clearField("contrast"),
279
- padding: 2,
280
- title: "Reset to auto"
281
- }), $[78] = active.contrast, $[79] = clearField, $[80] = t33) : t33 = $[80];
282
- let t34;
283
- $[81] !== t32 || $[82] !== t33 ? (t34 = /* @__PURE__ */ jsxs(Flex, {
284
- align: "center",
285
- gap: 2,
286
- children: [t32, t33]
287
- }), $[81] = t32, $[82] = t33, $[83] = t34) : t34 = $[83];
288
- let t35;
289
- $[84] === patch ? t35 = $[85] : (t35 = (event) => patch({ contrast: Number(event.currentTarget.value) }), $[84] = patch, $[85] = t35);
290
- let t36;
291
- $[86] !== resolved.contrast || $[87] !== t35 ? (t36 = /* @__PURE__ */ jsx(Range, {
292
- "aria-label": "Contrast",
293
- max: 100,
294
- min: 15,
295
- onChange: t35,
296
- step: 1,
297
- type: "range",
298
- value: resolved.contrast
299
- }), $[86] = resolved.contrast, $[87] = t35, $[88] = t36) : t36 = $[88];
300
- let t37;
301
- $[89] === Symbol.for("react.memo_cache_sentinel") ? (t37 = /* @__PURE__ */ jsx(Text, {
2040
+ }), $[5] = handleCopy, $[6] = t2);
2041
+ let t3;
2042
+ $[7] === Symbol.for("react.memo_cache_sentinel") ? (t3 = /* @__PURE__ */ jsx("code", { children: "sanity.config.ts" }), $[7] = t3) : t3 = $[7];
2043
+ let t4;
2044
+ $[8] === Symbol.for("react.memo_cache_sentinel") ? (t4 = /* @__PURE__ */ jsx("code", { children: "theme" }), $[8] = t4) : t4 = $[8];
2045
+ let t5;
2046
+ $[9] === Symbol.for("react.memo_cache_sentinel") ? (t5 = /* @__PURE__ */ jsx("code", { children: "defineConfig" }), $[9] = t5) : t5 = $[9];
2047
+ let t6;
2048
+ $[10] === theme.title ? t6 = $[11] : (t6 = displayTitle(theme.title), $[10] = theme.title, $[11] = t6);
2049
+ let t7;
2050
+ $[12] === t6 ? t7 = $[13] : (t7 = /* @__PURE__ */ jsxs(Text, {
302
2051
  muted: !0,
303
- size: 0,
304
- children: "100 keeps text and borders neutral — lower values blend in the accent"
305
- }), $[89] = t37) : t37 = $[89];
306
- let t38;
307
- $[90] !== t34 || $[91] !== t36 ? (t38 = /* @__PURE__ */ jsxs(Stack, {
308
- gap: 3,
2052
+ size: 1,
309
2053
  children: [
310
- t34,
311
- t36,
312
- t37
2054
+ "Export this from ",
2055
+ t3,
2056
+ " and pass it as the ",
2057
+ t4,
2058
+ " of",
2059
+ " ",
2060
+ t5,
2061
+ " to make “",
2062
+ t6,
2063
+ "” the Studio theme without this tool."
313
2064
  ]
314
- }), $[90] = t34, $[91] = t36, $[92] = t38) : t38 = $[92];
315
- let t39;
316
- $[93] === Symbol.for("react.memo_cache_sentinel") ? (t39 = /* @__PURE__ */ jsx(Text, {
317
- size: 1,
318
- weight: "medium",
319
- children: "Add to your config"
320
- }), $[93] = t39) : t39 = $[93];
321
- let t40;
322
- $[94] === snippet ? t40 = $[95] : (t40 = /* @__PURE__ */ jsx(Card, {
2065
+ }), $[12] = t6, $[13] = t7);
2066
+ let t8;
2067
+ $[14] === snippet ? t8 = $[15] : (t8 = /* @__PURE__ */ jsx(Card, {
323
2068
  border: !0,
324
2069
  overflow: "auto",
325
- padding: 2,
2070
+ padding: 3,
326
2071
  radius: 2,
327
2072
  tone: "transparent",
328
2073
  children: /* @__PURE__ */ jsx(Code, {
329
2074
  language: "ts",
330
- size: 0,
2075
+ size: 1,
331
2076
  children: snippet
332
2077
  })
333
- }), $[94] = snippet, $[95] = t40);
334
- let t41;
335
- $[96] === handleCopy ? t41 = $[97] : (t41 = /* @__PURE__ */ jsx(Button, {
336
- icon: ClipboardIcon,
337
- mode: "ghost",
338
- onClick: () => void handleCopy(),
339
- text: "Copy"
340
- }), $[96] = handleCopy, $[97] = t41);
341
- let t42 = options === null, t43;
342
- $[98] === setOptions ? t43 = $[99] : (t43 = () => setOptions(null), $[98] = setOptions, $[99] = t43);
343
- let t44;
344
- $[100] !== t42 || $[101] !== t43 ? (t44 = /* @__PURE__ */ jsx(Button, {
345
- disabled: t42,
346
- icon: ResetIcon,
347
- mode: "ghost",
348
- onClick: t43,
349
- text: "Reset",
350
- tone: "critical"
351
- }), $[100] = t42, $[101] = t43, $[102] = t44) : t44 = $[102];
352
- let t45;
353
- $[103] !== t41 || $[104] !== t44 ? (t45 = /* @__PURE__ */ jsxs(Flex, {
354
- gap: 2,
355
- children: [t41, t44]
356
- }), $[103] = t41, $[104] = t44, $[105] = t45) : t45 = $[105];
357
- let t46;
358
- $[106] !== t40 || $[107] !== t45 ? (t46 = /* @__PURE__ */ jsxs(Stack, {
359
- gap: 3,
360
- children: [
361
- t39,
362
- t40,
363
- t45
364
- ]
365
- }), $[106] = t40, $[107] = t45, $[108] = t46) : t46 = $[108];
366
- let t47;
367
- $[109] !== t13 || $[110] !== t29 || $[111] !== t38 || $[112] !== t46 ? (t47 = /* @__PURE__ */ jsx(Box, {
368
- flex: 1,
369
- overflow: "auto",
370
- padding: 3,
2078
+ }), $[14] = snippet, $[15] = t8);
2079
+ let t9;
2080
+ $[16] !== t7 || $[17] !== t8 ? (t9 = /* @__PURE__ */ jsx(Box, {
2081
+ padding: 4,
371
2082
  children: /* @__PURE__ */ jsxs(Stack, {
372
2083
  gap: 4,
373
- children: [
374
- t13,
375
- t29,
376
- t38,
377
- t46
378
- ]
379
- })
380
- }), $[109] = t13, $[110] = t29, $[111] = t38, $[112] = t46, $[113] = t47) : t47 = $[113];
381
- let t48;
382
- return $[114] !== t10 || $[115] !== t47 ? (t48 = /* @__PURE__ */ jsx(Card, {
383
- height: "fill",
384
- children: /* @__PURE__ */ jsxs(Flex, {
385
- direction: "column",
386
- height: "fill",
387
- children: [t10, t47]
2084
+ children: [t7, t8]
388
2085
  })
389
- }), $[114] = t10, $[115] = t47, $[116] = t48) : t48 = $[116], t48;
390
- }
391
- const paletteStyle = {
392
- display: "flex",
393
- gap: 1,
394
- background: "var(--card-border-color)",
395
- height: 21,
396
- borderRadius: 3,
397
- overflow: "hidden",
398
- boxShadow: "inset 0 0 0 1px var(--card-border-color)"
399
- };
400
- /** A little palette of the colors a preset would apply */
401
- function PresetButton(props) {
402
- let $ = c(27), { active, onClick, preset } = props, t0;
403
- $[0] === preset.options ? t0 = $[1] : (t0 = resolveThemeOptions(preset.options), $[0] = preset.options, $[1] = t0);
404
- let resolved = t0, t1;
405
- $[2] === resolved.accent ? t1 = $[3] : (t1 = ["accent", resolved.accent], $[2] = resolved.accent, $[3] = t1);
406
- let t2;
407
- $[4] === resolved.text ? t2 = $[5] : (t2 = ["text", resolved.text], $[4] = resolved.text, $[5] = t2);
408
- let t3;
409
- $[6] === resolved.background.dark ? t3 = $[7] : (t3 = ["dark", resolved.background.dark], $[6] = resolved.background.dark, $[7] = t3);
410
- let t4;
411
- $[8] === resolved.background.light ? t4 = $[9] : (t4 = ["light", resolved.background.light], $[8] = resolved.background.light, $[9] = t4);
412
- let t5;
413
- $[10] !== t1 || $[11] !== t2 || $[12] !== t3 || $[13] !== t4 ? (t5 = [
414
- t1,
415
- t2,
416
- t3,
417
- t4
418
- ], $[10] = t1, $[11] = t2, $[12] = t3, $[13] = t4, $[14] = t5) : t5 = $[14];
419
- let swatches = t5, t6;
420
- $[15] === swatches ? t6 = $[16] : (t6 = /* @__PURE__ */ jsx("span", {
421
- style: paletteStyle,
422
- children: swatches.map(_temp$1)
423
- }), $[15] = swatches, $[16] = t6);
424
- let t7;
425
- $[17] === preset.title ? t7 = $[18] : (t7 = /* @__PURE__ */ jsx(Text, {
426
- align: "left",
427
- size: 1,
428
- textOverflow: "ellipsis",
429
- children: preset.title
430
- }), $[17] = preset.title, $[18] = t7);
431
- let t8;
432
- $[19] !== t6 || $[20] !== t7 ? (t8 = /* @__PURE__ */ jsxs(Stack, {
433
- as: "span",
434
- gap: 2,
435
- children: [t6, t7]
436
- }), $[19] = t6, $[20] = t7, $[21] = t8) : t8 = $[21];
437
- let t9;
438
- return $[22] !== active || $[23] !== onClick || $[24] !== preset.title || $[25] !== t8 ? (t9 = /* @__PURE__ */ jsx(Button, {
439
- mode: "ghost",
440
- onClick,
441
- padding: 2,
442
- selected: active,
443
- title: preset.title,
444
- children: t8
445
- }), $[22] = active, $[23] = onClick, $[24] = preset.title, $[25] = t8, $[26] = t9) : t9 = $[26], t9;
446
- }
447
- function _temp$1(t0) {
448
- let [key, background] = t0;
449
- return /* @__PURE__ */ jsx("span", { style: {
450
- flex: 1,
451
- background
452
- } }, key);
2086
+ }), $[16] = t7, $[17] = t8, $[18] = t9) : t9 = $[18];
2087
+ let t10;
2088
+ return $[19] !== onClose || $[20] !== t2 || $[21] !== t9 ? (t10 = /* @__PURE__ */ jsx(Dialog, {
2089
+ animate: !0,
2090
+ footer: t2,
2091
+ header: "Add to your config",
2092
+ id: "themer-snippet",
2093
+ onClickOutside: onClose,
2094
+ onClose,
2095
+ width: 1,
2096
+ children: t9
2097
+ }), $[19] = onClose, $[20] = t2, $[21] = t9, $[22] = t10) : t10 = $[22], t10;
453
2098
  }
454
- const rampStyle = {
455
- ...paletteStyle,
456
- height: 13,
457
- borderRadius: 2
2099
+ const VIEW_TITLES = {
2100
+ list: "Themer",
2101
+ edit: "Edit theme",
2102
+ removed: "Removed themes"
458
2103
  };
459
2104
  /**
460
- * One color of the theme: a swatch with its value, an optional reset-to-auto
461
- * button, and the generated 50–950 tint ramp for the scales it anchors.
2105
+ * The themer sidebar: a header that navigates between the flows and toggles
2106
+ * the split preview, the flow itself — picking a theme, editing one, or
2107
+ * restoring removed ones — and the dialog with the `buildTheme` snippet of
2108
+ * the applied theme.
2109
+ *
2110
+ * @internal
462
2111
  */
463
- function ColorRow(props) {
464
- let $ = c(27), { adjusted, auto, onChange, onClear, tints, title, value } = props, t0;
465
- $[0] === title ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(Text, {
2112
+ function ThemerSidebar() {
2113
+ let $ = c(41), { active, split, view, send } = useThemer(), [snippetOpen, setSnippetOpen] = useState(!1), inList = view.name === "list", t0;
2114
+ $[0] !== inList || $[1] !== send ? (t0 = !inList && /* @__PURE__ */ jsx(TooltipButton, {
2115
+ icon: ArrowLeftIcon,
2116
+ mode: "bleed",
2117
+ onClick: () => send({ type: "flow.list" }),
2118
+ padding: 2,
2119
+ tooltip: "Back to the themes"
2120
+ }), $[0] = inList, $[1] = send, $[2] = t0) : t0 = $[2];
2121
+ let t1 = +!!inList, t2;
2122
+ $[3] === Symbol.for("react.memo_cache_sentinel") ? (t2 = { minWidth: 0 }, $[3] = t2) : t2 = $[3];
2123
+ let t3 = VIEW_TITLES[view.name], t4;
2124
+ $[4] === t3 ? t4 = $[5] : (t4 = /* @__PURE__ */ jsx(Text, {
466
2125
  size: 1,
467
- children: title
468
- }), $[0] = title, $[1] = t0);
469
- let t1 = adjusted !== void 0 && adjusted !== value ? ` → ${adjusted}` : "", t2 = auto ? " · auto" : "", t3;
470
- $[2] !== t1 || $[3] !== t2 || $[4] !== value ? (t3 = /* @__PURE__ */ jsxs(Text, {
471
- muted: !0,
472
- size: 0,
473
2126
  textOverflow: "ellipsis",
474
- children: [
475
- value,
476
- t1,
477
- t2
478
- ]
479
- }), $[2] = t1, $[3] = t2, $[4] = value, $[5] = t3) : t3 = $[5];
480
- let t4;
481
- $[6] !== t0 || $[7] !== t3 ? (t4 = /* @__PURE__ */ jsxs(Stack, {
482
- flex: 1,
483
- gap: 2,
484
- children: [t0, t3]
485
- }), $[6] = t0, $[7] = t3, $[8] = t4) : t4 = $[8];
2127
+ weight: "semibold",
2128
+ children: t3
2129
+ }), $[4] = t3, $[5] = t4);
486
2130
  let t5;
487
- $[9] !== auto || $[10] !== onClear ? (t5 = onClear && !auto && /* @__PURE__ */ jsx(Button, {
488
- icon: ResetIcon,
2131
+ $[6] !== t1 || $[7] !== t4 ? (t5 = /* @__PURE__ */ jsx(Box, {
2132
+ flex: 1,
2133
+ paddingLeft: t1,
2134
+ style: t2,
2135
+ children: t4
2136
+ }), $[6] = t1, $[7] = t4, $[8] = t5) : t5 = $[8];
2137
+ let t6;
2138
+ $[9] !== send || $[10] !== split || $[11] !== view.name ? (t6 = view.name !== "removed" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TooltipButton, {
2139
+ "aria-pressed": split,
2140
+ icon: SplitVerticalIcon,
489
2141
  mode: "bleed",
490
- onClick: onClear,
2142
+ onClick: () => send({ type: "preview.toggle" }),
2143
+ padding: 2,
2144
+ selected: split,
2145
+ tooltip: "Show light and dark side by side"
2146
+ }), /* @__PURE__ */ jsx(TooltipButton, {
2147
+ icon: CodeBlockIcon,
2148
+ mode: "bleed",
2149
+ onClick: () => setSnippetOpen(!0),
2150
+ padding: 2,
2151
+ tooltip: "Show the code for the applied theme"
2152
+ })] }), $[9] = send, $[10] = split, $[11] = view.name, $[12] = t6) : t6 = $[12];
2153
+ let t7;
2154
+ $[13] === send ? t7 = $[14] : (t7 = /* @__PURE__ */ jsx(TooltipButton, {
2155
+ icon: CloseIcon,
2156
+ mode: "bleed",
2157
+ onClick: () => send({ type: "sidebar.close" }),
491
2158
  padding: 2,
492
- title: "Reset to auto"
493
- }), $[9] = auto, $[10] = onClear, $[11] = t5) : t5 = $[11];
494
- let t6 = `${title} color`, t7;
495
- $[12] === onChange ? t7 = $[13] : (t7 = (event) => onChange(event.currentTarget.value), $[12] = onChange, $[13] = t7);
2159
+ tooltip: "Close themer"
2160
+ }), $[13] = send, $[14] = t7);
496
2161
  let t8;
497
- $[14] !== t6 || $[15] !== t7 || $[16] !== value ? (t8 = /* @__PURE__ */ jsx(Swatch, {
498
- "aria-label": t6,
499
- onChange: t7,
500
- type: "color",
501
- value
502
- }), $[14] = t6, $[15] = t7, $[16] = value, $[17] = t8) : t8 = $[17];
2162
+ $[15] !== t6 || $[16] !== t7 ? (t8 = /* @__PURE__ */ jsxs(Flex, { children: [t6, t7] }), $[15] = t6, $[16] = t7, $[17] = t8) : t8 = $[17];
503
2163
  let t9;
504
- $[18] !== t4 || $[19] !== t5 || $[20] !== t8 ? (t9 = /* @__PURE__ */ jsxs(Flex, {
505
- align: "center",
506
- gap: 2,
507
- children: [
508
- t4,
509
- t5,
510
- t8
511
- ]
512
- }), $[18] = t4, $[19] = t5, $[20] = t8, $[21] = t9) : t9 = $[21];
2164
+ $[18] !== t0 || $[19] !== t5 || $[20] !== t8 ? (t9 = /* @__PURE__ */ jsx(Card, {
2165
+ padding: 2,
2166
+ children: /* @__PURE__ */ jsxs(Flex, {
2167
+ align: "center",
2168
+ gap: 1,
2169
+ children: [
2170
+ t0,
2171
+ t5,
2172
+ t8
2173
+ ]
2174
+ })
2175
+ }), $[18] = t0, $[19] = t5, $[20] = t8, $[21] = t9) : t9 = $[21];
513
2176
  let t10;
514
- $[22] === tints ? t10 = $[23] : (t10 = tints && /* @__PURE__ */ jsx("span", {
515
- style: rampStyle,
516
- children: COLOR_TINTS.map((tint) => /* @__PURE__ */ jsx("span", { style: {
517
- flex: 1,
518
- background: tints[tint]
519
- } }, tint))
520
- }), $[22] = tints, $[23] = t10);
2177
+ $[22] === view.name ? t10 = $[23] : (t10 = view.name === "list" && /* @__PURE__ */ jsx(ThemeList, {}), $[22] = view.name, $[23] = t10);
521
2178
  let t11;
522
- return $[24] !== t10 || $[25] !== t9 ? (t11 = /* @__PURE__ */ jsxs(Stack, {
523
- gap: 2,
524
- children: [t9, t10]
525
- }), $[24] = t10, $[25] = t9, $[26] = t11) : t11 = $[26], t11;
2179
+ $[24] !== view.focusTitle || $[25] !== view.name || $[26] !== view.slug ? (t11 = view.name === "edit" && /* @__PURE__ */ jsx(ThemeEditor, {
2180
+ focusTitle: view.focusTitle,
2181
+ slug: view.slug
2182
+ }), $[24] = view.focusTitle, $[25] = view.name, $[26] = view.slug, $[27] = t11) : t11 = $[27];
2183
+ let t12;
2184
+ $[28] === view.name ? t12 = $[29] : (t12 = view.name === "removed" && /* @__PURE__ */ jsx(RemovedThemes, {}), $[28] = view.name, $[29] = t12);
2185
+ let t13;
2186
+ $[30] !== t10 || $[31] !== t11 || $[32] !== t12 || $[33] !== t9 ? (t13 = /* @__PURE__ */ jsxs(Flex, {
2187
+ direction: "column",
2188
+ height: "fill",
2189
+ children: [
2190
+ t9,
2191
+ t10,
2192
+ t11,
2193
+ t12
2194
+ ]
2195
+ }), $[30] = t10, $[31] = t11, $[32] = t12, $[33] = t9, $[34] = t13) : t13 = $[34];
2196
+ let t14;
2197
+ $[35] !== active || $[36] !== snippetOpen ? (t14 = snippetOpen && /* @__PURE__ */ jsx(ThemeSnippetDialog, {
2198
+ onClose: () => setSnippetOpen(!1),
2199
+ theme: active
2200
+ }), $[35] = active, $[36] = snippetOpen, $[37] = t14) : t14 = $[37];
2201
+ let t15;
2202
+ return $[38] !== t13 || $[39] !== t14 ? (t15 = /* @__PURE__ */ jsxs(Card, {
2203
+ height: "fill",
2204
+ children: [t13, t14]
2205
+ }), $[38] = t13, $[39] = t14, $[40] = t15) : t15 = $[40], t15;
526
2206
  }
527
2207
  /**
528
- * Narrow enough to leave the studio preview as much room as possible: it fits
529
- * the widest picker label next to its swatch, and the code snippet scrolls
530
- * horizontally rather than widening the sidebar.
2208
+ * The narrowest the sidebar goes — and its default: narrow enough to leave the
2209
+ * studio preview as much room as possible, while the themes stack in a single
2210
+ * column of preview cards and the editor fits a swatch next to its label.
531
2211
  */
532
- const sidebarStyle = {
533
- width: 240,
534
- flex: "none",
535
- borderLeft: "1px solid var(--card-border-color)",
536
- boxSizing: "border-box",
537
- overflow: "hidden"
538
- };
2212
+ const WIDTH_STORAGE_KEY = "sanityStudio:themer:width";
2213
+ function clampWidth(width) {
2214
+ return Math.min(400, Math.max(200, Math.round(width)));
2215
+ }
2216
+ function readStoredWidth() {
2217
+ try {
2218
+ let stored = Number(localStorage.getItem(WIDTH_STORAGE_KEY));
2219
+ return Number.isFinite(stored) && stored > 0 ? clampWidth(stored) : 200;
2220
+ } catch {
2221
+ return 200;
2222
+ }
2223
+ }
2224
+ function writeStoredWidth(width) {
2225
+ try {
2226
+ width === 200 ? localStorage.removeItem(WIDTH_STORAGE_KEY) : localStorage.setItem(WIDTH_STORAGE_KEY, String(width));
2227
+ } catch {}
2228
+ }
2229
+ const Sidebar = styled(Layer).withConfig({
2230
+ displayName: "Sidebar",
2231
+ componentId: "sc-ryrt6y-0"
2232
+ })`flex:none;`, Frame = styled(Card).withConfig({
2233
+ displayName: "Frame",
2234
+ componentId: "sc-ryrt6y-1"
2235
+ })`position:relative;`, ResizeHandle = styled.div.withConfig({
2236
+ displayName: "ResizeHandle",
2237
+ componentId: "sc-ryrt6y-2"
2238
+ })`position:absolute;top:0;bottom:0;left:-3px;z-index:1;width:7px;cursor:col-resize;touch-action:none;&::after{content:'';position:absolute;top:0;bottom:0;left:2px;width:3px;background:var(--card-focus-ring-color);opacity:0;transition:opacity 100ms;}&:hover::after,&:focus-visible::after,&[data-dragging='true']::after{opacity:1;}&:focus{outline:none;}`;
539
2239
  /**
540
- * Renders the themer sidebar next to the active tool, so the user can browse
541
- * around their own studio while tweaking the theme.
2240
+ * The themer sidebar, in a layer along the right edge of the Studio. Its left
2241
+ * edge drags (or arrow-keys) it wider, up to twice its default width — the
2242
+ * width sticks between sessions.
542
2243
  *
543
2244
  * @internal
544
2245
  */
545
- function ThemerActiveToolLayout(props) {
546
- let $ = c(9), { open } = useThemer(), t0;
547
- $[0] === props ? t0 = $[1] : (t0 = props.renderDefault(props), $[0] = props, $[1] = t0);
548
- let t1;
549
- $[2] === t0 ? t1 = $[3] : (t1 = /* @__PURE__ */ jsx(Box, {
550
- flex: 1,
551
- height: "fill",
552
- overflow: "auto",
553
- children: t0
554
- }), $[2] = t0, $[3] = t1);
2246
+ function ResizableSidebar() {
2247
+ let $ = c(21), [width, setWidth] = useState(readStoredWidth), [dragging, setDragging] = useState(!1), drag = useRef(null), t0, t1;
2248
+ $[0] === width ? (t0 = $[1], t1 = $[2]) : (t0 = () => writeStoredWidth(width), t1 = [width], $[0] = width, $[1] = t0, $[2] = t1), useEffect(t0, t1);
555
2249
  let t2;
556
- $[4] === open ? t2 = $[5] : (t2 = open && /* @__PURE__ */ jsx(Layer, {
2250
+ $[3] === width ? t2 = $[4] : (t2 = (event) => {
2251
+ event.button === 0 && (event.preventDefault(), event.currentTarget.setPointerCapture(event.pointerId), drag.current = {
2252
+ pointerId: event.pointerId,
2253
+ startX: event.clientX,
2254
+ startWidth: width
2255
+ }, setDragging(!0));
2256
+ }, $[3] = width, $[4] = t2);
2257
+ let handlePointerDown = t2, t3;
2258
+ $[5] === Symbol.for("react.memo_cache_sentinel") ? (t3 = (event_0) => {
2259
+ drag.current && drag.current.pointerId === event_0.pointerId && setWidth(clampWidth(drag.current.startWidth - (event_0.clientX - drag.current.startX)));
2260
+ }, $[5] = t3) : t3 = $[5];
2261
+ let handlePointerMove = t3, t4;
2262
+ $[6] === Symbol.for("react.memo_cache_sentinel") ? (t4 = (event_1) => {
2263
+ drag.current && drag.current.pointerId === event_1.pointerId && (drag.current = null, setDragging(!1));
2264
+ }, $[6] = t4) : t4 = $[6];
2265
+ let handlePointerUp = t4, t5;
2266
+ $[7] === Symbol.for("react.memo_cache_sentinel") ? (t5 = (event_2) => {
2267
+ let step = {
2268
+ ArrowLeft: 16,
2269
+ ArrowRight: -16,
2270
+ Home: 400,
2271
+ End: -400
2272
+ }[event_2.key];
2273
+ step !== void 0 && (event_2.preventDefault(), setWidth((current) => clampWidth(current + step)));
2274
+ }, $[7] = t5) : t5 = $[7];
2275
+ let handleKeyDown = t5, t6;
2276
+ $[8] === width ? t6 = $[9] : (t6 = { width }, $[8] = width, $[9] = t6);
2277
+ let t7;
2278
+ $[10] === Symbol.for("react.memo_cache_sentinel") ? (t7 = () => setWidth(200), $[10] = t7) : t7 = $[10];
2279
+ let t8;
2280
+ $[11] !== dragging || $[12] !== handlePointerDown || $[13] !== width ? (t8 = /* @__PURE__ */ jsx(ResizeHandle, {
2281
+ "aria-label": "Resize the themer",
2282
+ "aria-orientation": "vertical",
2283
+ "aria-valuemax": 400,
2284
+ "aria-valuemin": 200,
2285
+ "aria-valuenow": width,
2286
+ "data-dragging": dragging,
2287
+ onDoubleClick: t7,
2288
+ onKeyDown: handleKeyDown,
2289
+ onPointerCancel: handlePointerUp,
2290
+ onPointerDown: handlePointerDown,
2291
+ onPointerMove: handlePointerMove,
2292
+ onPointerUp: handlePointerUp,
2293
+ role: "separator",
2294
+ tabIndex: 0
2295
+ }), $[11] = dragging, $[12] = handlePointerDown, $[13] = width, $[14] = t8) : t8 = $[14];
2296
+ let t9;
2297
+ $[15] === Symbol.for("react.memo_cache_sentinel") ? (t9 = /* @__PURE__ */ jsx(Box, {
557
2298
  height: "fill",
558
- style: sidebarStyle,
559
- zOffset: 100,
2299
+ overflow: "hidden",
560
2300
  children: /* @__PURE__ */ jsx(ThemerSidebar, {})
561
- }), $[4] = open, $[5] = t2);
562
- let t3;
563
- return $[6] !== t1 || $[7] !== t2 ? (t3 = /* @__PURE__ */ jsxs(Flex, {
2301
+ }), $[15] = t9) : t9 = $[15];
2302
+ let t10;
2303
+ $[16] === t8 ? t10 = $[17] : (t10 = /* @__PURE__ */ jsxs(Frame, {
2304
+ borderLeft: !0,
564
2305
  height: "fill",
565
- sizing: "border",
566
- children: [t1, t2]
567
- }), $[6] = t1, $[7] = t2, $[8] = t3) : t3 = $[8], t3;
2306
+ children: [t8, t9]
2307
+ }), $[16] = t8, $[17] = t10);
2308
+ let t11;
2309
+ return $[18] !== t10 || $[19] !== t6 ? (t11 = /* @__PURE__ */ jsx(Sidebar, {
2310
+ style: t6,
2311
+ zOffset: 100,
2312
+ children: t10
2313
+ }), $[18] = t10, $[19] = t6, $[20] = t11) : t11 = $[20], t11;
568
2314
  }
569
- const STORAGE_KEY = "sanityStudio:themer:options";
2315
+ const STORAGE_KEY = "sanityStudio:themer:state", LEGACY_STORAGE_KEY = "sanityStudio:themer:options";
570
2316
  function sanitizeColor(value) {
571
2317
  return typeof value == "string" && isColor(value) ? value.toLowerCase() : null;
572
2318
  }
573
- function sanitizeOptions(value) {
574
- if (!value || typeof value != "object") return null;
2319
+ function sanitizeContrast(value) {
2320
+ return typeof value == "number" && Number.isFinite(value) ? Math.min(100, Math.max(15, value)) : null;
2321
+ }
2322
+ function sanitizeSchemeOptions(value) {
2323
+ let options = {};
2324
+ if (!value || typeof value != "object") return options;
2325
+ let accent = sanitizeColor(Reflect.get(value, "accent")), text = sanitizeColor(Reflect.get(value, "text")), background = sanitizeColor(Reflect.get(value, "background")), contrast = sanitizeContrast(Reflect.get(value, "contrast"));
2326
+ return accent && (options.accent = accent), text && (options.text = text), background && (options.background = background), contrast !== null && (options.contrast = contrast), options;
2327
+ }
2328
+ /**
2329
+ * Options from before they were grouped by scheme: one accent, text color and
2330
+ * contrast for both schemes, and a background per scheme. Both schemes get
2331
+ * the shared colors, so the theme keeps looking the same.
2332
+ */
2333
+ function sanitizeFlatOptions(value) {
575
2334
  let accent = sanitizeColor(Reflect.get(value, "accent"));
576
2335
  if (!accent) return null;
577
- let options = { accent }, text = sanitizeColor(Reflect.get(value, "text"));
578
- text && (options.text = text);
579
- let background = Reflect.get(value, "background");
580
- if (background && typeof background == "object") {
581
- let dark = sanitizeColor(Reflect.get(background, "dark")), light = sanitizeColor(Reflect.get(background, "light"));
582
- (dark || light) && (options.background = {}, dark && (options.background.dark = dark), light && (options.background.light = light));
2336
+ let shared = { accent }, text = sanitizeColor(Reflect.get(value, "text")), contrast = sanitizeContrast(Reflect.get(value, "contrast"));
2337
+ text && (shared.text = text), contrast !== null && (shared.contrast = contrast);
2338
+ let backgrounds = Reflect.get(value, "background"), options = {};
2339
+ for (let scheme of SCHEMES) {
2340
+ let background = backgrounds && typeof backgrounds == "object" ? sanitizeColor(Reflect.get(backgrounds, scheme)) : null;
2341
+ options[scheme] = background ? {
2342
+ ...shared,
2343
+ background
2344
+ } : { ...shared };
2345
+ }
2346
+ return options;
2347
+ }
2348
+ function sanitizeOptions(value) {
2349
+ if (!value || typeof value != "object") return null;
2350
+ if ("accent" in value) return sanitizeFlatOptions(value);
2351
+ let options = {};
2352
+ for (let scheme of SCHEMES) {
2353
+ let schemeOptions = sanitizeSchemeOptions(Reflect.get(value, scheme));
2354
+ Object.keys(schemeOptions).length > 0 && (options[scheme] = schemeOptions);
2355
+ }
2356
+ return options;
2357
+ }
2358
+ function isReservedSlug(slug) {
2359
+ return slug === "config" || presets.some((preset) => preset.slug === slug);
2360
+ }
2361
+ function sanitizeCustomTheme(value) {
2362
+ if (!value || typeof value != "object") return null;
2363
+ let slug = Reflect.get(value, "slug");
2364
+ if (typeof slug != "string" || !slug || isReservedSlug(slug)) return null;
2365
+ let options = sanitizeOptions(Reflect.get(value, "options"));
2366
+ if (!options) return null;
2367
+ let title = Reflect.get(value, "title"), palette = sanitizePalette(Reflect.get(value, "palette"));
2368
+ return {
2369
+ slug,
2370
+ title: displayTitle(typeof title == "string" ? title : ""),
2371
+ options,
2372
+ ...palette ? { palette } : {}
2373
+ };
2374
+ }
2375
+ function sanitizePalette(value) {
2376
+ if (!value || typeof value != "object") return null;
2377
+ let palette = {}, swatches = 0;
2378
+ for (let key of IMAGE_PALETTE_KEYS) {
2379
+ let swatch = sanitizeColor(Reflect.get(value, key));
2380
+ palette[key] = swatch, swatch && swatches++;
583
2381
  }
584
- let contrast = Reflect.get(value, "contrast");
585
- return typeof contrast == "number" && Number.isFinite(contrast) && (options.contrast = Math.min(100, Math.max(15, contrast))), options;
2382
+ return swatches > 0 ? palette : null;
2383
+ }
2384
+ function sanitizeState(value) {
2385
+ if (!value || typeof value != "object") return initialThemerState;
2386
+ let custom = [], rawCustom = Reflect.get(value, "custom");
2387
+ if (Array.isArray(rawCustom)) for (let item of rawCustom) {
2388
+ let theme = sanitizeCustomTheme(item);
2389
+ theme && !custom.some((existing) => existing.slug === theme.slug) && custom.push(theme);
2390
+ }
2391
+ let removed = [], rawRemoved = Reflect.get(value, "removed");
2392
+ if (Array.isArray(rawRemoved)) for (let slug of rawRemoved) typeof slug == "string" && slug !== "config" && (presets.some((preset) => preset.slug === slug) || custom.some((theme) => theme.slug === slug)) && !removed.includes(slug) && removed.push(slug);
2393
+ let rawActive = Reflect.get(value, "active");
2394
+ return {
2395
+ active: typeof rawActive == "string" ? rawActive : null,
2396
+ custom,
2397
+ removed
2398
+ };
2399
+ }
2400
+ function readLegacyState() {
2401
+ let raw = localStorage.getItem(LEGACY_STORAGE_KEY);
2402
+ if (!raw) return null;
2403
+ let options = sanitizeOptions(JSON.parse(raw));
2404
+ if (!options) return null;
2405
+ let theme = createCustomTheme("Draft theme", options);
2406
+ return {
2407
+ active: theme.slug,
2408
+ custom: [theme],
2409
+ removed: []
2410
+ };
586
2411
  }
587
2412
  /**
588
- * Restores draft theme options from localStorage, so theme drafts survive
589
- * studio reloads.
2413
+ * Restores the themer state from localStorage, so the user's themes survive
2414
+ * studio reloads. Falls back to the initial state when nothing usable is
2415
+ * stored.
590
2416
  *
591
2417
  * @internal
592
2418
  */
593
- function readStoredOptions() {
2419
+ function readStoredState() {
594
2420
  try {
595
- if (typeof localStorage > "u") return null;
2421
+ if (typeof localStorage > "u") return initialThemerState;
596
2422
  let raw = localStorage.getItem(STORAGE_KEY);
597
- return raw ? sanitizeOptions(JSON.parse(raw)) : null;
2423
+ return raw ? sanitizeState(JSON.parse(raw)) : readLegacyState() ?? initialThemerState;
598
2424
  } catch {
599
- return null;
2425
+ return initialThemerState;
600
2426
  }
601
2427
  }
602
2428
  /** @internal */
603
- function writeStoredOptions(options) {
2429
+ function writeStoredState(state) {
604
2430
  try {
605
2431
  if (typeof localStorage > "u") return;
606
- options === null ? localStorage.removeItem(STORAGE_KEY) : localStorage.setItem(STORAGE_KEY, JSON.stringify(options));
2432
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state)), localStorage.removeItem(LEGACY_STORAGE_KEY);
607
2433
  } catch {}
608
2434
  }
2435
+ function sameStoredState(a, b) {
2436
+ return a.active === b.active && a.custom === b.custom && a.removed === b.removed;
2437
+ }
2438
+ function selectView(snapshot) {
2439
+ let { editing } = snapshot.context;
2440
+ return snapshot.matches({ flow: "edit" }) && editing ? {
2441
+ name: "edit",
2442
+ slug: editing.slug,
2443
+ focusTitle: editing.focusTitle
2444
+ } : snapshot.matches({ flow: "removed" }) ? { name: "removed" } : { name: "list" };
2445
+ }
2446
+ function sameView(a, b) {
2447
+ return a.name === b.name ? a.name !== "edit" || b.name !== "edit" || a.slug === b.slug && a.focusTitle === b.focusTitle : !1;
2448
+ }
609
2449
  /**
610
- * Wraps the whole Studio so that draft themes generated by the themer sidebar
611
- * apply everywhere while the user browses around, and hosts the state that the
612
- * navbar toggle and the sidebar share.
2450
+ * Wraps the whole Studio so that the theme picked in the themer sidebar
2451
+ * applies everywhere while the user browses around, runs the themer machine
2452
+ * that the navbar toggle and the sidebar share — the user's themes, which one
2453
+ * is applied, which flow the sidebar is in and how the Studio is previewed —
2454
+ * and renders the sidebar next to the Studio. The sidebar sits at the
2455
+ * `layout` level rather than in `activeToolLayout` because the split preview
2456
+ * renders the Studio twice, and the sidebar must not come along.
613
2457
  *
614
- * The draft theme provider inherits the color scheme from the Studio, so the
615
- * preview follows the appearance setting (light/dark/system) like any other
616
- * theme.
2458
+ * The single preview inherits the color scheme from the Studio, so it follows
2459
+ * the appearance setting (light/dark/system) like any other theme.
617
2460
  *
618
2461
  * @internal
619
2462
  */
620
2463
  function ThemerLayout(props) {
621
- let $ = c(13), { baseOptions, ...layoutProps } = props, [open, setOpen] = useState(!1), [options, setOptions] = useState(readStoredOptions), t0, t1;
622
- $[0] === options ? (t0 = $[1], t1 = $[2]) : (t0 = () => writeStoredOptions(options), t1 = [options], $[0] = options, $[1] = t0, $[2] = t1), useEffect(t0, t1);
623
- let t2;
624
- $[3] === options ? t2 = $[4] : (t2 = options === null ? null : buildTheme(options), $[3] = options, $[4] = t2);
625
- let theme = t2, t3;
626
- $[5] !== baseOptions || $[6] !== open || $[7] !== options ? (t3 = {
2464
+ let $ = c(55), { baseOptions, ...layoutProps } = props, t0;
2465
+ $[0] === baseOptions ? t0 = $[1] : (t0 = () => ({
627
2466
  baseOptions,
628
- options,
629
- setOptions,
2467
+ stored: readStoredState()
2468
+ }), $[0] = baseOptions, $[1] = t0);
2469
+ let [input] = useState(t0), t1;
2470
+ $[2] === input ? t1 = $[3] : (t1 = { input }, $[2] = input, $[3] = t1);
2471
+ let [snapshot, send, actorRef] = useActor(themerMachine, t1), stored = useSelector(actorRef, selectStoredState, sameStoredState), view = useSelector(actorRef, selectView, sameView), t2;
2472
+ $[4] === snapshot ? t2 = $[5] : (t2 = snapshot.matches({ sidebar: "open" }), $[4] = snapshot, $[5] = t2);
2473
+ let open = t2, t3;
2474
+ $[6] === snapshot ? t3 = $[7] : (t3 = snapshot.matches({ preview: "split" }), $[6] = snapshot, $[7] = t3);
2475
+ let split = t3, { images } = snapshot.context, t4, t5;
2476
+ $[8] === stored ? (t4 = $[9], t5 = $[10]) : (t4 = () => writeStoredState(stored), t5 = [stored], $[8] = stored, $[9] = t4, $[10] = t5), useEffect(t4, t5);
2477
+ let previousImages = useRef(images), t6, t7;
2478
+ $[11] === images ? (t6 = $[12], t7 = $[13]) : (t6 = () => {
2479
+ let current = new Set(Object.values(images));
2480
+ for (let url of Object.values(previousImages.current)) current.has(url) || URL.revokeObjectURL(url);
2481
+ previousImages.current = images;
2482
+ }, t7 = [images], $[11] = images, $[12] = t6, $[13] = t7), useEffect(t6, t7);
2483
+ let t8, t9;
2484
+ $[14] === Symbol.for("react.memo_cache_sentinel") ? (t8 = () => () => {
2485
+ for (let url_0 of Object.values(previousImages.current)) URL.revokeObjectURL(url_0);
2486
+ }, t9 = [], $[14] = t8, $[15] = t9) : (t8 = $[14], t9 = $[15]), useEffect(t8, t9);
2487
+ let t10;
2488
+ $[16] !== baseOptions || $[17] !== stored ? (t10 = resolveThemes(stored, baseOptions), $[16] = baseOptions, $[17] = stored, $[18] = t10) : t10 = $[18];
2489
+ let { themes, removed, active } = t10, activeOptions = active.source === "config" ? null : active.options, t11;
2490
+ $[19] === activeOptions ? t11 = $[20] : (t11 = activeOptions === null ? null : buildTheme(activeOptions), $[19] = activeOptions, $[20] = t11);
2491
+ let theme = t11, scheme = useColorSchemeValue(), t12;
2492
+ $[21] !== scheme || $[22] !== theme?.v2?.color ? (t12 = () => {
2493
+ let background = theme?.v2?.color[scheme].default.bg;
2494
+ if (background === void 0) return;
2495
+ let { style } = document.body, previous = style.backgroundColor;
2496
+ return style.backgroundColor = background, () => {
2497
+ style.backgroundColor = previous;
2498
+ };
2499
+ }, $[21] = scheme, $[22] = theme?.v2?.color, $[23] = t12) : t12 = $[23];
2500
+ let t13;
2501
+ $[24] !== scheme || $[25] !== theme ? (t13 = [scheme, theme], $[24] = scheme, $[25] = theme, $[26] = t13) : t13 = $[26], useEffect(t12, t13);
2502
+ let t14;
2503
+ $[27] !== active || $[28] !== baseOptions || $[29] !== images || $[30] !== open || $[31] !== removed || $[32] !== send || $[33] !== split || $[34] !== themes || $[35] !== view ? (t14 = {
2504
+ baseOptions,
2505
+ themes,
2506
+ removed,
2507
+ active,
2508
+ images,
2509
+ view,
630
2510
  open,
631
- setOpen
632
- }, $[5] = baseOptions, $[6] = open, $[7] = options, $[8] = t3) : t3 = $[8];
633
- let context = t3, t4 = ThemerContext, t5 = theme === null ? layoutProps.renderDefault(layoutProps) : /* @__PURE__ */ jsx(ThemeProvider, {
2511
+ split,
2512
+ send
2513
+ }, $[27] = active, $[28] = baseOptions, $[29] = images, $[30] = open, $[31] = removed, $[32] = send, $[33] = split, $[34] = themes, $[35] = view, $[36] = t14) : t14 = $[36];
2514
+ let context = t14, studio = layoutProps.renderDefault(layoutProps), t15 = split ? "light" : void 0, t16;
2515
+ $[37] !== studio || $[38] !== t15 || $[39] !== theme ? (t16 = /* @__PURE__ */ jsx(StudioPreview, {
2516
+ scheme: t15,
634
2517
  theme,
635
- children: layoutProps.renderDefault(layoutProps)
636
- }), t6;
637
- return $[9] !== context || $[10] !== t4.Provider || $[11] !== t5 ? (t6 = /* @__PURE__ */ jsx(t4.Provider, {
2518
+ children: studio
2519
+ }), $[37] = studio, $[38] = t15, $[39] = theme, $[40] = t16) : t16 = $[40];
2520
+ let t17;
2521
+ $[41] !== split || $[42] !== studio || $[43] !== theme ? (t17 = split && /* @__PURE__ */ jsx(StudioPreview, {
2522
+ borderLeft: !0,
2523
+ scheme: "dark",
2524
+ theme,
2525
+ children: studio
2526
+ }), $[41] = split, $[42] = studio, $[43] = theme, $[44] = t17) : t17 = $[44];
2527
+ let t18;
2528
+ $[45] !== open || $[46] !== theme ? (t18 = open && /* @__PURE__ */ jsx(ThemeProvider, {
2529
+ theme: theme ?? void 0,
2530
+ children: /* @__PURE__ */ jsx(ResizableSidebar, {})
2531
+ }), $[45] = open, $[46] = theme, $[47] = t18) : t18 = $[47];
2532
+ let t19;
2533
+ $[48] !== t16 || $[49] !== t17 || $[50] !== t18 ? (t19 = /* @__PURE__ */ jsxs(Flex, {
2534
+ height: "fill",
2535
+ sizing: "border",
2536
+ children: [
2537
+ t16,
2538
+ t17,
2539
+ t18
2540
+ ]
2541
+ }), $[48] = t16, $[49] = t17, $[50] = t18, $[51] = t19) : t19 = $[51];
2542
+ let t20;
2543
+ return $[52] !== context || $[53] !== t19 ? (t20 = /* @__PURE__ */ jsx(ThemerContext.Provider, {
638
2544
  value: context,
639
- children: t5
640
- }), $[9] = context, $[10] = t4.Provider, $[11] = t5, $[12] = t6) : t6 = $[12], t6;
2545
+ children: t19
2546
+ }), $[52] = context, $[53] = t19, $[54] = t20) : t20 = $[54], t20;
2547
+ }
2548
+ /**
2549
+ * One copy of the Studio, in the given color scheme — or, without one, in the
2550
+ * scheme the Studio is showing. The configured theme (`null`) goes through
2551
+ * the provider too, inheriting the Studio's, so picking a theme swaps it
2552
+ * instead of remounting the Studio under a new provider.
2553
+ *
2554
+ * The two copies of the split preview share the router, the document store
2555
+ * and every other provider above the layout — only the scheme differs — so
2556
+ * they stay in sync while navigating. The `color-scheme` of a forced scheme
2557
+ * keeps native form controls and scrollbars in step with it.
2558
+ */
2559
+ function StudioPreview(props) {
2560
+ let $ = c(10), { borderLeft, children, scheme, theme } = props, t0 = theme ?? void 0, t1;
2561
+ $[0] === scheme ? t1 = $[1] : (t1 = scheme ? { colorScheme: scheme } : void 0, $[0] = scheme, $[1] = t1);
2562
+ let t2;
2563
+ $[2] !== borderLeft || $[3] !== children || $[4] !== t1 ? (t2 = /* @__PURE__ */ jsx(Card, {
2564
+ borderLeft,
2565
+ flex: 1,
2566
+ height: "fill",
2567
+ overflow: "hidden",
2568
+ style: t1,
2569
+ children
2570
+ }), $[2] = borderLeft, $[3] = children, $[4] = t1, $[5] = t2) : t2 = $[5];
2571
+ let t3;
2572
+ return $[6] !== scheme || $[7] !== t0 || $[8] !== t2 ? (t3 = /* @__PURE__ */ jsx(ThemeProvider, {
2573
+ scheme,
2574
+ theme: t0,
2575
+ children: t2
2576
+ }), $[6] = scheme, $[7] = t0, $[8] = t2, $[9] = t3) : t3 = $[9], t3;
641
2577
  }
642
2578
  function ThemerNavbarButton() {
643
- let $ = c(7), { open, setOpen } = useThemer(), t0;
2579
+ let $ = c(6), { open, send } = useThemer(), t0;
644
2580
  $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = /* @__PURE__ */ jsx(Text, {
645
2581
  size: 1,
646
2582
  children: "Themer"
647
2583
  }), $[0] = t0) : t0 = $[0];
648
2584
  let t1;
649
- $[1] !== open || $[2] !== setOpen ? (t1 = () => setOpen(!open), $[1] = open, $[2] = setOpen, $[3] = t1) : t1 = $[3];
2585
+ $[1] === send ? t1 = $[2] : (t1 = () => send({ type: "sidebar.toggle" }), $[1] = send, $[2] = t1);
650
2586
  let t2;
651
- return $[4] !== open || $[5] !== t1 ? (t2 = /* @__PURE__ */ jsx(Tooltip, {
2587
+ return $[3] !== open || $[4] !== t1 ? (t2 = /* @__PURE__ */ jsx(Tooltip, {
2588
+ animate: !0,
652
2589
  content: t0,
653
2590
  portal: !0,
654
2591
  children: /* @__PURE__ */ jsx(Button, {
@@ -659,22 +2596,25 @@ function ThemerNavbarButton() {
659
2596
  padding: 2,
660
2597
  selected: open
661
2598
  })
662
- }), $[4] = open, $[5] = t1, $[6] = t2) : t2 = $[6], t2;
2599
+ }), $[3] = open, $[4] = t1, $[5] = t2) : t2 = $[5], t2;
663
2600
  }
664
2601
  /**
665
2602
  * Adds the toggle that opens and closes the themer sidebar to the Studio
666
2603
  * navbar — an icon button with a tooltip in the top bar (like the Tasks
667
2604
  * toggle), and a regular titled action in the narrow-screen sidebar menu.
668
2605
  *
2606
+ * In the split preview every Studio copy renders its own navbar, so the
2607
+ * toggle shows up in both — they drive the same sidebar.
2608
+ *
669
2609
  * @internal
670
2610
  */
671
2611
  function ThemerNavbar(props) {
672
- let $ = c(10), { open, setOpen } = useThemer(), t0;
2612
+ let $ = c(9), { open, send } = useThemer(), t0;
673
2613
  $[0] === props.__internal_actions ? t0 = $[1] : (t0 = props.__internal_actions ?? [], $[0] = props.__internal_actions, $[1] = t0);
674
2614
  let t1;
675
- if ($[2] !== open || $[3] !== props || $[4] !== setOpen || $[5] !== t0) {
2615
+ if ($[2] !== open || $[3] !== props || $[4] !== send || $[5] !== t0) {
676
2616
  let t2;
677
- $[7] !== open || $[8] !== setOpen ? (t2 = () => setOpen(!open), $[7] = open, $[8] = setOpen, $[9] = t2) : t2 = $[9], t1 = props.renderDefault({
2617
+ $[7] === send ? t2 = $[8] : (t2 = () => send({ type: "sidebar.toggle" }), $[7] = send, $[8] = t2), t1 = props.renderDefault({
678
2618
  ...props,
679
2619
  __internal_actions: [
680
2620
  ...t0,
@@ -692,7 +2632,7 @@ function ThemerNavbar(props) {
692
2632
  title: "Themer"
693
2633
  }
694
2634
  ]
695
- }), $[2] = open, $[3] = props, $[4] = setOpen, $[5] = t0, $[6] = t1;
2635
+ }), $[2] = open, $[3] = props, $[4] = send, $[5] = t0, $[6] = t1;
696
2636
  } else t1 = $[6];
697
2637
  return t1;
698
2638
  }
@@ -701,11 +2641,16 @@ function _temp() {
701
2641
  }
702
2642
  /**
703
2643
  * A Studio plugin that adds a themer sidebar for `buildTheme` themes: a
704
- * navbar toggle opens the sidebar next to the active tool, where presets, the
705
- * accent/text/background pickers and the contrast slider preview a
706
- * `buildTheme` theme live on the whole Studio while you browse around.
707
- * Toggle between light and dark mode with the regular appearance menu — the
708
- * preview follows it.
2644
+ * navbar toggle opens the sidebar next to the Studio, with a list of themes —
2645
+ * the configured theme, the presets and your own — each previewed as a tiny
2646
+ * Studio in both color schemes. Picking one applies it live to the whole
2647
+ * Studio while you browse around; your own themes can be edited with
2648
+ * accent/text/background pickers and a contrast slider per scheme, added,
2649
+ * duplicated, given the colors of an image (its palette is read on device),
2650
+ * removed and restored, and a dialog shows the `buildTheme` snippet that
2651
+ * makes the applied theme permanent. Toggle between light and dark mode with
2652
+ * the regular appearance menu — the preview follows it — or split the
2653
+ * preview to see the whole Studio in light and dark side by side.
709
2654
  *
710
2655
  * ```ts
711
2656
  * import {themerTool} from '@sanity/themer/tool'
@@ -723,7 +2668,7 @@ function _temp() {
723
2668
  * @alpha
724
2669
  */
725
2670
  const themerTool = definePlugin((options) => {
726
- let baseOptions = options?.config ?? { accent: DEFAULT_ACCENT };
2671
+ let baseOptions = options?.config ?? {};
727
2672
  function ThemerLayoutWithOptions(props) {
728
2673
  let $ = c(2), t0;
729
2674
  return $[0] === props ? t0 = $[1] : (t0 = /* @__PURE__ */ jsx(ThemerLayout, {
@@ -735,8 +2680,7 @@ const themerTool = definePlugin((options) => {
735
2680
  name: "@sanity/themer/tool",
736
2681
  studio: { components: {
737
2682
  layout: ThemerLayoutWithOptions,
738
- navbar: ThemerNavbar,
739
- activeToolLayout: ThemerActiveToolLayout
2683
+ navbar: ThemerNavbar
740
2684
  } }
741
2685
  };
742
2686
  });