UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

486 lines (434 loc) 10.8 kB
/** * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact schukai GmbH. * * SPDX-License-Identifier: AGPL-3.0 */ import { instanceSymbol, internalSymbol } from "../../constants.mjs"; import { CustomControl } from "../../dom/customcontrol.mjs"; import { Observer } from "../../types/observer.mjs"; import { ProxyObserver } from "../../types/proxyobserver.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { assembleMethodSymbol, registerCustomElement, updaterTransformerMethodsSymbol, } from "../../dom/customelement.mjs"; import { isFunction, isObject } from "../../types/is.mjs"; import { ToggleSwitchStyleSheet } from "./stylesheet/toggle-switch.mjs"; import { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { getWindow } from "../../dom/util.mjs"; import { fireEvent } from "../../dom/events.mjs"; import { addErrorAttribute } from "../../dom/error.mjs"; export { ToggleSwitch }; /** * @private * @type {symbol} */ const switchElementSymbol = Symbol("switchElement"); /** * @type {string} */ export const STATE_ON = "on"; /** * @type {string} */ export const STATE_OFF = "off"; /** * A simple toggle switch * * @fragments /fragments/components/form/toggle-switch * * @example /examples/components/form/toggle-switch-simple Simple example * * @since 3.57.0 * @copyright schukai GmbH * @summary A beautiful switch element * @fires monster-options-set * @fires monster-selected * @fires monster-change * @fires monster-changed */ class ToggleSwitch extends CustomControl { /** * To set the options via the HTML tag, the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * * The individual configuration values can be found in the table. * * @property {string} value=current value of the element * @property {Boolean} disabled Disabled state * @property {Object} classes * @property {string} classes.on specifies the class for the on state. * @property {string} classes.off specifies the class for the off state. * @property {Object} values * @property {string} values.off specifies the value of the element if it is not selected * @property {Object} labels * @property {string} labels.on specifies the label for the on state. * @property {string} labels.off specifies the label for the off state. * @property {string} actions * @property {string} actions.on specifies the action for the on state. * @property {string} actions.off specifies the action for the off state. * @property {Object} templates * @property {string} templates.main the main template used by the control. */ get defaults() { return Object.assign({}, super.defaults, { value: null, disabled: false, classes: { on: "monster-theme-on", off: "monster-theme-off", handle: "monster-theme-primary-1", error: "monster-theme-error-1", }, values: { on: "on", off: "off", }, labels: { toggleSwitchOn: "✔", toggleSwitchOff: "×", }, templates: { main: getTemplate(), }, actions: { on: () => {}, off: () => {}, }, }); } /** * @return {void} */ [assembleMethodSymbol]() { const self = this; super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); setTimeout(() => { /** * init value to off * if the value was not defined before inserting it into the HTML */ if (self.getOption("value") === null) { self.setOption("value", self.getOption("values.off")); } /** * value from attribute */ if (self.hasAttribute("value")) { self.setOption("value", self.getAttribute("value")); } /** * validate value */ validateAndSetValue.call(self); // this state is a getter if (this.state === STATE_ON) { toggleOn.call(self); } else { toggleOff.call(self); } }, 0); } /** * updater transformer methods for pipe * * @return {function} */ [updaterTransformerMethodsSymbol]() { return { "state-callback": () => { return this.state; }, }; } /** * @return [CSSStyleSheet] */ static getCSSStyleSheet() { return [ToggleSwitchStyleSheet]; } /** * toggle switch * * ``` * e = document.querySelector('monster-toggle-switch'); * e.click() * ``` */ click() { this.toggle(); } /** * toggle switch on/off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggle() * ``` * * @return {ToggleSwitch} */ toggle() { if (this.getOption("value") === this.getOption("values.on")) { return this.toggleOff(); } return this.toggleOn(); } /** * toggle switch on * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOn() * ``` * * @return {ToggleSwitch} */ toggleOn() { this.setOption("value", this.getOption("values.on")); fireEvent(this, "change"); return this; } /** * toggle switch off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOff() * ``` * * @return {ToggleSwitch} */ toggleOff() { this.setOption("value", this.getOption("values.off")); fireEvent(this, "change"); return this; } /** * returns the status of the element * * ``` * e = document.querySelector('monster-toggle-switch'); * console.log(e.state) * // ↦ off * ``` * * @return {string} */ get state() { return this.getOption("value") === this.getOption("values.on") ? STATE_ON : STATE_OFF; } /** * The current value of the Switch * * ``` * e = document.querySelector('monster-toggle-switch'); * console.log(e.value) * // ↦ on * ``` * * @return {string} */ get value() { return this.getOption("value"); } /** * Set value * * ``` * e = document.querySelector('monster-toggle-switch'); * e.value="on" * ``` * * @property {string} value */ set value(value) { if ( value === this.getOption("values.on") || value === this.getOption("values.off") ) { if ( this.state !== (value === this.getOption("values.on") ? STATE_ON : STATE_OFF) ) { this.setOption("value", value); } return; } addErrorAttribute( this, 'The value "' + value + '" must be "' + this.getOption("values.on") + '" or "' + this.getOption("values.off"), ); showError.call(this); } /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/form/toggle-switch@@instance", ); } /** * * @returns {string} */ static getTag() { return "monster-toggle-switch"; } } /** * @private */ function initControlReferences() { this[switchElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=switch]`, ); } /** * @private */ function toggleOn() { if (!this[switchElementSymbol]) { return; } this[switchElementSymbol].classList.remove(this.getOption("classes.off")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.on")); // change color const callback = this.getOption("actions.on"); if (isFunction(callback)) { callback.call(this); } if (typeof this.setFormValue === "function") { this.setFormValue(this.getOption("values.on")); } } /** * @private */ function toggleOff() { if (!this[switchElementSymbol]) { return; } this[switchElementSymbol].classList.remove(this.getOption("classes.on")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.off")); // change color const callback = this.getOption("actions.off"); if (isFunction(callback)) { callback.call(this); } if (typeof this.setFormValue === "function") { this.setFormValue(this.getOption("values.off")); } } /** * @private */ function showError() { if (!this[switchElementSymbol]) { return; } this[switchElementSymbol].classList.remove(this.getOption("classes.on")); this[switchElementSymbol].classList.remove(this.getOption("classes.off")); this[switchElementSymbol].classList.add(this.getOption("classes.error")); } /** * @private */ function validateAndSetValue() { const value = this.getOption("value"); const validatedValues = []; validatedValues.push(this.getOption("values.on")); validatedValues.push(this.getOption("values.off")); if (validatedValues.includes(value) === false) { addAttributeToken( this, ATTRIBUTE_ERRORMESSAGE, 'The value "' + value + '" must be "' + this.getOption("values.on") + '" or "' + this.getOption("values.off"), ); this.setOption("disabled", true); this.formDisabledCallback(true); showError.call(this); return; } this.setOption("disabled", false); this.formDisabledCallback(false); if (value === this.getOption("values.on")) { toggleOn.call(this); return; } toggleOff.call(this); } /** * @private * @return {initEventHandler} */ function initEventHandler() { const self = this; let lastValue = self.value; self[internalSymbol].attachObserver( new Observer(function () { if (isObject(this) && this instanceof ProxyObserver) { const n = this.getSubject()?.options?.value; if (lastValue !== n) { lastValue = n; validateAndSetValue.call(self); } } }), ); self.addEventListener("keyup", (event) => { if (event.keyCode === 32) { self.toggle(); } }); self.addEventListener("click", (event) => { self.toggle(); }); self.addEventListener("touch", (event) => { self.toggle(); }); return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control" tabindex="0"> <div class="switch" data-monster-role="switch" data-monster-attributes="data-monster-state path:value | call:state-callback"> <div class="label on" data-monster-replace="path:labels.toggleSwitchOn"></div> <div class="label off" data-monster-replace="path:labels.toggleSwitchOff"></div> <div data-monster-attributes="class path:classes.handle | suffix:\\ switch-slider"></div> </div> </div>`; } registerCustomElement(ToggleSwitch);