New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mode-watcher

Package Overview
Dependencies
Maintainers
0
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mode-watcher - npm Package Compare versions

Comparing version 0.3.1 to 0.4.0

dist/utils.d.ts

6

dist/index.d.ts

@@ -1,3 +0,3 @@

import { localStorageKey, userPrefersMode, systemPrefersMode, mode, setMode, toggleMode, resetMode } from './mode.js';
export { setMode, toggleMode, resetMode, localStorageKey, userPrefersMode, systemPrefersMode, mode, };
export { default as ModeWatcher } from './mode-watcher.svelte';
import { mode, modeStorageKey, resetMode, setMode, setTheme, systemPrefersMode, theme, themeStorageKey, toggleMode, userPrefersMode } from "./mode.js";
export { setMode, toggleMode, resetMode, modeStorageKey, userPrefersMode, systemPrefersMode, mode, theme, setTheme, themeStorageKey, };
export { default as ModeWatcher } from "./mode-watcher.svelte";

@@ -1,3 +0,3 @@

import { localStorageKey, userPrefersMode, systemPrefersMode, mode, setMode, toggleMode, resetMode, } from './mode.js';
export { setMode, toggleMode, resetMode, localStorageKey, userPrefersMode, systemPrefersMode, mode, };
export { default as ModeWatcher } from './mode-watcher.svelte';
import { mode, modeStorageKey, resetMode, setMode, setTheme, systemPrefersMode, theme, themeStorageKey, toggleMode, userPrefersMode, } from "./mode.js";
export { setMode, toggleMode, resetMode, modeStorageKey, userPrefersMode, systemPrefersMode, mode, theme, setTheme, themeStorageKey, };
export { default as ModeWatcher } from "./mode-watcher.svelte";
import { SvelteComponent } from "svelte";
import type { ModeWatcherProps } from './types.js';
import type { ModeWatcherProps } from "./types.js";
declare const __propDef: {

@@ -9,2 +9,4 @@ props: ModeWatcherProps;

slots: {};
exports?: {} | undefined;
bindings?: string | undefined;
};

@@ -11,0 +13,0 @@ type ModeWatcherProps_ = typeof __propDef.props;

@@ -1,3 +0,3 @@

import { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode, themeColors, disableTransitions } from './stores.js';
import type { Mode, ThemeColors } from './types.js';
import { derivedMode, derivedTheme, disableTransitions, modeStorageKey, systemPrefersMode, themeColors, themeStorageKey, userPrefersMode } from "./stores.js";
import type { Mode, ThemeColors } from "./types.js";
/** Toggle between light and dark mode */

@@ -9,4 +9,16 @@ export declare function toggleMode(): void;

export declare function resetMode(): void;
/** Set the theme to a custom value */
export declare function setTheme(theme: string): void;
export declare function defineConfig(config: SetInitialModeArgs): SetInitialModeArgs;
type SetInitialModeArgs = {
defaultMode: Mode;
themeColors?: ThemeColors;
darkClassNames: string[];
lightClassNames: string[];
defaultTheme: string;
modeStorageKey: string;
themeStorageKey: string;
};
/** Used to set the mode on initial page load to prevent FOUC */
export declare function setInitialMode(defaultMode: Mode, themeColors?: ThemeColors): void;
export { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode as mode, themeColors, disableTransitions, };
export declare function setInitialMode({ defaultMode, themeColors, darkClassNames, lightClassNames, defaultTheme, }: SetInitialModeArgs): void;
export { modeStorageKey, themeStorageKey, derivedTheme as theme, userPrefersMode, systemPrefersMode, derivedMode as mode, themeColors, disableTransitions, };

@@ -1,6 +0,6 @@

import { get } from 'svelte/store';
import { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode, themeColors, disableTransitions, } from './stores.js';
import { get } from "svelte/store";
import { derivedMode, derivedTheme, disableTransitions, modeStorageKey, systemPrefersMode, themeColors, themeStorageKey, theme as themeStore, userPrefersMode, } from "./stores.js";
/** Toggle between light and dark mode */
export function toggleMode() {
userPrefersMode.set(get(derivedMode) === 'dark' ? 'light' : 'dark');
userPrefersMode.set(get(derivedMode) === "dark" ? "light" : "dark");
}

