Skip to content

Commit

Permalink
feat: ability to use manual script injection in hooks (#89)
Browse files Browse the repository at this point in the history
Co-authored-by: Hunter Johnston <[email protected]>
Co-authored-by: Hunter Johnston <[email protected]>
  • Loading branch information
3 people authored Nov 19, 2024
1 parent 7f6fa59 commit 271a593
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-seas-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"mode-watcher": minor
---

feat: add ability to disable head script injection via the `disableHeadScriptInjection` prop for handling in hooks.server files
2 changes: 2 additions & 0 deletions packages/mode-watcher/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
generateSetInitialModeExpression,
mode,
modeStorageKey,
resetMode,
Expand All @@ -12,6 +13,7 @@ import {
} from "./mode.js";

export {
generateSetInitialModeExpression,
setMode,
toggleMode,
resetMode,
Expand Down
23 changes: 23 additions & 0 deletions packages/mode-watcher/src/lib/mode-watcher-full.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { setInitialMode } from "./mode.js";
import type { ThemeColors } from "./types.js";
export let trueNonce: string = "";
export let initConfig: Parameters<typeof setInitialMode>[0];
export let themeColors: ThemeColors = undefined;
</script>

<svelte:head>
{#if themeColors}
<!-- default to dark mode for to allow testing -->
<!-- this will be overwritten by FOUC prevention snippet below -->
<!-- but that snippet does not run in vitest -->
<meta name="theme-color" content={themeColors.dark} />
{/if}
<!-- eslint-disable-next-line svelte/no-at-html-tags, prefer-template -->
{@html `<script${trueNonce ? ` nonce=${trueNonce}` : ""}>(` +
setInitialMode.toString() +
`)(` +
JSON.stringify(initConfig) +
`);</script>`}
</svelte:head>
12 changes: 12 additions & 0 deletions packages/mode-watcher/src/lib/mode-watcher-lite.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import type { ThemeColors } from "./types.js";
export let themeColors: ThemeColors = undefined;
</script>

{#if themeColors}
<!-- default to dark mode for to allow testing -->
<!-- this will be overwritten by FOUC prevention snippet below -->
<!-- but that snippet does not run in vitest -->
<meta name="theme-color" content={themeColors.dark} />
{/if}
24 changes: 8 additions & 16 deletions packages/mode-watcher/src/lib/mode-watcher.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
defineConfig,
disableTransitions as disableTransitionsStore,
mode,
setInitialMode,
setMode,
setTheme,
systemPrefersMode,
Expand All @@ -20,6 +19,8 @@
modeStorageKey as modeStorageKeyStore,
themeStorageKey as themeStorageKeyStore,
} from "./stores.js";
import ModeWatcherLite from "./mode-watcher-lite.svelte";
import ModeWatcherFull from "./mode-watcher-full.svelte";
type $$Props = ModeWatcherProps;
Expand All @@ -33,6 +34,7 @@
export let nonce: string = "";
export let themeStorageKey: string = "mode-watcher-theme";
export let modeStorageKey: string = "mode-watcher-mode";
export let disableHeadScriptInjection = false;
$: disableTransitionsStore.set(disableTransitions);
$: themeColorsStore.set(themeColors);
Expand Down Expand Up @@ -70,18 +72,8 @@
$: trueNonce = typeof window === "undefined" ? nonce : "";
</script>

<svelte:head>
{#if themeColors}
<!-- default to dark mode for to allow testing -->
<!-- this will be overwritten by FOUC prevention snippet below -->
<!-- but that snippet does not run in vitest -->
<meta name="theme-color" content={themeColors.dark} />
{/if}

<!-- eslint-disable-next-line svelte/no-at-html-tags, prefer-template -->
{@html `<script${trueNonce ? ` nonce=${trueNonce}` : ""}>(` +
setInitialMode.toString() +
`)(` +
JSON.stringify(initConfig) +
`);</script>`}
</svelte:head>
{#if disableHeadScriptInjection}
<ModeWatcherLite {themeColors} />
{:else}
<ModeWatcherFull {trueNonce} {initConfig} {themeColors} />
{/if}
25 changes: 16 additions & 9 deletions packages/mode-watcher/src/lib/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,24 @@ export function defineConfig(config: SetInitialModeArgs) {
}

type SetInitialModeArgs = {
defaultMode: Mode;
defaultMode?: Mode;
themeColors?: ThemeColors;
darkClassNames: string[];
lightClassNames: string[];
defaultTheme: string;
modeStorageKey: string;
themeStorageKey: string;
darkClassNames?: string[];
lightClassNames?: string[];
defaultTheme?: string;
modeStorageKey?: string;
themeStorageKey?: string;
};

/** Used to set the mode on initial page load to prevent FOUC */
export function setInitialMode({
defaultMode,
defaultMode = "system",
themeColors,
darkClassNames = ["dark"],
lightClassNames = [],
defaultTheme = "",
modeStorageKey,
themeStorageKey,
modeStorageKey = "mode-watcher-mode",
themeStorageKey = "mode-watcher-theme",
}: SetInitialModeArgs) {
const rootEl = document.documentElement;
const mode = localStorage.getItem(modeStorageKey) || defaultMode;
Expand Down Expand Up @@ -89,6 +89,13 @@ export function setInitialMode({
localStorage.setItem(modeStorageKey, mode);
}

/**
* A type-safe way to generate the source expression used to set the initial mode and avoid FOUC.
*/
export function generateSetInitialModeExpression(config: SetInitialModeArgs = {}): string {
return `(${setInitialMode.toString()})(${JSON.stringify(config)});`;
}

export {
modeStorageKey,
themeStorageKey,
Expand Down
8 changes: 8 additions & 0 deletions packages/mode-watcher/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,12 @@ export type ModeWatcherProps = {
* @defaultValue `undefined`
*/
nonce?: string;

/**
* Whether to disable the injected script tag that sets the initial mode.
* Set this if you are manually injecting the script using a hook.
*
* @defaultValue `false`
*/
disableHeadScriptInjection?: boolean;
};
8 changes: 8 additions & 0 deletions sites/docs/content/api-reference/mode-watcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,13 @@ export type ModeWatcherProps = {
* @defaultValue `undefined`
*/
nonce?: string;

/**
* Whether to disable the injected script tag that sets the initial mode.
* Set this if you are manually injecting the script using a hook.
*
* @defaultValue `false`
*/
disableHeadScriptInjection?: boolean;
};
````

0 comments on commit 271a593

Please sign in to comment.