From abb85f701d340528473be589412344ac6db5e1f4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 Oct 2023 11:05:02 -0700 Subject: [PATCH] Ruby Switch script v1 --- assets/scripts/ruby_switch.js | 164 ++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 assets/scripts/ruby_switch.js diff --git a/assets/scripts/ruby_switch.js b/assets/scripts/ruby_switch.js new file mode 100644 index 0000000..af48857 --- /dev/null +++ b/assets/scripts/ruby_switch.js @@ -0,0 +1,164 @@ +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: "あa", + 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; + } + `; + } + + #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.textContent = 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);