@@ -13,20 +13,43 @@ /** Set the mode to light or dark */

export function resetMode() {
userPrefersMode.set('system');
userPrefersMode.set("system");
}
/** Set the theme to a custom value */
export function setTheme(theme) {
themeStore.set(theme);
}
export function defineConfig(config) {
return config;
}
/** Used to set the mode on initial page load to prevent FOUC */
export function setInitialMode(defaultMode, themeColors) {
export function setInitialMode({ defaultMode, themeColors, darkClassNames = ["dark"], lightClassNames = [], defaultTheme = "", }) {
const rootEl = document.documentElement;
const mode = localStorage.getItem('mode-watcher-mode') || defaultMode;
const light = mode === 'light' ||
(mode === 'system' && window.matchMedia('(prefers-color-scheme: light)').matches);
rootEl.classList[light ? 'remove' : 'add']('dark');
rootEl.style.colorScheme = light ? 'light' : 'dark';
const mode = localStorage.getItem("mode-watcher-mode") || defaultMode;
const theme = localStorage.getItem("mode-watcher-theme") || defaultTheme;
const light = mode === "light" ||
(mode === "system" && window.matchMedia("(prefers-color-scheme: light)").matches);
if (light) {
if (darkClassNames.length)
rootEl.classList.remove(...darkClassNames);
if (lightClassNames.length)
rootEl.classList.add(...lightClassNames);
}
else {
if (lightClassNames.length)
rootEl.classList.remove(...lightClassNames);
if (darkClassNames.length)
rootEl.classList.add(...darkClassNames);
}
rootEl.style.colorScheme = light ? "light" : "dark";
if (themeColors) {
const themeMetaEl = document.querySelector('meta[name="theme-color"]');
if (themeMetaEl) {
themeMetaEl.setAttribute('content', mode === 'light' ? themeColors.light : themeColors.dark);
themeMetaEl.setAttribute("content", mode === "light" ? themeColors.light : themeColors.dark);
}
}
localStorage.setItem('mode-watcher-mode', mode);
if (theme) {
rootEl.setAttribute("data-theme", theme);
localStorage.setItem("mode-watcher-theme", theme);
}
localStorage.setItem("mode-watcher-mode", mode);
}
export { localStorageKey, userPrefersMode, systemPrefersMode, derivedMode as mode, themeColors, disableTransitions, };
export { modeStorageKey, themeStorageKey, derivedTheme as theme, userPrefersMode, systemPrefersMode, derivedMode as mode, themeColors, disableTransitions, };

@@ -1,9 +0,13 @@

/// <reference types="svelte" />
import type { Mode, ThemeColors } from './types.js';
import type { Mode, ThemeColors } from "./types.js";
/** the modes that are supported, used for validation & type derivation */
export declare const modes: readonly ["dark", "light", "system"];
/**
* The key used to store the mode in localStorage.
* The key used to store the `mode` in localStorage.
*/
export declare const localStorageKey = "mode-watcher-mode";
export declare const modeStorageKey: import("svelte/store").Writable<string>;
/**
* The key used to store the `theme` in localStorage.
*/
export declare const themeStorageKey: import("svelte/store").Writable<string>;
/**
* Writable store that represents the user's preferred mode (`"dark"`, `"light"` or `"system"`)

@@ -28,2 +32,9 @@ */

/**
* A custom theme to apply and persist to the root `html` element.
*/
export declare const theme: {
subscribe: (this: void, run: import("svelte/store").Subscriber<string>, invalidate?: import("svelte/store").Invalidator<string> | undefined) => import("svelte/store").Unsubscriber;
set: (v: string) => void;
};
/**
* Whether to disable transitions when changing the mode.

@@ -33,2 +44,10 @@ */

/**
* The classnames to add to the root `html` element when the mode is dark.
*/
export declare const darkClassNames: import("svelte/store").Writable<string[]>;
/**
* The classnames to add to the root `html` element when the mode is light.
*/
export declare const lightClassNames: import("svelte/store").Writable<string[]>;
/**
* Derived store that represents the current mode (`"dark"`, `"light"` or `undefined`)

@@ -39,2 +58,8 @@ */

};
/**
* Derived store that represents the current custom theme
*/
export declare const derivedTheme: {
subscribe: (this: void, run: import("svelte/store").Subscriber<string | undefined>, invalidate?: import("svelte/store").Invalidator<string | undefined> | undefined) => import("svelte/store").Unsubscriber;
};
export declare function isValidMode(value: unknown): value is Mode;

