@ou-imdt/css
Version:
The IMDT CSS library styles native elements with light, extendable CSS. It is developed for Interactive Media Developers at the Open University.
108 lines (97 loc) • 3.42 kB
JavaScript
const makeTemplate = () => {
const template = document.createElement("template");
const style = `<style defer></style>`;
const markup = `<slot></slot>`;
template.innerHTML = style + markup;
return template;
};
class Component extends HTMLElement {
static get tag() {
return "imdt-themer";
}
static get observedAttributes() {
return [
"data-active"
];
}
constructor() {
super();
this.attachShadow({ mode: "open", delegatesFocus: false });
const template = makeTemplate();
this.shadowRoot.append(template.content.cloneNode(true));
}
// lifecycle will see everytime an observed attribute changes
attributeChangedCallback(name, oldValue, newValue) {}
connectedCallback() {
this.addEventListener("click", ({ target }) => {
this.applyTheme(target.getAttribute("data-name"));
});
this.addEventListener("change", ({ target }) => {
this.applyTheme(target.getAttribute("data-name"));
});
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (e) => this.addSystemChangeEvent());
this.addSlotChangeEvents();
}
addSystemChangeEvent(event) {
const newTheme = event.matches ? "dark" : "light";
this.applyTheme(newTheme);
}
// handles aria pressed state for active theme
// this only works for buttons, will need work for other inputs
updateTargets(theme = undefined) {
const items = this.querySelectorAll("[data-name]");
items.forEach((i) => i.removeAttribute("aria-pressed"));
if (theme) {
const target = this.querySelector(`[data-name="${theme}"]`);
if (target) target.setAttribute("aria-pressed", "true");
}
}
getSystemTheme() {
const isDark = window.matchMedia("(prefers-color-scheme: dark)");
return isDark ? "dark" : "light";
}
// Gets theme saved in local storage
getLocalTheme() {
return localStorage.getItem("theme");
}
// adds theme class to body tag
// theme !== systemTheme -> set theme in local storage
// theme === systemTheme -> remove theme in local storage
setTheme(theme) {
const systemTheme = this.getSystemTheme();
const localTheme = this.getLocalTheme();
const finalTheme = theme || localTheme || systemTheme;
localStorage.setItem("theme", finalTheme);
document.body.setAttribute("data-theme", finalTheme);
if (finalTheme === systemTheme) {
localStorage.removeItem("theme");
}
return finalTheme;
}
// Method that applies theme, fallback to saved local theme, fallback to system theme
applyTheme(theme = "") {
const final = this.setTheme(theme);
this.updateTargets(final);
}
// THIS FIRES after connect event when slots are loaded, and then whenever they are updated
// custom method to show adding events to slots changing
// this only fires when element with 'slot' attrib changes, not it's children
// use .assignedNodes() to access a slots children
// could possibly use mutation observer to look for child changes
addSlotChangeEvents() {
const slots = Array.from(this.shadowRoot.querySelectorAll("slot"));
slots.map((i) => {
i.addEventListener("slotchange", (e) => {
console.log("slot");
this.setTheme();
});
return true;
});
}
}
// register component
if (!customElements.get(Component.tag)) {
customElements.define(Component.tag, Component);
}