73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { createAccentVariables, type RgbTuple } from "./accent.js";
|
||
|
||
export type NodedcTheme = "dark" | "light";
|
||
|
||
export interface NodedcMaterial {
|
||
/** Base fill used by glass panels, application windows and modals. */
|
||
panel?: RgbTuple;
|
||
/** 0–1 alpha for the base glass panel. */
|
||
panelOpacity?: number;
|
||
/** Base fill used by text, range and selection fields. */
|
||
field?: RgbTuple;
|
||
/** 0–1 alpha for field fill. */
|
||
fieldOpacity?: number;
|
||
}
|
||
|
||
export interface ApplyThemeOptions {
|
||
theme?: NodedcTheme;
|
||
accent?: RgbTuple;
|
||
material?: NodedcMaterial;
|
||
}
|
||
|
||
function formatRgb([red, green, blue]: RgbTuple): string {
|
||
return `${red} ${green} ${blue}`;
|
||
}
|
||
|
||
function clampOpacity(value: number): number {
|
||
return Math.max(0, Math.min(1, value));
|
||
}
|
||
|
||
export function applyNodedcTheme(
|
||
target: HTMLElement,
|
||
{ theme = "dark", accent, material }: ApplyThemeOptions = {},
|
||
): void {
|
||
target.dataset.nodedcTheme = theme;
|
||
target.dataset.nodedcUi = "true";
|
||
|
||
if (accent) {
|
||
const variables = createAccentVariables(accent);
|
||
Object.entries(variables).forEach(([property, value]) => {
|
||
target.style.setProperty(property, value);
|
||
});
|
||
}
|
||
|
||
if (!material) return;
|
||
if (material.panel) target.style.setProperty("--nodedc-panel-material-rgb", formatRgb(material.panel));
|
||
if (typeof material.panelOpacity === "number" && Number.isFinite(material.panelOpacity)) {
|
||
const opacity = clampOpacity(material.panelOpacity);
|
||
target.style.setProperty("--nodedc-panel-material-opacity", String(opacity));
|
||
target.style.setProperty("--nodedc-panel-material-strong-opacity", String(clampOpacity(opacity + 0.12)));
|
||
target.style.setProperty("--nodedc-panel-material-soft-opacity", String(clampOpacity(Math.max(0.12, opacity - 0.2))));
|
||
}
|
||
if (material.field) target.style.setProperty("--nodedc-field-material-rgb", formatRgb(material.field));
|
||
if (typeof material.fieldOpacity === "number" && Number.isFinite(material.fieldOpacity)) {
|
||
target.style.setProperty("--nodedc-field-material-opacity", String(clampOpacity(material.fieldOpacity)));
|
||
}
|
||
}
|
||
|
||
export function clearNodedcAccent(target: HTMLElement): void {
|
||
target.style.removeProperty("--nodedc-accent-rgb");
|
||
target.style.removeProperty("--nodedc-on-accent-rgb");
|
||
}
|
||
|
||
export function clearNodedcMaterial(target: HTMLElement): void {
|
||
[
|
||
"--nodedc-panel-material-rgb",
|
||
"--nodedc-panel-material-opacity",
|
||
"--nodedc-panel-material-strong-opacity",
|
||
"--nodedc-panel-material-soft-opacity",
|
||
"--nodedc-field-material-rgb",
|
||
"--nodedc-field-material-opacity",
|
||
].forEach((property) => target.style.removeProperty(property));
|
||
}
|