@@ -1,19 +0,22 @@

import { writable, derived } from 'svelte/store';
import { withoutTransition } from './without-transition.js';
import { derived, get, writable } from "svelte/store";
import { withoutTransition } from "./without-transition.js";
import { sanitizeClassNames } from "./utils.js";
// saves having to branch for server vs client
const noopStorage = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getItem: (_key) => null,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setItem: (_key, _value) => { },
};
// whether we are running on server vs client
const isBrowser = typeof document !== 'undefined';
// the modes that are supported, used for validation & type derivation
export const modes = ['dark', 'light', 'system'];
const isBrowser = typeof document !== "undefined";
/** the modes that are supported, used for validation & type derivation */
export const modes = ["dark", "light", "system"];
/**
* The key used to store the mode in localStorage.
* The key used to store the `mode` in localStorage.
*/
export const localStorageKey = 'mode-watcher-mode';
export const modeStorageKey = writable("mode-watcher-mode");
/**
* The key used to store the `theme` in localStorage.
*/
export const themeStorageKey = writable("mode-watcher-theme");
/**
* Writable store that represents the user's preferred mode (`"dark"`, `"light"` or `"system"`)

@@ -31,2 +34,6 @@ */

/**
* A custom theme to apply and persist to the root `html` element.
*/
export const theme = createCustomTheme();
/**
* Whether to disable transitions when changing the mode.

@@ -36,11 +43,26 @@ */

/**
* The classnames to add to the root `html` element when the mode is dark.
*/
export const darkClassNames = writable([]);
/**
* The classnames to add to the root `html` element when the mode is light.
*/
export const lightClassNames = writable([]);
/**
* Derived store that represents the current mode (`"dark"`, `"light"` or `undefined`)
*/
export const derivedMode = createDerivedMode();
/**
* Derived store that represents the current custom theme
*/
export const derivedTheme = createDerivedTheme();
// derived from: https://github.com/CaptainCodeman/svelte-web-storage
function createUserPrefersMode() {
const defaultValue = 'system';
const defaultValue = "system";
const storage = isBrowser ? localStorage : noopStorage;
const initialValue = storage.getItem(localStorageKey);
const initialValue = storage.getItem(getModeStorageKey());
let value = isValidMode(initialValue) ? initialValue : defaultValue;
function getModeStorageKey() {
return get(modeStorageKey);
}
const { subscribe, set: _set } = writable(value, () => {

@@ -50,3 +72,3 @@ if (!isBrowser)

const handler = (e) => {
if (e.key !== localStorageKey)
if (e.key !== getModeStorageKey())
return;

@@ -61,8 +83,8 @@ const newValue = e.newValue;

};
addEventListener('storage', handler);
return () => removeEventListener('storage', handler);
addEventListener("storage", handler);
return () => removeEventListener("storage", handler);
});
function set(v) {
_set((value = v));
storage.setItem(localStorageKey, value);
storage.setItem(getModeStorageKey(), value);
}

@@ -74,2 +96,35 @@ return {

}
function createCustomTheme() {
const storage = isBrowser ? localStorage : noopStorage;
const initialValue = storage.getItem(getThemeStorageKey());
let value = initialValue === null || initialValue === undefined ? "" : initialValue;
function getThemeStorageKey() {
return get(themeStorageKey);
}
const { subscribe, set: _set } = writable(value, () => {
if (!isBrowser)
return;
const handler = (e) => {
if (e.key !== getThemeStorageKey())
return;
const newValue = e.newValue;
if (newValue === null) {
_set((value = ""));
}
else {
_set((value = newValue));
}
};
addEventListener("storage", handler);
return () => removeEventListener("storage", handler);
});
function set(v) {
_set((value = v));
storage.setItem(getThemeStorageKey(), value);
}
return {
subscribe,
set,
};
}
function createSystemMode() {

@@ -84,7 +139,7 @@ const defaultValue = undefined;

return;
set(e.matches ? 'light' : 'dark');
set(e.matches ? "light" : "dark");
};
const mediaQueryState = window.matchMedia('(prefers-color-scheme: light)');
mediaQueryState.addEventListener('change', handler);
return () => mediaQueryState.removeEventListener('change', handler);
const mediaQueryState = window.matchMedia("(prefers-color-scheme: light)");
mediaQueryState.addEventListener("change", handler);
return () => mediaQueryState.removeEventListener("change", handler);
});

