PageSourceSearch

https://www.hooforia.com/content/themes/jahr-media/js/theme-toggle.js?ver=1788859743

js hooforia.com collected 2026-09-25 05:54:52 UTC 4,490 bytes, 148 lines download raw bytes

1/**
2 * Theme toggle: the light/dark control in the header bar and the mobile sheet.
3 *
4 * The scheme is already correct before this file runs — the default comes from
5 * the `prefers-color-scheme` media query in the stylesheet, and a stored choice
6 * was stamped onto the html element by the inline script in inc/dark-mode.php,
7 * ahead of the first paint. What is left for this file is the part that cannot
8 * be expressed in CSS: the click, the accessible state, and the browser chrome
9 * colour.
10 *
11 * WHY A MATCHING CHOICE IS DELETED RATHER THAN STORED. Choosing the scheme the
12 * system is already on removes the override instead of writing it. Otherwise
13 * every reader who once pressed the button would be pinned to that scheme for
14 * good, and a phone that turns dark at sunset would stop being followed — the
15 * button would silently have become "never change again". Deleting it puts the
16 * page back on the system, which is the state it started in.
17 */
18( function () {
19	'use strict';
20
21	var KEY = window.jmThemeStorageKey || 'jm-theme';
22	var root = document.documentElement;
23	var query = window.matchMedia ? window.matchMedia( '( prefers-color-scheme: dark )' ) : null;
24	var buttons = [].slice.call( document.querySelectorAll( '[data-jm-theme-toggle]' ) );
25
26	if ( ! buttons.length ) {
27		return;
28	}
29
30	function systemScheme() {
31		return query && query.matches ? 'dark' : 'light';
32	}
33
34	function storedScheme() {
35		try {
36			var value = window.localStorage.getItem( KEY );
37			return 'dark' === value || 'light' === value ? value : null;
38		} catch ( error ) {
39			// Safari in private mode throws on access rather than returning null.
40			return null;
41		}
42	}
43
44	function activeScheme() {
45		return storedScheme() || systemScheme();
46	}
47
48	/**
49	 * Keeps the Android status bar and the desktop title bar in step.
50	 *
51	 * Both tags are printed by inc/pwa-manifest.php with a `media` that already
52	 * matches the system. Overwriting it is how an explicit choice reaches the
53	 * browser chrome, which has no other way to hear about it.
54	 */
55	function paintChrome( isDark ) {
56		var metas = document.querySelectorAll( 'meta[name="theme-color"][data-jm-theme-color]' );
57
58		[].forEach.call( metas, function ( meta ) {
59			var wantsDark = 'dark' === meta.getAttribute( 'data-jm-theme-color' );
60			meta.setAttribute( 'media', wantsDark === isDark ? 'all' : 'not all' );
61		} );
62	}
63
64	function sync() {
65		var isDark = 'dark' === activeScheme();
66
67		buttons.forEach( function ( button ) {
68			button.setAttribute( 'aria-pressed', isDark ? 'true' : 'false' );
69		} );
70
71		paintChrome( isDark );
72	}
73
74	/**
75	 * The theme lives on tokens that a good many rules animate with the shared
76	 * `--baseTransition`, so a switch would otherwise crawl across the page one
77	 * component at a time. The class suppresses transitions for one frame; the
78	 * double rAF is what guarantees the new colours have been painted before it
79	 * comes off again.
80	 */
81	function withoutTransitions( change ) {
82		root.classList.add( 'jm-theme-switching' );
83		change();
84		window.requestAnimationFrame( function () {
85			window.requestAnimationFrame( function () {
86				root.classList.remove( 'jm-theme-switching' );
87			} );
88		} );
89	}
90
91	function apply( scheme ) {
92		withoutTransitions( function () {
93			if ( scheme === systemScheme() ) {
94				try {
95					window.localStorage.removeItem( KEY );
96				} catch ( error ) {}
97
98				root.removeAttribute( 'data-theme' );
99			} else {
100				try {
101					window.localStorage.setItem( KEY, scheme );
102				} catch ( error ) {}
103
104				root.setAttribute( 'data-theme', scheme );
105			}
106		} );
107
108		sync();
109	}
110
111	buttons.forEach( function ( button ) {
112		button.addEventListener( 'click', function () {
113			apply( 'dark' === activeScheme() ? 'light' : 'dark' );
114		} );
115	} );
116
117	// The system changed under an unpinned page: CSS has already followed, the
118	// button state and the chrome colour have not.
119	if ( query ) {
120		if ( query.addEventListener ) {
121			query.addEventListener( 'change', sync );
122		} else if ( query.addListener ) {
123			query.addListener( sync );
124		}
125	}
126
127	// Another tab switched: follow it, so the reader does not have two of our
128	// pages open in two different schemes.
129	window.addEventListener( 'storage', function ( event ) {
130		if ( event.key !== KEY ) {
131			return;
132		}
133
134		withoutTransitions( function () {
135			var scheme = storedScheme();
136
137			if ( scheme ) {
138				root.setAttribute( 'data-theme', scheme );
139			} else {
140				root.removeAttribute( 'data-theme' );
141			}
142		} );
143
144		sync();
145	} );
146
147	sync();
148}() );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.