import { Component } from /**/"./Component.js"; let Toolkit; /////////////////////////////////////////////////////////////////////////////// // Button // /////////////////////////////////////////////////////////////////////////////// // Push, toggle or radio button class Button extends Component { static Component = Component; //////////////////////////////// Constants //////////////////////////////// // Types static BUTTON = 0; static RADIO = 1; static TOGGLE = 2; ///////////////////////// Initialization Methods ////////////////////////// constructor(gui, options) { super(gui, options, { className: "tk tk-button", focusable: true, role : "button", tagName : "div", style : { display : "inline-block", userSelect: "none" } }); // Configure instance fields options = options || {}; this.attribute = options.attribute || "aria-pressed"; this.group = null; this.isEnabled = null; this.isSelected = false; this.text = null; this.type = Button.BUTTON; // Configure contents this.contents = document.createElement("div"); this.append(this.contents); // Configure component this.setEnabled(!("enabled" in options) || options.enabled); if ("group" in options) options.group.add(this); this.setText (options.text); this.setType (options.type); if ("selected" in options) this.setSelected(options.selected); // Configure event handlers this.addEventListener("keydown" , e=>this.onKeyDown (e)); this.addEventListener("pointerdown", e=>this.onPointerDown(e)); this.addEventListener("pointermove", e=>this.onPointerMove(e)); this.addEventListener("pointerup" , e=>this.onPointerUp (e)); } ///////////////////////////// Event Handlers ////////////////////////////// // Key press onKeyDown(e) { // Processing by key switch (e.key) { case "Enter": // Fallthrough case " " : this.click(); break; default: return; } // Configure event e.stopPropagation(); e.preventDefault(); } // Pointer down onPointerDown(e) { this.focus(); // Error checking if ( !this.isEnabled || this.element.hasPointerCapture(e.pointerId) || e.button != 0 ) return; // Configure event this.element.setPointerCapture(e.pointerId); e.stopPropagation(); e.preventDefault(); // Configure component this.element.classList.add("active"); } // Pointer move onPointerMove(e) { // Error checking if (!this.element.hasPointerCapture(e.pointerId)) return; // Configure event e.stopPropagation(); e.preventDefault(); // Configure component this.element.classList[ Toolkit.isInside(this.element, e) ? "add" : "remove"]("active"); } // Pointer up onPointerUp(e) { // Error checking if ( !this.isEnabled || e.button != 0 || !this.element.hasPointerCapture(e.pointerId) ) return; // Configure event this.element.releasePointerCapture(e.pointerId); e.stopPropagation(); e.preventDefault(); // Configure component this.element.classList.remove("active"); // Item is an action let bounds = this.getBounds(); if (this.menu == null && Toolkit.isInside(this.element, e)) this.click(); } ///////////////////////////// Public Methods ////////////////////////////// // Programmatically activate the button click() { if (this instanceof Toolkit.CheckBox) this.setSelected(this instanceof Toolkit.Radio||!this.isSelected); this.event("action"); } // Specify whether the button can be activated setEnabled(enabled) { this.isEnabled = enabled = !!enabled; this.setAttribute("aria-disabled", enabled ? null : "true"); } // Specify whether the toggle or radio button is selected setSelected(selected) { selected = !!selected; // Take no action if (selected == this.isSelected) return; // Processing by button type switch (this.type) { case Button.RADIO : if (selected && this.group != null) this.group.deselect(); // Fallthrough case Button.TOGGLE: this.isSelected = selected; this.setAttribute(this.attribute, selected); } } // Specify the widget's display text setText(text) { this.text = (text || "").toString().trim(); this.translate(); } // Specify what kind of button this is setType(type) { switch (type) { case Button.BUTTON: this.type = type; this.setAttribute(this.attribute, null); this.setSelected(false); break; case Button.RADIO : // Fallthrough case Button.TOGGLE: this.type = type; this.setAttribute(this.attribute, this.isSelected); this.setSelected(this.isSelected); break; default: return; } } ///////////////////////////// Package Methods ///////////////////////////// // Update the global Toolkit object static setToolkit(toolkit) { Toolkit = toolkit; } // Regenerate localized display text translate() { super.translate(); if (this.contents != null) this.contents.innerText = this.gui.translate(this.text, this); } } /////////////////////////////////////////////////////////////////////////////// // CheckBox // /////////////////////////////////////////////////////////////////////////////// // On/off toggle box class CheckBox extends Button { ///////////////////////// Initialization Methods ////////////////////////// constructor(app, options) { // Default options override let uptions = {}; Object.assign(uptions, options || {}); for (let entry of Object.entries({ attribute: "aria-checked", className: "tk tk-checkbox", role : "checkbox", style : {}, type : Button.TOGGLE })) if (!(entry[0] in uptions)) uptions[entry[0]] = entry[1]; // Default styles override for (let entry of Object.entries({ display : "inline-grid", gridTemplateColumns: "max-content auto" })) if (!(entry[0] in uptions.style)) uptions.style[entry[0]] = entry[1]; // Component overrides super(app, uptions); this.contents.classList.add("tk-contents"); // Configure icon this.icon = document.createElement("div"); this.icon.className = "tk tk-icon"; this.icon.setAttribute("aria-hidden", "true"); this.prepend(this.icon); } } /////////////////////////////////////////////////////////////////////////////// // Radio // /////////////////////////////////////////////////////////////////////////////// // Single selection box class Radio extends CheckBox { ///////////////////////// Initialization Methods ////////////////////////// constructor(app, options) { // Default options override let uptions = {}; Object.assign(uptions, options || {}); for (let entry of Object.entries({ className: "tk tk-radio", role : "radio", type : Button.RADIO })) if (!(entry[0] in uptions)) uptions[entry[0]] = entry[1]; // Component overrides super(app, uptions); } } /////////////////////////////////////////////////////////////////////////////// // Group // /////////////////////////////////////////////////////////////////////////////// // Radio button or menu item group class Group extends Component { ///////////////////////// Initialization Methods ////////////////////////// constructor(app) { super(app, { tagName: "div", style : { height : "0", position: "absolute", width : "0" } }); // Configure instance fields this.items = []; } ///////////////////////////// Public Methods ////////////////////////////// // Add an item add(item) { // Error checking if (!Toolkit.isComponent(item) || this.items.indexOf(item) != -1) return item; // Configure component this.setAttribute("role", item instanceof Toolkit.Radio ? "radiogroup" : "group"); // Configure item if (item.group != null) item.group.remove(item); item.group = this; // Add the item to the collection item.id = item.id || Toolkit.id(); this.items.push(item); this.setAttribute("aria-owns", this.items.map(i=>i.id).join(" ")); return item; } // Remove all items clear() { this.items.splice(); this.setAttribute("aria-owns", ""); } // Un-check all items in the group deselect() { for (let item of this.items) if (item.isSelected && "setSelected" in item) item.setSelected(false); } // Remove an item remove(item) { // Error checking let index = this.items.indexOf(item); if (index == -1) return; // Remove the item from the collection this.items.splice(index, 1); this.setAttribute("aria-owns", this.items.map(i=>i.id).join(" ")); item.group = null; return item; } } export { Button, CheckBox, Group, Radio };