@@ -97,4 +152,4 @@ /**

return;
const mediaQueryState = window.matchMedia('(prefers-color-scheme: light)');
set(mediaQueryState.matches ? 'light' : 'dark');
const mediaQueryState = window.matchMedia("(prefers-color-scheme: light)");
set(mediaQueryState.matches ? "light" : "dark");
}

@@ -114,21 +169,36 @@ /**

function createDerivedMode() {
const { subscribe } = derived([userPrefersMode, systemPrefersMode, themeColors, disableTransitions], ([$userPrefersMode, $systemPrefersMode, $themeColors, $disableTransitions]) => {
const { subscribe } = derived([
userPrefersMode,
systemPrefersMode,
themeColors,
disableTransitions,
darkClassNames,
lightClassNames,
], ([$userPrefersMode, $systemPrefersMode, $themeColors, $disableTransitions, $darkClassNames, $lightClassNames,]) => {
if (!isBrowser)
return undefined;
const derivedMode = $userPrefersMode === 'system' ? $systemPrefersMode : $userPrefersMode;
const derivedMode = $userPrefersMode === "system" ? $systemPrefersMode : $userPrefersMode;
const sanitizedDarkClassNames = sanitizeClassNames($darkClassNames);
const sanitizedLightClassNames = sanitizeClassNames($lightClassNames);
function update() {
const htmlEl = document.documentElement;
const themeColorEl = document.querySelector('meta[name="theme-color"]');
if (derivedMode === 'light') {
htmlEl.classList.remove('dark');
htmlEl.style.colorScheme = 'light';
if (derivedMode === "light") {
if (sanitizedDarkClassNames.length)
htmlEl.classList.remove(...sanitizedDarkClassNames);
if (sanitizedLightClassNames.length)
htmlEl.classList.add(...sanitizedLightClassNames);
htmlEl.style.colorScheme = "light";
if (themeColorEl && $themeColors) {
themeColorEl.setAttribute('content', $themeColors.light);
themeColorEl.setAttribute("content", $themeColors.light);
}
}
else {
htmlEl.classList.add('dark');
htmlEl.style.colorScheme = 'dark';
if (sanitizedLightClassNames.length)
htmlEl.classList.remove(...sanitizedLightClassNames);
if (sanitizedDarkClassNames.length)
htmlEl.classList.add(...sanitizedDarkClassNames);
htmlEl.style.colorScheme = "dark";
if (themeColorEl && $themeColors) {
themeColorEl.setAttribute('content', $themeColors.dark);
themeColorEl.setAttribute("content", $themeColors.dark);
}

@@ -149,6 +219,26 @@ }

}
function createDerivedTheme() {
const { subscribe } = derived([theme, disableTransitions], ([$theme, $disableTransitions]) => {
if (!isBrowser)
return undefined;
function update() {
const htmlEl = document.documentElement;
htmlEl.setAttribute("data-theme", $theme);
}
if ($disableTransitions) {
withoutTransition(update);
}
else {
update();
}
return $theme;
});
return {
subscribe,
};
}
export function isValidMode(value) {
if (typeof value !== 'string')
if (typeof value !== "string")
return false;
return modes.includes(value);
}

@@ -1,2 +0,2 @@

import type { modes } from './stores.js';
import type { modes } from "./stores.js";
export type Mode = (typeof modes)[number];

@@ -22,2 +22,14 @@ export type ThemeColors = {

/**
* The default theme to use, which will be applied to the root `html` element
* and can be managed with the `setTheme` function.
*
* @example
* ```html
* <html data-theme="your-custom-theme"></html>
* ```
*
* @defaultValue `undefined`
*/
defaultTheme?: string;
/**
* The theme colors to use for each mode.

@@ -30,2 +42,33 @@ */

