diff options
| -rw-r--r-- | scripts/gui-common/widgets.js | 299 | ||||
| -rw-r--r-- | styles/widgets.css | 94 |
2 files changed, 197 insertions, 196 deletions
diff --git a/scripts/gui-common/widgets.js b/scripts/gui-common/widgets.js index 1be8ac3..3762e92 100644 --- a/scripts/gui-common/widgets.js +++ b/scripts/gui-common/widgets.js @@ -15,8 +15,11 @@ class Widget extends EventTarget{ /** @type {boolean} */ #inactive = false; - /** @type {Array<string>} */ - prevent_keys = []; + /** @type {Object} */ + prevent_keys = { + "Enter": 1, + " ": 1 + }; /** * Construct a new widget @@ -85,7 +88,7 @@ class Widget extends EventTarget{ /** @param {boolean} i */ setInactive (i) { - this.element.classList.toggle("inactive", i); + this.element.setAttribute("aria-disabled", i ? "true" : "false"); this.#inactive = i; } @@ -112,9 +115,6 @@ class Widget extends EventTarget{ /** @param {TouchEvent} event */ #emitTouchEvent(event) { - //if (window.SCROLLING) - // return; - if (event.type == "touchstart") this.element.classList.add("touch"); else if (event.type == "touchend" || event.type == "touchcancel") @@ -135,15 +135,7 @@ class Widget extends EventTarget{ else this.dispatchEvent(new KeyboardEvent(event.type, event)); - if (event.key == " " || event.key == "Enter") { - if (event.type == "keydown") - this.element.classList.add("touch"); - else if (event.type == "keyup") - this.element.classList.remove("touch"); - event.preventDefault(); - } - - if (this.prevent_keys.indexOf(event.key) != -1) + if (this.prevent_keys[event.key] == 1) event.preventDefault(); } @@ -292,6 +284,8 @@ class WidgetButton extends Widget if (this.#pressing == 0) this.set(this.get("true")); this.#pressing |= button; + + this.element.classList.add("touch"); } } @@ -307,7 +301,10 @@ class WidgetButton extends Widget if ((button & this.#pressing) !== 0) { this.#pressing -= button; if (this.#pressing == 0) + { this.set(this.get("false")); + this.element.classList.remove("touch"); + } } } @@ -319,7 +316,10 @@ class WidgetButton extends Widget if ((button & this.#pressing) !== 0) { this.#pressing -= (button & this.#pressing); if (this.#pressing == 0) + { this.set(this.get("false")); + this.element.classList.remove("touch"); + } } } @@ -360,45 +360,58 @@ class WidgetToggle extends Widget super(); this.element.classList.add("button"); this.element.classList.add("toggle"); - this.element.setAttribute("aria-role", "switch"); + this.element.setAttribute("role", "switch"); this.element.setAttribute("aria-checked", value === tv ? "true" : "false"); this.addEventListener("mousedown", this.#prime); - this.addEventListener("mouseup", this.#toggle); this.addEventListener("mouseleave", this.#leave); this.addEventListener("mouseenter", this.#enter); this.addEventListener("change", this.update); this.addEventListener("touchend", this.#touchend); + this.addEventListener("keydown", this.#keystart); + this.addEventListener("keyup", this.#keyend); + this.addEventListener("focusout", this.#focusend); this.#bound = this.#toggle.bind(this); super.set_by_id("false", fv); super.set_by_id("true", tv); this.set(value); } + do_toggle() { + if (this.get() === this.get("true")) + this.set(this.get("false")); + else + this.set(this.get("true")); + } + /** @param {MouseEvent} event */ #prime(event) { - this.#primed |= (1 << event.button); + if (event.button != 0) + return; + this.#primed |= 1; + window.addEventListener("mouseup", this.#bound); } /** @param {MouseEvent} event */ #toggle(event) { - if (this.#gone) + if (event.button != 0) + return; + + if ((this.#primed & 1) != 0) { + window.removeEventListener("mouseup", this.#bound); + } + + if (this.#gone != 0) { this.#primed = 0; this.#gone = 0; - window.removeEventListener("mouseup", this.#bound); - } - if (((1 << event.button) & this.#primed) == 0) return; + } - if (this.get() === this.get("true")) - this.set(this.get("false")); - else - this.set(this.get("true")); - + this.do_toggle(); this.update(); - this.#primed -= (1 << event.button); + this.#primed -= 1; } update() @@ -412,7 +425,6 @@ class WidgetToggle extends Widget if (this.#primed == 0) return; this.#gone = 1; - window.addEventListener("mouseup", this.#bound); } /** @param {MouseEvent} event */ @@ -421,7 +433,6 @@ class WidgetToggle extends Widget if (this.#primed == 0) return; this.#gone = 0; - window.removeEventListener("mouseup", this.#bound); } /** @param {TouchEvent} event */ @@ -439,15 +450,54 @@ class WidgetToggle extends Widget i.clientY > rect.top && i.clientY < rect.bottom ) { - if (this.get() === this.get("true")) - this.set(this.get("false")); - else - this.set(this.get("true")); + this.do_toggle(); return; } } } + #keystart (event) + { + let btn = 0; + if (event.key == " ") + btn = 2; + else if (event.key == "Enter") + btn = 4; + + this.#primed |= btn; + + if (this.#primed && btn) + this.element.classList.add("touch"); + } + + #keyend (event) + { + let btn = 0; + if (event.key == " ") + btn = 2; + else if (event.key == "Enter") + btn = 4; + + if (this.#primed & btn) { + this.#primed -= btn; + + this.do_toggle() + + if (this.#primed == 0) + this.element.classList.remove("touch"); + } + } + + #focusend(event) + { + let btn = 6; + if ((this.#primed & btn) != 0) { + this.#primed -= (this.#primed & btn); + if (this.#primed == 0) + this.element.classList.remove("touch"); + } + } + /** * @param {string} id * @param {*} v @@ -474,7 +524,7 @@ class WidgetCheckbox extends WidgetToggle { constructor(value = false, tv = true, fv = false) { super(value, tv, fv); - this.element.setAttribute("aria-role", "checkbox"); + this.element.setAttribute("role", "checkbox"); this.element.classList.remove("toggle"); this.element.classList.add("checkbox"); } @@ -646,7 +696,12 @@ class WidgetSlider extends WidgetDragable { super(1); this.element.classList.add("slider"); + this.element.setAttribute("role", "slider"); this.element.setAttribute("aria-orientation", "vertical"); + this.element.setAttribute("aria-valuenow", value); + if (percent) + this.element.setAttribute("aria-valuetext", `${Math.trunc((value - min) / (max - min) * 100)}%`); + let fill = document.createElement("div"); fill.classList.add("fill"); this.element.appendChild(fill); @@ -658,16 +713,30 @@ class WidgetSlider extends WidgetDragable this.#u_detail = this.#update_detail.bind(this); this.addEventListener("change", this.#change); - this.addEventListener("keydown", this.#keypress); + this.addEventListener("keydown", this.#keystart); + this.addEventListener("focusout", this.#focusend); super.set_by_id("max", max); + this.element.setAttribute("aria-valuemax", max); super.set_by_id("min", min); + this.element.setAttribute("aria-valuemin", min); super.set_by_id("step", step); super.set_by_id("prec", precision); super.set_by_id("perc", percent); this.set(value); - this.prevent_keys = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown", "End", "Home"]; + this.prevent_keys = { + "ArrowUp": 1, + "ArrowDown": 1, + "ArrowLeft": 1, + "ArrowRight": 1, + "PageUp": 1, + "PageDown": 1, + "End": 1, + "Home": 1, + "Enter": 1, + " ": 1 + }; } #common_move(x, y) @@ -752,6 +821,9 @@ class WidgetSlider extends WidgetDragable let percent = (this.#tmpNum - min) / (max - min); this.#u_detail(this.#detail, this.#tmpNum, percent); + // Reason we set percent here even though we also set the aria-valuenow + // is that advanced attr support SUCKS MY ASS. Seriously these people + // have had it in the standard for years and it just never gets implemented. this.element.style.setProperty("--percent", percent); } @@ -761,7 +833,13 @@ class WidgetSlider extends WidgetDragable if (perc) el.innerText = `${Math.trunc(percent * 100)}%`; else - el.innerText = `${Math.trunc(Math.pow(10, prec) * val) / Math.pow(10, prec)}` + el.innerText = `${Math.trunc(Math.pow(10, prec) * val) / Math.pow(10, prec)}`; + + this.element.setAttribute("aria-valuenow", `${Math.trunc(Math.pow(10, prec) * val) / Math.pow(10, prec)}`); + if (perc) + this.element.setAttribute("aria-valuetext", `${Math.trunc(percent * 100)}%`); + else + this.element.removeAttribute("aria-valuetext"); } setDetailUpdater(updater) @@ -770,7 +848,10 @@ class WidgetSlider extends WidgetDragable } /** @param {KeyboardEvent} event */ - #keypress(event) { + #keystart(event) { + if (event.key != "Tab" && event.key != "Shift") + this.element.classList.add("touch"); + if (event.key == "Home") { this.set(this.get("min")); return; @@ -805,6 +886,10 @@ class WidgetSlider extends WidgetDragable this.set(val); } + #focusend() { + this.element.classList.remove("touch"); + } + /** * @param {string} id * @param {*} v @@ -856,6 +941,9 @@ class WidgetColorTemp extends WidgetSlider else out = WidgetColorTemp.WHITE.interpolate(WidgetColorTemp.BLUE, (percent - 0.85) / 0.15); this.element.style.setProperty("--detail", out.rgb()); + + this.element.setAttribute("aria-valuenow", `${Math.trunc(val)}`); + this.element.setAttribute("aria-valuetext", `${Math.trunc(val)} Kelvin`); } } @@ -880,7 +968,7 @@ class WidgetColorLight extends WidgetSlider } /** - * Update the detail for the color temp slider + * Update the detail for the color light slider * @param {HTMLElement} el * @param {number} val * @param {number} percent @@ -889,6 +977,9 @@ class WidgetColorLight extends WidgetSlider { let out = WidgetColorLight.BLACK.interpolate(WidgetColorLight.WHITE, percent); this.element.style.setProperty("--detail", out.rgb()); + + this.element.setAttribute("aria-valuenow", `${val}`); + this.element.setAttribute("aria-valuetext", `${Math.trunc(percent * 100)}% luminosity`); } } @@ -1397,6 +1488,7 @@ class WidgetScrubber extends WidgetDragable super(1); this.element.classList.add("scrubber"); + this.element.setAttribute("role", "slider") this.element.setAttribute("aria-orientation", "vertical"); this.#fill = document.createElement("div"); @@ -1409,8 +1501,12 @@ class WidgetScrubber extends WidgetDragable this.element.style.setProperty("--percent", 0); + this.element.setAttribute("aria-valuenow", value); + super.set_by_id("max", max); + this.element.setAttribute("aria-valuemax", max); super.set_by_id("min", min); + this.element.setAttribute("aria-valuemin", min); super.set_by_id("step", step); super.set_by_id("zones", zones); super.set_by_id("speed", speed); @@ -1563,6 +1659,11 @@ class WidgetScrubber extends WidgetDragable this.#detail.innerText = this.#tmpNum; } + #keypress(event) + { + + } + /** * @param {string} id * @param {*} v @@ -1585,118 +1686,22 @@ class WidgetScrubber extends WidgetDragable } /** - * A toggle widget, similar to a WidgetCheckbox, but meant - * to be used for on/off power states. - * @extends Widget<*> + * A radio widget, similar to a WidgetToggle, but meant + * to be used for single selection states. + * @extends WidgetToggle */ -class WidgetRadio extends Widget { - /** @type {number} */ - #primed = 0; - - /** @type {number} */ - #gone = 0; - - #bound = null; +class WidgetRadio extends WidgetToggle { - constructor (value = false, tv = true, fv = false) + constructor(value = false, tv = true, fv = false) { - super(); - this.element.classList.add("button"); + super(value, tv, fv); + this.element.setAttribute("role", "radio"); + this.element.classList.remove("toggle"); this.element.classList.add("radio"); - this.element.setAttribute("aria-checked", value === tv ? "true" : false); - this.addEventListener("mousedown", this.#prime); - this.addEventListener("mouseup", this.#toggle); - this.addEventListener("mouseleave", this.#leave); - this.addEventListener("mouseenter", this.#enter); - this.addEventListener("change", this.update); - this.addEventListener("touchend", this.#touchend); - this.#bound = this.#toggle.bind(this); - super.set_by_id("false", fv); - super.set_by_id("true", tv); - this.set(value); - } - - /** @param {MouseEvent} event */ - #prime(event) - { - this.#primed |= (1 << event.button); } - /** @param {MouseEvent} event */ - #toggle(event) - { - if (this.#gone) - { - this.#primed = 0; - this.#gone = 0; - window.removeEventListener("mouseup", this.#bound); - } - if (((1 << event.button) & this.#primed) == 0) - return; - - this.set(this.get("true")); - - this.update(); - this.#primed -= (1 << event.button); - } - - update() - { - this.element.setAttribute("aria-checked", this.get() === this.get("true") ? "true" : false); - } - - /** @param {MouseEvent} event */ - #leave(event) - { - if (this.#primed == 0) - return; - this.#gone = 1; - window.addEventListener("mouseup", this.#bound); - } - - /** @param {MouseEvent} event */ - #enter(event) - { - if (this.#primed == 0) - return; - this.#gone = 0; - window.removeEventListener("mouseup", this.#bound); - } - - /** @param {TouchEvent} event */ - #touchend(event) - { - if (event.changedTouches.length < 1) - return; - - let rect = this.element.getBoundingClientRect(); - - for (let i of event.changedTouches) - { - if (i.clientX < rect.right && - i.clientX > rect.left && - i.clientY > rect.top && - i.clientY < rect.bottom - ) { - this.set(this.get("true")); - return; - } - } - } - - /** - * @param {string} id - * @param {*} v - * @param {boolean} [emit] - */ - set_by_id(id, v, emit = false) - { - if (id == "false" || id == "true") - { - if (this.get() === this.get(id)) - super.set_by_id("value", v); - } - super.set_by_id(id, v, emit); + do_toggle() { + this.set(this.get("true")) } } @@ -1770,7 +1775,7 @@ class WidgetRadioGroup extends Widget { delete_option(index) { - this.#radios[index].element.remove(); + this.#radios[index].element.parentElement.remove(); this.#radios[index].removeEventListener("change", this.#binder); this.#radios.splice(index, 1); this.#vals.splice(index, 1); diff --git a/styles/widgets.css b/styles/widgets.css index d92258a..13676f6 100644 --- a/styles/widgets.css +++ b/styles/widgets.css @@ -36,7 +36,7 @@ height: calc(var(--height) * var(--base-unit) + (var(--height) - 1) * var(--alt-unit)); } -.widget:hover, .widget:focus-within { +.widget:hover { background-color: var(--w-bg-hover); } @@ -44,11 +44,11 @@ background-color: var(--w-bg-active); } -.widget.inactive, .widget:disabled { +.widget[aria-disabled="true"] { box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive); } -.widget.inactive::before, .widget:disabled::before { +.widget[aria-disabled="true"]::before { content: ''; display: block; position: absolute; @@ -63,17 +63,17 @@ z-index: 1; } -.widget.inactive:hover::before, .widget:disabled:hover::before, -.widget.inactive:focus-within::before, .widget:disabled:focus-within::before { +.widget[aria-disabled="true"]:hover::before { background-color: rgba(0, 0, 0, 0); } -.widget.inactive:hover, .widget:disabled:hover, -.widget.inactive:focus-within, .widget:disabled:focus-within { +.widget[aria-disabled="true"]:hover, +.widget[aria-disabled="true"]:focus-within { background-color: var(--w-bg); } -.widget.inactive:active, .widget:disabled:active, .widget.inactive.touch, .widget.touch:disabled { +.widget[aria-disabled="true"]:active, +.widget[aria-disabled="true"].touch { background-color: var(--w-bg); } @@ -98,7 +98,8 @@ border:none; } -.button.inactive:active, .button.inactive.touch, .button:disabled:active, .button.touch:disabled { +.button[aria-disabled="true"]:active, +.button[aria-disabled="true"].touch { transform: none; box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive) !important; border: inherit; @@ -167,8 +168,7 @@ height: 100%; } -.slider:hover > .fill::before, -.slider:focus-within > .fill::before { +.slider:hover > .fill::before { background-color: var(--w-sl-fill-hover); } @@ -225,7 +225,9 @@ transition-duration: 0.15s; } -.slider[aria-orientation="horizontal"] > .detail, .color-light[aria-orientation="horizontal"] > .detail, .color-temp[aria-orientation="horizontal"] > .detail { +.slider[aria-orientation="horizontal"] > .detail, +.color-light[aria-orientation="horizontal"] > .detail, +.color-temp[aria-orientation="horizontal"] > .detail { bottom: calc(100% + 20px); right: calc(-1.2 * var(--base-unit) / 2 + 100% * (1 - var(--percent))); } @@ -251,7 +253,7 @@ transition-duration: 0s; } -.inactive > .detail { +[aria-disabled="true"] > .detail { display: none; } @@ -274,7 +276,7 @@ overflow: unset; } -.checkbox:hover, .checkbox:focus-within { +.checkbox:hover { border-color: var(--w-cb-inactive-outline-hover); } @@ -282,23 +284,23 @@ border: 7px solid var(--w-cb-inactive-outline-active); } -.checkbox.inactive, .checkbox:disabled { +.checkbox[aria-disabled="true"] { box-shadow: 5px 5px inset var(--w-shadow-inactive), 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive); } -.checkbox.inactive::before, .checkbox:disabled::before { +.checkbox[aria-disabled="true"]::before { width: calc(100% + 14px); height: calc(100% + 14px); top: -7px; left: -7px; } -.checkbox.inactive:hover, .checkbox:disabled:hover, -.checkbox.inactive:focus-within, .checkbox:disabled:focus-within { +.checkbox[aria-disabled="true"]:hover { border-color: var(--w-cb-inactive-outline); } -.checkbox.inactive:active, .checkbox:disabled:active, .checkbox.inactive.touch, .checkbox.touch:disabled { +.checkbox[aria-disabled="true"]:active, +.checkbox[aria-disabled="true"].touch { border: 7px solid var(--w-cb-inactive-outline); box-shadow: 5px 5px inset var(--w-shadow-inactive), 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive) !important; } @@ -342,19 +344,17 @@ border-width: 0px 5px 5px 0px; } -.checkbox.inactive[aria-checked="true"], .checkbox:disabled[aria-checked="true"] { +.checkbox[aria-disabled="true"][aria-checked="true"] { box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive); } -.checkbox.inactive[aria-checked="true"]:active, -.checkbox[aria-checked="true"]:disabled:active, -.checkbox.inactive.touch[aria-checked="true"], -.checkbox.touch[aria-checked="true"]:disabled { +.checkbox[aria-disabled="true"][aria-checked="true"]:active, +.checkbox.touch[aria-disabled="true"][aria-checked="true"] { border: none; box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive) !important; } -.checkbox.inactive[aria-checked="true"]::before, .checkbox[aria-checked="true"]:disabled::before { +.checkbox[aria-disabled="true"][aria-checked="true"]::before { width: 100%; height: 100%; top: 0; @@ -393,16 +393,15 @@ transition-duration: 0s; } -.color-wheel:hover::after, -.color-wheel:focus-within:after { +.color-wheel:hover::after { border-color: #888; } -.color-wheel.inactive::before { +.color-wheel[aria-disabled="true"]::before { border-radius: 50%; } -.color-wheel.inactive::after { +.color-wheel[aria-disabled="true"]::after { border-color: rgb(68, 68, 68, 0); } @@ -480,12 +479,12 @@ transition-duration: 0s; } -.color-temp:hover > .fill::after, -.color-temp:focus-within > .fill::after { +.color-temp:hover > .fill::after { border-color: #888; } -.color-temp.inactive > .fill::after, .color-light.inactive > .fill::after { +.color-temp[aria-disabled="true"] > .fill::after, +.color-light[aria-disabled="true"] > .fill::after { border-color: rgb(68, 68, 68, 0); } @@ -653,7 +652,7 @@ } -.sel-button > .button.active { +.sel-button > .button[aria-checked="true"] { --w-bg: var(--w-sel-button-selected); --w-bg-hover: var(--w-sel-button-selected-hover); --w-bg-active: var(--w-sel-button-selected-active); @@ -710,7 +709,7 @@ bottom: calc(50% - (var(--base-unit) - 10px) / 2); } -.scrubber:hover > .fill::after, .scrubber.touch > .fill::after, .scrubber:focus-within > .fill::after { +.scrubber:hover > .fill::after, .scrubber.touch > .fill::after { background-color: var(--w-scr-nub-hover); } @@ -811,7 +810,7 @@ overflow: hidden; } -.radio:hover, .radio:focus-within { +.radio:hover { border-color: var(--w-radio-inactive-hover); } @@ -819,22 +818,23 @@ border: 5px solid var(--w-radio-inactive-active); } -.radio.inactive, .radio:disabled { +.radio[aria-disabled="true"] { box-shadow: 5px 5px inset var(--w-shadow-inactive), 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive); } -.radio.inactive::before, .radio:disabled::before { +.radio[aria-disabled="true"]::before { width: calc(100% + 14px); height: calc(100% + 14px); top: -7px; left: -7px; } -.radio.inactive:hover, .radio:disabled:hover, .radio.inactive:focus-within, .radio:disabled:focus-within { +.radio[aria-disabled="true"]:hover { border-color: var(--w-radio-inactive); } -.radio.inactive:active, .radio:disabled:active, .radio.inactive.touch, .radio.touch:disabled { +.radio[aria-disabled="true"]:active, +.radio[aria-disabled="true"].touch { border: 7px solid var(--w-radio-inactive); box-shadow: 5px 5px inset var(--w-shadow-inactive), 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive) !important; } @@ -867,13 +867,11 @@ border-color: var(--w-radio-active); } -.radio[aria-checked="true"]:hover, -.radio[aria-checked="true"]:focus-within { +.radio[aria-checked="true"]:hover { border-color: var(--w-radio-active-hover); } -.radio[aria-checked="true"]:hover::after, -.radio[aria-checked="true"]:focus-within::after { +.radio[aria-checked="true"]:hover::after { background-color: var(--w-radio-active-hover); } @@ -894,18 +892,16 @@ } -.radio.inactive[aria-checked="true"], .radio[aria-checked="true"]:disabled { +.radio[aria-disabled="true"][aria-checked="true"] { box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive); } -.radio.inactive[aria-checked="true"]:active, -.radio[aria-checked="true"]:disabled:active, -.radio.inactive.touch[aria-checked="true"], -.radio.touch[aria-checked="true"]:disabled { +.radio[aria-disabled="true"][aria-checked="true"]:active, +.radio.touch[aria-disabled="true"][aria-checked="true"] { box-shadow: 1px 1px var(--w-shadow-inactive), 3px 3px var(--w-shadow-inactive), 5px 5px var(--w-shadow-inactive) !important; } -.radio.inactive[aria-checked="true"]::before, .radio[aria-checked="true"]:disabled::before { +.radio[aria-disabled="true"][aria-checked="true"]::before { width: 100%; height: 100%; top: 0; |