2025-04-14 12:52:00 -04:00
|
|
|
import { domChange } from './util/dom-change.js';
|
|
|
|
|
export { domChange };
|
2025-01-10 15:32:28 -05:00
|
|
|
|
2025-01-13 13:14:12 -05:00
|
|
|
export function nextFrame() {
|
2025-01-10 15:32:28 -05:00
|
|
|
return new Promise(resolve => requestAnimationFrame(resolve));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ThemeAspect {
|
|
|
|
|
constructor(options) {
|
|
|
|
|
Object.assign(this, options);
|
|
|
|
|
this.set();
|
|
|
|
|
|
|
|
|
|
// Update when local storage changes.
|
|
|
|
|
// That way changes in one window will propagate to others (including iframes).
|
|
|
|
|
window.addEventListener('storage', event => {
|
|
|
|
|
if (event.key === this.key) {
|
|
|
|
|
this.set();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Listen for selections
|
2025-01-23 17:52:41 -05:00
|
|
|
document.addEventListener('change', event => {
|
2025-01-10 15:32:28 -05:00
|
|
|
const picker = event.target.closest(this.picker);
|
|
|
|
|
if (picker) {
|
|
|
|
|
this.set(picker.value);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-01-15 15:12:32 -05:00
|
|
|
|
|
|
|
|
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
|
|
|
|
|
document.addEventListener(eventName, e => {
|
|
|
|
|
const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream;
|
|
|
|
|
if (newElement) {
|
|
|
|
|
this.syncUI(newElement);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-01-10 15:32:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get() {
|
|
|
|
|
return localStorage.getItem(this.key) ?? this.defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
computed = {};
|
|
|
|
|
|
|
|
|
|
get computedValue() {
|
|
|
|
|
if (this.value in this.computed) {
|
|
|
|
|
return this.computed[this.value];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set(value = this.get()) {
|
|
|
|
|
if (value === this.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.value = value;
|
|
|
|
|
|
|
|
|
|
if (this.value === this.defaultValue) {
|
|
|
|
|
localStorage.removeItem(this.key);
|
|
|
|
|
} else {
|
|
|
|
|
localStorage.setItem(this.key, this.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.applyChange();
|
|
|
|
|
this.syncUI();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
syncUI(container = document) {
|
|
|
|
|
for (let picker of container.querySelectorAll(this.picker)) {
|
|
|
|
|
picker.setAttribute('value', this.value);
|
|
|
|
|
picker.value = this.value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const colorScheme = new ThemeAspect({
|
|
|
|
|
defaultValue: 'auto',
|
|
|
|
|
key: 'colorScheme',
|
|
|
|
|
picker: 'wa-select.color-scheme-selector',
|
|
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
|
get auto() {
|
|
|
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
applyChange() {
|
|
|
|
|
// Toggle the dark mode class
|
|
|
|
|
domChange(() => {
|
|
|
|
|
let dark = this.computedValue === 'dark';
|
|
|
|
|
document.documentElement.classList.toggle(`wa-dark`, dark);
|
2025-03-26 17:11:28 -04:00
|
|
|
document.documentElement.dispatchEvent(new CustomEvent('wa-color-scheme-change', { detail: { dark } }));
|
2025-05-27 16:33:18 -04:00
|
|
|
syncViewportDemoColorSchemes();
|
2025-01-10 15:32:28 -05:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-27 16:33:18 -04:00
|
|
|
function syncViewportDemoColorSchemes() {
|
|
|
|
|
const isDark = document.documentElement.classList.contains('wa-dark');
|
|
|
|
|
|
|
|
|
|
// Update viewport demo color schemes in code examples
|
|
|
|
|
document.querySelectorAll('.code-example.is-viewport-demo wa-viewport-demo').forEach(demo => {
|
|
|
|
|
demo.querySelectorAll('iframe').forEach(iframe => {
|
|
|
|
|
iframe.contentWindow.document.documentElement?.classList?.toggle('wa-dark', isDark);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 15:32:28 -05:00
|
|
|
// Update the color scheme when the preference changes
|
|
|
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => colorScheme.set());
|
|
|
|
|
|
|
|
|
|
// Toggle color scheme with backslash
|
|
|
|
|
document.addEventListener('keydown', event => {
|
|
|
|
|
if (
|
|
|
|
|
event.key === '\\' &&
|
|
|
|
|
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
|
|
|
|
) {
|
|
|
|
|
event.preventDefault();
|
2025-05-01 16:17:27 -04:00
|
|
|
colorScheme.set(colorScheme.get() === 'dark' ? 'light' : 'dark');
|
2025-01-10 15:32:28 -05:00
|
|
|
}
|
|
|
|
|
});
|
2025-05-27 16:33:18 -04:00
|
|
|
|
|
|
|
|
// When rendering a code example with a viewport demo, set the theme to match initially
|
|
|
|
|
document.querySelectorAll('.code-example.is-viewport-demo wa-viewport-demo iframe').forEach(iframe => {
|
|
|
|
|
const isDark = document.documentElement.classList.contains('wa-dark');
|
|
|
|
|
|
|
|
|
|
iframe.addEventListener('load', () => {
|
|
|
|
|
iframe.contentWindow.document.documentElement?.classList?.toggle('wa-dark', isDark);
|
|
|
|
|
});
|
|
|
|
|
});
|