@sanity/themer 0.9.0 → 0.10.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/README.md +159 -19
- package/dist/{AnimatedColorWheelIcon-DEy-cy3X.js → AnimatedColorWheelIcon-CwWOVAsw.js} +3 -3
- package/dist/AnimatedColorWheelIcon-CwWOVAsw.js.map +1 -0
- package/dist/{ResizableSidebar-BpgLA4XQ.js → ResizableSidebar-C1nr3Ls2.js} +428 -339
- package/dist/ResizableSidebar-C1nr3Ls2.js.map +1 -0
- package/dist/bundle.css +27 -19
- package/dist/{themes-Bxgltjmb.js → themes-au1BLKmY.js} +8 -8
- package/dist/themes-au1BLKmY.js.map +1 -0
- package/dist/tool.js +287 -85
- package/dist/tool.js.map +1 -1
- package/package.json +1 -1
- package/dist/AnimatedColorWheelIcon-DEy-cy3X.js.map +0 -1
- package/dist/ResizableSidebar-BpgLA4XQ.js.map +0 -1
- package/dist/themes-Bxgltjmb.js.map +0 -1
package/dist/tool.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "@sanity/themer/bundle.css";
|
|
2
2
|
import { l as isColor, n as buildTheme, o as SCHEMES, t as presets } from "./presets-CqBgS3A-.js";
|
|
3
|
-
import { a as displayTitle, c as resolveThemes, d as viewTransitionClasses, f as NAVBAR_SELECTOR, i as createCustomTheme, l as addThemerTransitionType, n as CONFIG_TITLE, o as duplicateTitle, s as initialThemerState, u as layout } from "./themes-
|
|
3
|
+
import { a as displayTitle, c as resolveThemes, d as viewTransitionClasses, f as NAVBAR_SELECTOR, i as createCustomTheme, l as addThemerTransitionType, n as CONFIG_TITLE, o as duplicateTitle, s as initialThemerState, u as layout } from "./themes-au1BLKmY.js";
|
|
4
4
|
import { c } from "react/compiler-runtime";
|
|
5
5
|
import { definePlugin, useColorSchemeValue } from "sanity";
|
|
6
6
|
import { ColorWheelIcon } from "@sanity/icons/ColorWheel";
|
|
7
|
-
import { BoundaryElementProvider, Box, Button, Card, Flex, Text, ThemeProvider, useMediaIndex } from "@sanity/ui";
|
|
7
|
+
import { BoundaryElementProvider, Box, Button, Card, Flex, Text, ThemeProvider, useMediaIndex, usePrefersReducedMotion } from "@sanity/ui";
|
|
8
8
|
import { Tooltip } from "@sanity/ui/tooltip";
|
|
9
9
|
import { Activity, Suspense, ViewTransition, createContext, lazy, startTransition, use, useEffect, useReducer, useRef, useState, useTransition } from "react";
|
|
10
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -16,11 +16,11 @@ import { assign, not, setup } from "xstate";
|
|
|
16
16
|
/** @internal */
|
|
17
17
|
const ToolIsOpenContext = createContext(!1), ToolDispatchContext = createContext(() => {
|
|
18
18
|
throw Error("ToolDispatchContext not initialized");
|
|
19
|
-
}), ToolSplitIsOpenContext = createContext(!1), ToolShouldDetectNavbarHeightContext = createContext(!1), PluginConfigContext = createContext(null), AnimatedColorWheelIcon = lazy(() => import("./AnimatedColorWheelIcon-
|
|
19
|
+
}), ToolSplitIsOpenContext = createContext(!1), ToolShouldDetectNavbarHeightContext = createContext(!1), PluginConfigContext = createContext(null), AnimatedColorWheelIcon = lazy(() => import("./AnimatedColorWheelIcon-CwWOVAsw.js"));
|
|
20
20
|
function ThemerNavbarButton() {
|
|
21
21
|
let $ = c(27), config = use(PluginConfigContext), dispatch = use(ToolDispatchContext), open = use(ToolIsOpenContext), shouldDetectNavbarHeight = use(ToolShouldDetectNavbarHeightContext), split = use(ToolSplitIsOpenContext);
|
|
22
22
|
if (!config) throw TypeError("PluginConfigContext.Provider is missing");
|
|
23
|
-
let [busy, startBusyTransition] = useTransition(), iconRef = useRef(null), t0;
|
|
23
|
+
let [busy, startBusyTransition] = useTransition(), iconRef = useRef(null), prefersReducedMotion = usePrefersReducedMotion(), t0;
|
|
24
24
|
$[0] !== dispatch || $[1] !== split ? (t0 = () => startBusyTransition(() => {
|
|
25
25
|
split && addThemerTransitionType("split-screen:open"), addThemerTransitionType("open"), dispatch({ type: "open" });
|
|
26
26
|
}), $[0] = dispatch, $[1] = split, $[2] = t0) : t0 = $[2];
|
|
@@ -51,43 +51,41 @@ function ThemerNavbarButton() {
|
|
|
51
51
|
children: title
|
|
52
52
|
}), $[11] = title, $[12] = t4);
|
|
53
53
|
let t5 = shouldDetectNavbarHeight ? observeNavbarHeight : void 0, t6;
|
|
54
|
-
$[13]
|
|
55
|
-
|
|
56
|
-
$[14] === busy ? t7 = $[15] : (t7 = /* @__PURE__ */ jsx(Suspense, {
|
|
57
|
-
fallback: t6,
|
|
54
|
+
$[13] !== busy || $[14] !== prefersReducedMotion ? (t6 = prefersReducedMotion ? /* @__PURE__ */ jsx(ColorWheelIcon, {}) : /* @__PURE__ */ jsx(Suspense, {
|
|
55
|
+
fallback: /* @__PURE__ */ jsx(ColorWheelIcon, {}),
|
|
58
56
|
children: /* @__PURE__ */ jsx(AnimatedColorWheelIcon, {
|
|
59
57
|
busy,
|
|
60
58
|
ref: iconRef
|
|
61
59
|
})
|
|
62
|
-
}), $[
|
|
63
|
-
let
|
|
64
|
-
$[16] !== busy || $[17] !== handlePrerender || $[18] !== t5 || $[19] !==
|
|
60
|
+
}), $[13] = busy, $[14] = prefersReducedMotion, $[15] = t6) : t6 = $[15];
|
|
61
|
+
let t7 = open ? handleClose : handleOpen, t8 = open || busy, t9;
|
|
62
|
+
$[16] !== busy || $[17] !== handlePrerender || $[18] !== t5 || $[19] !== t6 || $[20] !== t7 || $[21] !== t8 || $[22] !== title ? (t9 = /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(ViewTransition, {
|
|
65
63
|
default: "none",
|
|
66
64
|
update: viewTransitionClasses.navbarButton.update,
|
|
67
65
|
children: /* @__PURE__ */ jsx(Button, {
|
|
68
66
|
ref: t5,
|
|
69
67
|
"aria-busy": busy,
|
|
70
68
|
"aria-label": title,
|
|
71
|
-
icon:
|
|
69
|
+
icon: t6,
|
|
72
70
|
mode: "bleed",
|
|
73
|
-
onClick:
|
|
71
|
+
onClick: t7,
|
|
74
72
|
onFocus: handlePrerender,
|
|
75
73
|
onMouseEnter: handlePrerender,
|
|
76
74
|
padding: 2,
|
|
77
|
-
selected:
|
|
75
|
+
selected: t8
|
|
78
76
|
})
|
|
79
|
-
}) }), $[16] = busy, $[17] = handlePrerender, $[18] = t5, $[19] =
|
|
80
|
-
let
|
|
81
|
-
return $[24] !==
|
|
77
|
+
}) }), $[16] = busy, $[17] = handlePrerender, $[18] = t5, $[19] = t6, $[20] = t7, $[21] = t8, $[22] = title, $[23] = t9) : t9 = $[23];
|
|
78
|
+
let t10;
|
|
79
|
+
return $[24] !== t4 || $[25] !== t9 ? (t10 = /* @__PURE__ */ jsx(ViewTransition, {
|
|
82
80
|
default: "none",
|
|
83
81
|
update: viewTransitionClasses.navbarButtonTooltip.update,
|
|
84
82
|
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
85
83
|
animate: !0,
|
|
86
84
|
content: t4,
|
|
87
85
|
portal: !0,
|
|
88
|
-
children:
|
|
86
|
+
children: t9
|
|
89
87
|
})
|
|
90
|
-
}), $[24] =
|
|
88
|
+
}), $[24] = t4, $[25] = t9, $[26] = t10) : t10 = $[26], t10;
|
|
91
89
|
}
|
|
92
90
|
function ThemerNavbar(props) {
|
|
93
91
|
"use memo";
|
|
@@ -175,6 +173,11 @@ function toolReducer(state, action) {
|
|
|
175
173
|
default: throw Error(`Unknown themer tool action: ${JSON.stringify(action)}`);
|
|
176
174
|
}
|
|
177
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Where a tab starts: closed, nothing prerendered, no theme applied —
|
|
178
|
+
* `ThemerProvider` seeds only the applied theme, from the machine's restored
|
|
179
|
+
* snapshot
|
|
180
|
+
*/
|
|
178
181
|
const initialToolState = {
|
|
179
182
|
navbarHeight: null,
|
|
180
183
|
open: !1,
|
|
@@ -283,13 +286,17 @@ function parseThemerState(input) {
|
|
|
283
286
|
let result = v.safeParse(themerStateSchema, input);
|
|
284
287
|
return result.success ? result.output : null;
|
|
285
288
|
}
|
|
286
|
-
/** The
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
289
|
+
/** The flows a session restores to */
|
|
290
|
+
const flowSchema = v.picklist([
|
|
291
|
+
"list",
|
|
292
|
+
"edit",
|
|
293
|
+
"removed"
|
|
294
|
+
]), snapshotValueSchema = v.object({
|
|
295
|
+
flow: v.union([flowSchema, v.object({ list: v.picklist([
|
|
296
|
+
"idle",
|
|
297
|
+
"pasting",
|
|
298
|
+
"snippet"
|
|
299
|
+
]) })]),
|
|
293
300
|
theme: v.picklist(["applied", "switching"])
|
|
294
301
|
});
|
|
295
302
|
/**
|
|
@@ -322,8 +329,9 @@ function snapshotFromState(state, editing = null, flow = "list") {
|
|
|
322
329
|
/**
|
|
323
330
|
* The machine's snapshot as the last session persisted it, made fit for this
|
|
324
331
|
* one: the themes are parsed like any persisted state, the flow keeps its
|
|
325
|
-
* subject or falls back to the list, a
|
|
326
|
-
* and the image URLs of the last session
|
|
332
|
+
* subject or falls back to the list, a dialog that was open is closed, a
|
|
333
|
+
* switch that was under way is over, and the image URLs of the last session
|
|
334
|
+
* are gone with it.
|
|
327
335
|
*
|
|
328
336
|
* @internal
|
|
329
337
|
*/
|
|
@@ -344,7 +352,7 @@ const persistedSnapshotSchema = v.pipe(v.object({
|
|
|
344
352
|
removed: [],
|
|
345
353
|
order: []
|
|
346
354
|
}, editing = context.editing && state.custom.some((theme) => theme.slug === context.editing?.slug) && !state.removed.includes(context.editing.slug) ? context.editing : null;
|
|
347
|
-
return snapshotFromState(state, editing, value.flow === "edit" && editing === null || value.flow === "removed" && state.removed.length === 0 ? "list" : value.flow);
|
|
355
|
+
return snapshotFromState(state, editing, typeof value.flow != "string" || value.flow === "edit" && editing === null || value.flow === "removed" && state.removed.length === 0 ? "list" : value.flow);
|
|
348
356
|
})), SNAPSHOT_STORAGE_KEY = "sanityStudio:themer:snapshot", STATE_STORAGE_KEY = "sanityStudio:themer:state", LEGACY_STORAGE_KEY = "sanityStudio:themer:options", LEGACY_DRAFT = {
|
|
349
357
|
slug: "custom-draft",
|
|
350
358
|
title: "Draft theme"
|
|
@@ -418,18 +426,20 @@ function revokeObjectUrl(url) {
|
|
|
418
426
|
}
|
|
419
427
|
/**
|
|
420
428
|
* The themes of the themer tool and the sidebar's way through them: which
|
|
421
|
-
* `flow` the sidebar is in — picking a theme from the `list`,
|
|
422
|
-
*
|
|
423
|
-
* `
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
429
|
+
* `flow` the sidebar is in — picking a theme from the `list`, with its
|
|
430
|
+
* dialogs for pasting a shared code and for the applied theme's snippet,
|
|
431
|
+
* `edit`ing one of the user's own themes, or restoring `removed` ones — and,
|
|
432
|
+
* in a parallel `theme` region, whether another theme was just applied. The
|
|
433
|
+
* context carries the persisted state (the applied theme, the user's themes
|
|
434
|
+
* and what was removed) alongside what the flows need. Whether the sidebar is
|
|
435
|
+
* open and whether the Studio shows twice is not the machine's: the tool
|
|
436
|
+
* reducer in `ThemerProvider` owns that, so that the navbar toggle and the
|
|
437
|
+
* sidebar can change it in transitions of their own. Nor is persisting: the
|
|
438
|
+
* machine never touches storage and takes no input — it starts from no
|
|
439
|
+
* themes, and a session with something to pick up from restores its
|
|
440
|
+
* persisted snapshot instead (see `storage.ts` and `sync.ts`), while the
|
|
441
|
+
* persisted state of other tabs reaches it as a `themes.sync` event like any
|
|
442
|
+
* other.
|
|
433
443
|
*
|
|
434
444
|
* The `theme` region is `switching` (its tag) right after an event that
|
|
435
445
|
* applies another theme — picking one, adding, duplicating, importing or
|
|
@@ -453,6 +463,7 @@ const themerMachine = setup({
|
|
|
453
463
|
isCustomTheme: ({ context }, params) => themesOf(context).themes.some((theme) => theme.slug === params.slug && theme.source === "custom"),
|
|
454
464
|
isEditingListedTheme: ({ context }) => context.editing !== null && themesOf(context).themes.some((theme) => theme.slug === context.editing?.slug && theme.source === "custom"),
|
|
455
465
|
hasRemovedThemes: ({ context }) => themesOf(context).removed.length > 0,
|
|
466
|
+
hasAppliedTheme: ({ context }) => themesOf(context).active !== void 0,
|
|
456
467
|
isAnotherTheme: ({ context }, params) => params.slug !== (context.active ?? "config"),
|
|
457
468
|
isAppliedTheme: ({ context }, params) => params.slug === context.active,
|
|
458
469
|
appliesAnotherTheme: ({ context }, params) => params.state.active !== context.active,
|
|
@@ -768,7 +779,26 @@ const themerMachine = setup({
|
|
|
768
779
|
}
|
|
769
780
|
},
|
|
770
781
|
states: {
|
|
771
|
-
list: {
|
|
782
|
+
list: {
|
|
783
|
+
initial: "idle",
|
|
784
|
+
states: {
|
|
785
|
+
idle: { on: {
|
|
786
|
+
"dialog.paste": "pasting",
|
|
787
|
+
"dialog.snippet": {
|
|
788
|
+
guard: "hasAppliedTheme",
|
|
789
|
+
target: "snippet"
|
|
790
|
+
}
|
|
791
|
+
} },
|
|
792
|
+
pasting: { on: { "dialog.close": "idle" } },
|
|
793
|
+
snippet: {
|
|
794
|
+
on: { "dialog.close": "idle" },
|
|
795
|
+
always: {
|
|
796
|
+
guard: not("hasAppliedTheme"),
|
|
797
|
+
target: "idle"
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
},
|
|
772
802
|
edit: {
|
|
773
803
|
exit: "stopEditing",
|
|
774
804
|
always: {
|
|
@@ -784,57 +814,225 @@ const themerMachine = setup({
|
|
|
784
814
|
}
|
|
785
815
|
}
|
|
786
816
|
});
|
|
817
|
+
/** The part of the machine context that is persisted between sessions @internal */
|
|
818
|
+
function selectStoredState(snapshot) {
|
|
819
|
+
let { active, custom, removed, order } = snapshot.context;
|
|
820
|
+
return {
|
|
821
|
+
active,
|
|
822
|
+
custom,
|
|
823
|
+
removed,
|
|
824
|
+
order
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/** The channel every tab of the same origin shares — one Studio, one set of themes */
|
|
828
|
+
const PERSIST_TIMEOUT = 1e3;
|
|
829
|
+
function openBroadcastChannel() {
|
|
830
|
+
if (typeof BroadcastChannel != "function") return null;
|
|
831
|
+
let channel = new BroadcastChannel("sanity-themer");
|
|
832
|
+
return {
|
|
833
|
+
post: (message) => channel.postMessage(message),
|
|
834
|
+
listen: (listener) => {
|
|
835
|
+
let onMessage = (event) => listener(event.data);
|
|
836
|
+
return channel.addEventListener("message", onMessage), () => channel.removeEventListener("message", onMessage);
|
|
837
|
+
},
|
|
838
|
+
close: () => channel.close()
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function browserLocks() {
|
|
842
|
+
return typeof navigator < "u" && "locks" in navigator ? navigator.locks : void 0;
|
|
843
|
+
}
|
|
844
|
+
function scheduleIdle(callback) {
|
|
845
|
+
if (typeof requestIdleCallback == "function") {
|
|
846
|
+
let id = requestIdleCallback(callback, { timeout: PERSIST_TIMEOUT });
|
|
847
|
+
return () => cancelIdleCallback(id);
|
|
848
|
+
}
|
|
849
|
+
let id = setTimeout(callback, PERSIST_TIMEOUT);
|
|
850
|
+
return () => clearTimeout(id);
|
|
851
|
+
}
|
|
852
|
+
function parseMessage(data) {
|
|
853
|
+
if (!data || typeof data != "object") return null;
|
|
854
|
+
let type = Reflect.get(data, "type");
|
|
855
|
+
if (type === "hello") {
|
|
856
|
+
let from = Reflect.get(data, "from");
|
|
857
|
+
return typeof from == "string" ? {
|
|
858
|
+
type,
|
|
859
|
+
from
|
|
860
|
+
} : null;
|
|
861
|
+
}
|
|
862
|
+
if (type === "state") {
|
|
863
|
+
let state = parseThemerState(Reflect.get(data, "state")), revision = Reflect.get(data, "revision"), from = Reflect.get(data, "from"), to = Reflect.get(data, "to");
|
|
864
|
+
return !state || typeof revision != "number" || !Number.isFinite(revision) || typeof from != "string" ? null : {
|
|
865
|
+
type,
|
|
866
|
+
state,
|
|
867
|
+
revision,
|
|
868
|
+
from,
|
|
869
|
+
...typeof to == "string" ? { to } : {}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
function tabId() {
|
|
875
|
+
return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Keeps the themes of every tab of the Studio the same, and persists them
|
|
879
|
+
* from one tab only.
|
|
880
|
+
*
|
|
881
|
+
* Every tab runs its own machine, and what the machine persists — the
|
|
882
|
+
* applied theme, the user's themes, what was removed and reordered — goes
|
|
883
|
+
* around to the other tabs on a `BroadcastChannel` as it changes, where it
|
|
884
|
+
* reaches their machines as a `themes.sync` event: the same themes
|
|
885
|
+
* everywhere, with the flow each tab is in, the tool's open and split state
|
|
886
|
+
* (the reducer's, in `plugin.tsx`) and the images of this session staying
|
|
887
|
+
* the tab's own. A tab that starts asks the others for the current state,
|
|
888
|
+
* in case what it read from storage was not written yet.
|
|
889
|
+
*
|
|
890
|
+
* Persisting is the business of one tab at a time: the one holding the Web
|
|
891
|
+
* Lock, which it keeps for as long as it lives. The others queue for it, so
|
|
892
|
+
* the next tab takes over as the holder goes away, and a tab that takes
|
|
893
|
+
* over persists right away, in case the last one left with a write pending.
|
|
894
|
+
* Writes happen when the browser is idle — a run of edits is written once —
|
|
895
|
+
* and as the page hides, so that nothing pending is lost.
|
|
896
|
+
*
|
|
897
|
+
* Returns what stops the sync: it lets go of the lock, which hands the
|
|
898
|
+
* persisting on, and closes the channel.
|
|
899
|
+
*
|
|
900
|
+
* @internal
|
|
901
|
+
*/
|
|
902
|
+
function syncThemer(actorRef, options) {
|
|
903
|
+
let { persist, openChannel = openBroadcastChannel, schedule = scheduleIdle } = options, channel = openChannel(), locks = channel ? "locks" in options ? options.locks : browserLocks() : void 0, id = tabId(), abort = new AbortController(), leader = !1, cancelPersist, applying = !1, shared = selectStoredState(actorRef.getSnapshot()), revision = 0, origin = id, unchanged = !0, unanswered = /* @__PURE__ */ new Set(), isNewer = (message) => message.revision > revision || message.revision === revision && message.from > origin, persistNow = () => {
|
|
904
|
+
cancelPersist?.(), cancelPersist = void 0, persist(actorRef.getPersistedSnapshot());
|
|
905
|
+
}, persistSoon = () => {
|
|
906
|
+
cancelPersist?.(), cancelPersist = schedule(persistNow);
|
|
907
|
+
}, answer = (to) => {
|
|
908
|
+
channel?.post({
|
|
909
|
+
type: "state",
|
|
910
|
+
state: shared,
|
|
911
|
+
revision,
|
|
912
|
+
from: origin,
|
|
913
|
+
to
|
|
914
|
+
});
|
|
915
|
+
}, becomeLeader = () => {
|
|
916
|
+
leader = !0, persistNow();
|
|
917
|
+
for (let from of unanswered) answer(from);
|
|
918
|
+
unanswered.clear();
|
|
919
|
+
};
|
|
920
|
+
locks ? locks.request("sanity-themer:persist", { signal: abort.signal }, () => (becomeLeader(), new Promise((resolve) => {
|
|
921
|
+
abort.signal.addEventListener("abort", () => resolve(), { once: !0 });
|
|
922
|
+
}))).catch(() => {}) : becomeLeader();
|
|
923
|
+
let subscription = actorRef.subscribe((snapshot) => {
|
|
924
|
+
if (leader && persistSoon(), applying) return;
|
|
925
|
+
let state = selectStoredState(snapshot);
|
|
926
|
+
dequal(state, shared) || (shared = state, unchanged = !1, revision += 1, origin = id, channel?.post({
|
|
927
|
+
type: "state",
|
|
928
|
+
state,
|
|
929
|
+
revision,
|
|
930
|
+
from: id
|
|
931
|
+
}));
|
|
932
|
+
});
|
|
933
|
+
channel && (channel.listen((data) => {
|
|
934
|
+
let message = parseMessage(data);
|
|
935
|
+
if (message) {
|
|
936
|
+
if (message.type === "hello") {
|
|
937
|
+
leader ? answer(message.from) : unanswered.add(message.from);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (message.to !== void 0) {
|
|
941
|
+
if (message.to !== id) return;
|
|
942
|
+
if (!unchanged) {
|
|
943
|
+
if (origin === id) {
|
|
944
|
+
revision = Math.max(revision, message.revision) + 1, channel.post({
|
|
945
|
+
type: "state",
|
|
946
|
+
state: shared,
|
|
947
|
+
revision,
|
|
948
|
+
from: id
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
if (!isNewer(message)) return;
|
|
953
|
+
}
|
|
954
|
+
} else if (!isNewer(message)) return;
|
|
955
|
+
if (revision = message.revision, origin = message.from, !dequal(message.state, shared)) {
|
|
956
|
+
shared = message.state, unchanged = !1, applying = !0;
|
|
957
|
+
try {
|
|
958
|
+
actorRef.send({
|
|
959
|
+
type: "themes.sync",
|
|
960
|
+
state: message.state
|
|
961
|
+
});
|
|
962
|
+
} finally {
|
|
963
|
+
applying = !1;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}), channel.post({
|
|
968
|
+
type: "hello",
|
|
969
|
+
from: id
|
|
970
|
+
}));
|
|
971
|
+
let flush = () => {
|
|
972
|
+
leader && cancelPersist && persistNow();
|
|
973
|
+
}, page = typeof window > "u" ? void 0 : window;
|
|
974
|
+
return page?.addEventListener("pagehide", flush), () => {
|
|
975
|
+
subscription.unsubscribe(), page?.removeEventListener("pagehide", flush), flush(), abort.abort(), channel?.close();
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Persists the machine's snapshot for the next session, and keeps the themes
|
|
980
|
+
* in step with the other tabs of the Studio — from the one tab holding the
|
|
981
|
+
* Web Lock, when the browser is idle; see `sync.ts`. Rendered by the layout,
|
|
982
|
+
* outside the sidebar's `Activity`, so that it runs while the sidebar is
|
|
983
|
+
* closed too: other tabs change the themes whether this one shows them or
|
|
984
|
+
* not.
|
|
985
|
+
*/
|
|
787
986
|
function PersistActorSnapshot(t0) {
|
|
788
987
|
let $ = c(3), { actorRef } = t0, t1, t2;
|
|
789
|
-
return $[0] === actorRef ? (t1 = $[1], t2 = $[2]) : (t1 = () => {
|
|
790
|
-
let cancel, cancelOrClear = function cancelOrClear() {
|
|
791
|
-
"cancelIdleCallback" in window ? cancelIdleCallback(cancel) : clearTimeout(cancel);
|
|
792
|
-
}, subscription = actorRef.subscribe(() => {
|
|
793
|
-
cancelOrClear();
|
|
794
|
-
let callback = () => {
|
|
795
|
-
writePersistedSnapshot(actorRef.getPersistedSnapshot());
|
|
796
|
-
};
|
|
797
|
-
cancel = "cancelIdleCallback" in window ? requestIdleCallback(callback) : setTimeout(callback, 1e3);
|
|
798
|
-
});
|
|
799
|
-
return () => {
|
|
800
|
-
subscription.unsubscribe(), cancelOrClear();
|
|
801
|
-
};
|
|
802
|
-
}, t2 = [actorRef], $[0] = actorRef, $[1] = t1, $[2] = t2), useEffect(t1, t2), null;
|
|
988
|
+
return $[0] === actorRef ? (t1 = $[1], t2 = $[2]) : (t1 = () => syncThemer(actorRef, { persist: writePersistedSnapshot }), t2 = [actorRef], $[0] = actorRef, $[1] = t1, $[2] = t2), useEffect(t1, t2), null;
|
|
803
989
|
}
|
|
804
990
|
/**
|
|
805
991
|
* The sidebar — everything in it, from the theme list to the snippet dialog —
|
|
806
992
|
* loads the first time it opens. There is no `Suspense` boundary around it,
|
|
807
|
-
* so it must only ever mount from a
|
|
808
|
-
* it
|
|
809
|
-
*
|
|
993
|
+
* so it must only ever mount from a transition: the navbar toggle prerenders
|
|
994
|
+
* it in one as the pointer comes near, which keeps the Studio as it was while
|
|
995
|
+
* the code loads, where an urgent render would suspend up to the Studio's
|
|
810
996
|
* own boundary and swap the whole Studio for its loading screen.
|
|
811
997
|
*/
|
|
812
|
-
const ResizableSidebar = lazy(() => import("./ResizableSidebar-
|
|
998
|
+
const ResizableSidebar = lazy(() => import("./ResizableSidebar-C1nr3Ls2.js"));
|
|
813
999
|
/**
|
|
814
|
-
* Wraps the whole Studio
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
* `layout` level rather than in `activeToolLayout` because the split preview
|
|
820
|
-
* renders the Studio twice, and the sidebar must not come along.
|
|
1000
|
+
* Wraps the whole Studio to run the themer machine that the sidebar's flows
|
|
1001
|
+
* share — the user's themes, which one is applied and which flow the sidebar
|
|
1002
|
+
* is in — and renders the sidebar next to the Studio. The sidebar sits at
|
|
1003
|
+
* the `layout` level rather than in `activeToolLayout` because the split
|
|
1004
|
+
* preview renders the Studio twice, and the sidebar must not come along.
|
|
821
1005
|
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
1006
|
+
* Whether the sidebar is open and whether the Studio shows twice comes in as
|
|
1007
|
+
* props from the tool reducer in `ThemerProvider`, and goes on to the sidebar
|
|
1008
|
+
* as props along with the machine's actor, which the sidebar selects its
|
|
1009
|
+
* themes and flow from (see `ThemerProps`): the navbar toggle and the sidebar
|
|
1010
|
+
* dispatch to the reducer inside transitions tagged with a transition type
|
|
1011
|
+
* (see `addThemerTransitionType`), and the `ViewTransition` boundaries below
|
|
1012
|
+
* pick their classes by that type, so React animates the sidebar sliding in
|
|
1013
|
+
* and out, the split copy coming and going and a picked theme cross-fading
|
|
1014
|
+
* in (React 19.3), styled in `ViewTransitions.css.ts`. The sidebar and the
|
|
1015
|
+
* split copy stay mounted while hidden, in `Activity`, so that opening them
|
|
1016
|
+
* again is instant and they can prerender ahead of the transition. The
|
|
1017
|
+
* navbar toggle, which the Studio renders somewhere in `children`, reads the
|
|
1018
|
+
* same values and the plugin's options from the contexts
|
|
1019
|
+
* `ThemerNavbarProvider` provides in each copy of the Studio, and measures
|
|
1020
|
+
* the Studio navbar for the sidebar's header — in the Studio proper, not in
|
|
1021
|
+
* the split copy, which `ToolShouldDetectNavbarHeightContext` tells apart.
|
|
827
1022
|
*
|
|
828
|
-
* The
|
|
829
|
-
* `
|
|
830
|
-
*
|
|
831
|
-
*
|
|
1023
|
+
* The applied theme is the reducer's, provided above this layout in
|
|
1024
|
+
* `ThemerProvider`: the machine publishes it synchronously, which React does
|
|
1025
|
+
* not animate, so `ThemerThemeCrossfader` hands it to the reducer in a
|
|
1026
|
+
* transition of its own. The Studio next to the sidebar always follows the
|
|
1027
|
+
* appearance setting (light/dark/system) and the picked theme like any other
|
|
1028
|
+
* theme would; the split preview adds a second copy in the opposite scheme
|
|
1029
|
+
* on the far side.
|
|
832
1030
|
*
|
|
833
1031
|
* Whatever else on the page has a `view-transition-name` — an avatar the
|
|
834
1032
|
* Studio names so it moves as one piece — comes along in step: every
|
|
835
1033
|
* transition the layout starts carries `layoutTransitionType`, which the
|
|
836
1034
|
* stylesheet keys on to move every group in the layout's time (see
|
|
837
|
-
* `
|
|
1035
|
+
* `ViewTransitions.css.ts`).
|
|
838
1036
|
*
|
|
839
1037
|
* @internal
|
|
840
1038
|
*/
|
|
@@ -929,14 +1127,12 @@ function ThemerLayout(t0) {
|
|
|
929
1127
|
}
|
|
930
1128
|
/**
|
|
931
1129
|
* One copy of the Studio, in the given color scheme — or, without one, in the
|
|
932
|
-
* scheme the Studio is showing. The
|
|
933
|
-
* the
|
|
934
|
-
*
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
* they stay in sync while navigating. The `color-scheme` of a forced scheme
|
|
939
|
-
* keeps native form controls and scrollbars in step with it.
|
|
1130
|
+
* scheme the Studio is showing. The two copies of the split preview share the
|
|
1131
|
+
* router, the document store and every other provider above the layout —
|
|
1132
|
+
* only the scheme differs — so they stay in sync while navigating. The
|
|
1133
|
+
* `color-scheme` of a forced scheme keeps native form controls and scrollbars
|
|
1134
|
+
* in step with it. Each copy provides the navbar toggle it renders with the
|
|
1135
|
+
* contexts it reads (see `ThemerNavbarProvider`).
|
|
940
1136
|
*/
|
|
941
1137
|
function StudioPreview(t0) {
|
|
942
1138
|
let $ = c(17), { borderRight, children, config, dispatch, open, scheme, shouldDetectNavbarHeight, split } = t0, [boundaryElement, setBoundaryElement] = useState(null), t1;
|
|
@@ -972,6 +1168,12 @@ function StudioPreview(t0) {
|
|
|
972
1168
|
}), $[13] = borderRight, $[14] = scheme, $[15] = t4, $[16] = t5) : t5 = $[16], t5;
|
|
973
1169
|
}
|
|
974
1170
|
function _temp() {}
|
|
1171
|
+
/**
|
|
1172
|
+
* Hands the applied theme to the tool reducer as the machine publishes it —
|
|
1173
|
+
* in a transition tagged to cross-fade while the machine says a theme is
|
|
1174
|
+
* being switched to, so that editing the applied theme's colors applies live
|
|
1175
|
+
* while picking another theme fades the Studio over to it
|
|
1176
|
+
*/
|
|
975
1177
|
function ThemerThemeCrossfader(t0) {
|
|
976
1178
|
let $ = c(4), { actorRef, dispatch } = t0, t1, t2;
|
|
977
1179
|
return $[0] !== actorRef || $[1] !== dispatch ? (t1 = () => {
|