disableTransitions?: boolean;
/**
* The classname to add to the root `html` element when the mode is dark.
*
* @defaultValue `["dark"]`
*/
darkClassNames?: string[];
/**
* The classname to add to the root `html` element when the mode is light.
*
* @defaultValue `[]`
*/
lightClassNames?: string[];
/**
* Optionally provide a custom local storage key to use for storing the mode.
*
* @defaultValue `'mode-watcher-mode'`
*/
modeStorageKey?: string;
/**
* Optionally provide a custom local storage key to use for storing the theme.
*
* @defaultValue `'mode-watcher-theme'`
*/
themeStorageKey?: string;
/**
* An optional nonce to use for the injected script tag to allow-list mode-watcher
* if you are using a Content Security Policy.
*
* @defaultValue `undefined`
*/
nonce?: string;
};

@@ -5,5 +5,5 @@ // Original Source: https://reemus.dev/article/disable-css-transition-color-scheme-change#heading-ultimate-solution-for-changing-color-scheme-without-transitions

// Perform a task without any css transitions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line ts/no-explicit-any
export function withoutTransition(action) {
if (typeof document === 'undefined')
if (typeof document === "undefined")
return;

@@ -14,3 +14,3 @@ // Clear fallback timeouts

// Create style element to disable transitions
const style = document.createElement('style');
const style = document.createElement("style");
const css = document.createTextNode(`* {

@@ -28,5 +28,6 @@ -webkit-transition: none !important;

// Best method, getComputedStyle forces browser to repaint
if (typeof window.getComputedStyle !== 'undefined') {
if (typeof window.getComputedStyle !== "undefined") {
disable();
action();
// eslint-disable-next-line ts/no-unused-expressions -- this is a side effect
window.getComputedStyle(style).opacity;

@@ -37,3 +38,3 @@ enable();

// Better method, requestAnimationFrame processes function before next repaint
if (typeof window.requestAnimationFrame !== 'undefined') {
if (typeof window.requestAnimationFrame !== "undefined") {
disable();

@@ -40,0 +41,0 @@ action();

{
"name": "mode-watcher",
"version": "0.3.1",
"version": "0.4.0",
"description": "SSR-friendly light and dark mode for SvelteKit",

@@ -32,17 +32,11 @@ "license": "MIT",

"@svitejs/changesets-changelog-github-compact": "^1.1.0",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/svelte": "^4.0.4",
"@testing-library/dom": "^10.3.1",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/svelte": "^5.2.0",
"@testing-library/user-event": "^14.5.1",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@types/node": "^20.14.10",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"jsdom": "^22.1.0",
"jsdom": "^24.1.0",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"publint": "^0.1.9",

@@ -55,3 +49,4 @@ "svelte": "^4.0.5",

"vite": "^5.0.0",
"vitest": "^1.0.0"
"vitest": "^1.0.0",
"vitest-localstorage-mock": "^0.1.2"
},

@@ -69,3 +64,3 @@ "svelte": "./dist/index.js",

"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"lint": "eslint .",
"format": "prettier --plugin-search-dir . --write .",

@@ -72,0 +67,0 @@ "watch": "svelte-kit sync && svelte-package --watch"

@@ -25,3 +25,3 @@ # Mode Watcher

<script lang="ts">
import { ModeWatcher } from 'mode-watcher';
import { ModeWatcher } from "mode-watcher";
</script>

@@ -65,3 +65,3 @@

<script lang="ts">
import { toggleMode } from 'mode-watcher';
import { toggleMode } from "mode-watcher";
</script>

@@ -78,7 +78,7 @@

<script lang="ts">
import { setMode } from 'mode-watcher';
import { setMode } from "mode-watcher";
</script>
<button on:click={() => setMode('light')}>Set Light Mode</button>
<button on:click={() => setMode('dark')}>Set Dark Mode</button>
<button on:click={() => setMode("light")}>Set Light Mode</button>
<button on:click={() => setMode("dark")}>Set Dark Mode</button>
```

@@ -92,3 +92,3 @@

<script lang="ts">
import { resetMode } from 'mode-watcher';
import { resetMode } from "mode-watcher";
</script>

@@ -105,9 +105,9 @@

<script lang="ts">
import { setMode, mode } from 'mode-watcher';
import { setMode, mode } from "mode-watcher";
function handleModeChange() {
if ($mode === 'light') {
setMode('dark');
if ($mode === "light") {
setMode("dark");
} else {
setMode('light');
setMode("light");
}

@@ -114,0 +114,0 @@ }

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc