erynwells.me/assets/scripts/ruby_switch.js

169 lines
4.7 KiB
JavaScript

// Eryn Wells <eryn@erynwells.me>
class RubySwitch extends HTMLElement {
static controlSizeInPixels = 32;
static thumbTransitionDuration = 0.1;
static settings = [
{
id: "ruby-switch-none",
value: "none",
label: "あ"
},
{
id: "ruby-switch-both",
value: "both",
label: "<ruby>あ<rt>a</rt></ruby>",
default: true
},
{
id: "ruby-switch-hidden",
value: "hidden",
label: "ab"
},
];
#root;
#thumb;
constructor() {
super();
this.#updateValue(RubySwitch.settings.find(obj => obj.default).value);
this.addEventListener("RubyStyleChanged", event => {
this.#updateValue(event.detail.style);
});
this.#root = this.attachShadow({ mode: "closed" });
this.#buildShadowDOM();
this.#updateThumbPosition(this.#root.querySelector(".control[data-default]"));
}
#updateValue(style) {
this.setAttribute("value", style);
}
get #stylesheet() {
const controlSize = RubySwitch.controlSizeInPixels;
const halfControlSize = controlSize / 2;
return `
#ruby-controls {
box-sizing: border-box;
display: inline-block;
position: relative;
}
#controls {
border: none;
box-sizing: border-box;
display: inline grid;
grid-template-columns: repeat(3, ${controlSize}px);
margin: 0;
overflow: none;
padding: 0;
}
#thumb {
box-sizing: border-box;
box-shadow: 2px 2px 6px #ccc;
border: 0.5px solid #aaa;
border-radius: ${halfControlSize}px;
position: absolute;
top: 0;
height: ${controlSize}px;
width: ${controlSize}px;
z-index: 50;
transition: left ${RubySwitch.thumbTransitionDuration}s;
}
.control {
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
height: ${controlSize}px;
width: ${controlSize}px;
}
b {
font-weight: normal;
cursor: pointer;
}
label {
z-index: 2;
text-align: center;
}
#ruby-switch-both {
}
`;
}
#buildShadowDOM() {
const root = this.#root;
const style = document.createElement("style");
style.textContent = this.#stylesheet;
root.appendChild(style);
let container = document.createElement("div");
container.id = "ruby-controls";
root.appendChild(container);
let controls = document.createElement("div");
controls.id = "controls";
container.appendChild(controls);
for (const desc of RubySwitch.settings) {
let control = document.createElement("div");
control.classList.add("control")
control.id = desc.id;
control.dataset.value = desc.value;
if (desc.default) {
control.dataset.default = "";
}
controls.appendChild(control);
control.addEventListener("click", event => {
event.stopPropagation();
event.preventDefault();
this.#updateThumbPosition(event.currentTarget);
this.#root.dispatchEvent(new CustomEvent("RubyStyleChanged", {
bubbles: true,
composed: true,
detail: {
style: control.dataset.value,
},
}));
}, { capture: true });
const label = document.createElement("b");
label.innerHTML = desc.label;
control.appendChild(label);
}
const thumb = document.createElement("div");
this.#thumb = thumb;
thumb.id = "thumb";
container.appendChild(thumb);
}
#updateThumbPosition(selectedControl) {
const controls = this.#root.querySelector("#controls");
const trackBoundingRect = controls.getBoundingClientRect();
const controlBoundingRect = selectedControl.getBoundingClientRect();
const offset = controlBoundingRect.left - trackBoundingRect.left;
this.#thumb.style.left = `${offset}px`;
}
}
customElements.define("ruby-switch", RubySwitch);