diff --git a/assets/scripts/ruby_switch.js b/assets/scripts/ruby_switch.js new file mode 100644 index 0000000..4556eb7 --- /dev/null +++ b/assets/scripts/ruby_switch.js @@ -0,0 +1,169 @@ +// Eryn Wells + +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; + } + + #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); diff --git a/assets/styles/root/050_ruby_controls.css b/assets/styles/root/050_ruby_controls.css index 60965f9..c8f4b9a 100644 --- a/assets/styles/root/050_ruby_controls.css +++ b/assets/styles/root/050_ruby_controls.css @@ -1,29 +1,25 @@ @layer root { - body:has(fieldset#ruby-controls > input[value=NONE]:checked) rt { display: none; } + body:has(ruby-switch[value=none]) rt { + display: none; + } - /* When ruby-controls is set to hidden, hide the ruby base and only show the */ - body:has(fieldset#ruby-controls > input[value=HIDDEN]:checked) ruby > span { display: none; } + /* When the ruby switch is set to "hidden", hide the ruby base and only show the . */ + body:has(ruby-switch[value=hidden]) ruby > span { + display: none; + } - body:has(fieldset#ruby-controls > input[value=HIDDEN]:checked) rt { + body:has(ruby-switch[value=hidden]) rt { color: inherit; display: inline; font-size: inherit; white-space: inherit; } - body:has(fieldset#ruby-controls > input[value=HIDDEN]:checked) :is(h1, h2, h3, h4, h5, h6) rt { + body:has(ruby-switch[value=hidden]) :is(h1, h2, h3, h4, h5, h6) rt { font-family: var(--font-family-heading); font-weight: bold; } - fieldset#ruby-controls input[type=radio] { - margin-inline-end: calc(var(--body-item-spacing) / 6); - } - - fieldset#ruby-controls label { - margin-inline-end: calc(var(--body-item-spacing)); - } - rt { transition: color 0.3s; } ruby:hover rt { transition: color 0.3s; } } diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 9ef4747..b137185 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -30,6 +30,11 @@ {{ block "scripts" . }}{{ end }} {{- with partial "resources/site_js.html" . -}} - + + {{ end }} + {{ if .HasShortcode "ruby" }} + {{ with partial "resources/script.html" "ruby_switch" }} + + {{ end }} {{ end }} diff --git a/layouts/partials/resources/script.html b/layouts/partials/resources/script.html new file mode 100644 index 0000000..e61b09b --- /dev/null +++ b/layouts/partials/resources/script.html @@ -0,0 +1,2 @@ +{{ $script := resources.Get (printf "scripts/%s.js" .) | fingerprint "md5" }} +{{ return $script }} diff --git a/layouts/partials/ruby_controls.html b/layouts/partials/ruby_controls.html index bdb75f6..bd89f4b 100644 --- a/layouts/partials/ruby_controls.html +++ b/layouts/partials/ruby_controls.html @@ -1,13 +1,5 @@ {{ if .HasShortcode "ruby" -}} {{- end }}