@unpunnyfuns/swatchbook-blocks 0.1.1 → 0.1.3

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/index.d.mts CHANGED
@@ -324,7 +324,7 @@ declare function useActiveAxes(): Readonly<Record<string, string>>;
324
324
  * is safe to call from MDX doc blocks (where the preview-hooks context
325
325
  * isn't available).
326
326
  */
327
- declare const ColorFormatContext: _$react.Context<ColorFormat>;
327
+ declare const ColorFormatContext: _$react.Context<ColorFormat | null>;
328
328
  declare function useColorFormat(): ColorFormat;
329
329
  //#endregion
330
330
  //#region src/provider.d.ts
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import Color from "colorjs.io";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
3
  import { addons } from "storybook/preview-api";
4
4
  import { axes, css, cssVarPrefix, defaultTheme, themes, themesResolved } from "virtual:swatchbook/tokens";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -178,6 +178,83 @@ function stringifyFallback(value, fallback) {
178
178
  return fallback;
179
179
  }
180
180
  //#endregion
181
+ //#region src/internal/channel-globals.ts
182
+ const AXES_GLOBAL_KEY = "swatchbookAxes";
183
+ const THEME_GLOBAL_KEY = "swatchbookTheme";
184
+ const COLOR_FORMAT_GLOBAL_KEY = "swatchbookColorFormat";
185
+ let snapshot = {
186
+ axes: null,
187
+ theme: null,
188
+ format: null
189
+ };
190
+ const listeners = /* @__PURE__ */ new Set();
191
+ let subscribed = false;
192
+ function isColorFormat(value) {
193
+ return typeof value === "string" && COLOR_FORMATS.includes(value);
194
+ }
195
+ function ensureSubscribed() {
196
+ if (subscribed || typeof window === "undefined") return;
197
+ subscribed = true;
198
+ const channel = addons.getChannel();
199
+ const onGlobals = (payload) => {
200
+ const globals = payload.globals;
201
+ if (!globals) return;
202
+ let next = snapshot;
203
+ const nextAxes = globals[AXES_GLOBAL_KEY];
204
+ if (nextAxes && typeof nextAxes === "object") next = {
205
+ ...next,
206
+ axes: nextAxes
207
+ };
208
+ const nextTheme = globals[THEME_GLOBAL_KEY];
209
+ if (typeof nextTheme === "string") next = {
210
+ ...next,
211
+ theme: nextTheme
212
+ };
213
+ const nextFormat = globals[COLOR_FORMAT_GLOBAL_KEY];
214
+ if (isColorFormat(nextFormat)) next = {
215
+ ...next,
216
+ format: nextFormat
217
+ };
218
+ if (next !== snapshot) {
219
+ snapshot = next;
220
+ for (const cb of listeners) cb();
221
+ }
222
+ };
223
+ /**
224
+ * `setGlobals` fires once on preview init carrying the URL-persisted user
225
+ * globals (Storybook stores toolbar selections in `?globals=…`). Without
226
+ * this listener, deeplinking to an MDX page with a non-default axis tuple
227
+ * or color format renders defaults for one frame before the first
228
+ * `updateGlobals` arrives. `emitGlobals()` reads from `userGlobals.get()`
229
+ * (current state), so the payload is never stale — safe to handle.
230
+ */
231
+ channel.on("globalsUpdated", onGlobals);
232
+ channel.on("updateGlobals", onGlobals);
233
+ channel.on("setGlobals", onGlobals);
234
+ }
235
+ /**
236
+ * Subscribe at module load so the `SET_GLOBALS` emission from preview init
237
+ * lands in our snapshot before any block renders. Running `useSyncExternalStore`'s
238
+ * `subscribe` lazily on first hook call would miss the event in most cases.
239
+ */
240
+ ensureSubscribed();
241
+ function subscribe(cb) {
242
+ ensureSubscribed();
243
+ listeners.add(cb);
244
+ return () => {
245
+ listeners.delete(cb);
246
+ };
247
+ }
248
+ function getSnapshot() {
249
+ return snapshot;
250
+ }
251
+ function getServerSnapshot() {
252
+ return snapshot;
253
+ }
254
+ function useChannelGlobals() {
255
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
256
+ }
257
+ //#endregion
181
258
  //#region src/contexts.ts
182
259
  /**
183
260
  * Context carrying the full {@link ProjectSnapshot}. `null` sentinel lets
@@ -222,15 +299,15 @@ function useActiveAxes() {
222
299
  * is safe to call from MDX doc blocks (where the preview-hooks context
223
300
  * isn't available).
224
301
  */
225
- const ColorFormatContext = createContext("hex");
302
+ const ColorFormatContext = createContext(null);
226
303
  function useColorFormat() {
227
- return useContext(ColorFormatContext);
304
+ const contextValue = useContext(ColorFormatContext);
305
+ const channelGlobals = useChannelGlobals();
306
+ return contextValue ?? channelGlobals.format ?? "hex";
228
307
  }
229
308
  //#endregion
230
309
  //#region src/internal/use-project.ts
231
310
  const STYLE_ELEMENT_ID = "swatchbook-tokens";
232
- const GLOBAL_KEY = "swatchbookTheme";
233
- const AXES_GLOBAL_KEY = "swatchbookAxes";
234
311
  function ensureStylesheet(css) {
235
312
  if (typeof document === "undefined") return;
236
313
  let style = document.getElementById(STYLE_ELEMENT_ID);
@@ -288,30 +365,14 @@ function useProject() {
288
365
  function useVirtualModuleFallback(enabled) {
289
366
  const contextTheme = useActiveTheme();
290
367
  const contextAxes = useActiveAxes();
291
- const [channelTheme, setChannelTheme] = useState(null);
292
- const [channelAxes, setChannelAxes] = useState(null);
368
+ const channelGlobals = useChannelGlobals();
293
369
  useEffect(() => {
294
370
  if (!enabled) return;
295
371
  ensureStylesheet(css);
296
372
  }, [enabled]);
297
- useEffect(() => {
298
- if (!enabled) return;
299
- const channel = addons.getChannel();
300
- const onGlobals = (payload) => {
301
- const nextTheme = payload.globals?.[GLOBAL_KEY];
302
- if (typeof nextTheme === "string") setChannelTheme(nextTheme);
303
- const nextAxes = payload.globals?.[AXES_GLOBAL_KEY];
304
- if (nextAxes && typeof nextAxes === "object") setChannelAxes(nextAxes);
305
- };
306
- channel.on("globalsUpdated", onGlobals);
307
- channel.on("updateGlobals", onGlobals);
308
- return () => {
309
- channel.off("globalsUpdated", onGlobals);
310
- channel.off("updateGlobals", onGlobals);
311
- };
312
- }, [enabled]);
313
- const activeAxes = Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelAxes ?? defaultTuple(axes);
373
+ const activeAxes = Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelGlobals.axes ?? defaultTuple(axes);
314
374
  const derivedName = nameForTuple(themes, activeAxes);
375
+ const channelTheme = channelGlobals.theme;
315
376
  const fallbackTupleName = channelTheme && tupleForName(themes, channelTheme) ? channelTheme : null;
316
377
  const activeTheme = contextTheme || derivedName || fallbackTupleName || channelTheme || defaultTheme || themes[0]?.name || "";
317
378
  return {