/** * 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 } from "../../constants.mjs"; import { 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 { isObject, isFunction } from "../../types/is.mjs"; import { ToggleSwitchStyleSheet } from "./stylesheet/toggle-switch.mjs"; import { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_ROLE, } from "../../dom/constants.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 * * @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=false 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=specifies 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", }, values: { on: "on", off: "off", }, labels: { toggleSwitchOn: "✔", toggleSwitchOff: "✖", }, templates: { main: getTemplate(), }, actions: { on: () => { throw new Error("the on action is not defined"); }, off: () => { throw new Error("the off action is not defined"); }, }, }); } /** * @return {ToggleSwitch} */ [assembleMethodSymbol]() { const self = this; super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); /** * 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); if (this.state === STATE_ON) { toggleClassOn.call(self); } else { toggleClassOff.call(self); } /** * is called when options changed */ self[internalSymbol].attachObserver( new Observer(function () { if (isObject(this) && this instanceof ProxyObserver) { validateAndSetValue.call(self); toggleClass.call(self); } }), ); return this; } /** * updater transformer methods for pipe * * @return {function} */ [updaterTransformerMethodsSymbol]() { return { "state-callback": (Wert) => { return this.state; }, }; } /** * @return [CSSStyleSheet] */ static getCSSStyleSheet() { return [ToggleSwitchStyleSheet]; } /** * toggle switch * * ``` * e = document.querySelector('monster-toggle-switch'); * e.click() * ``` */ click() { toggleValues.call(this); } /** * toggle switch on/off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggle() * ``` * * @return {ToggleSwitch} */ toggle() { this.click(); return this; } /** * toggle switch on * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOn() * ``` * * @return {ToggleSwitch} */ toggleOn() { this.setOption("value", this.getOption("values.on")); return this; } /** * toggle switch off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOff() * ``` * * @return {ToggleSwitch} */ toggleOff() { this.setOption("value", this.getOption("values.off")); 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.state === STATE_ON ? this.getOption("values.on") : this.getOption("values.off"); } /** * Set value * * ``` * e = document.querySelector('monster-toggle-switch'); * e.value="on" * ``` * * @property {string} value */ set value(value) { this.setOption("value", value); } /** * This method is called by the `instanceof` operator. * @returns {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/form/toggle-switch@@instance", ); } static getTag() { return "monster-toggle-switch"; } } /** * @private */ function initControlReferences() { this[switchElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=switch]`, ); } /** * @private */ function toggleClassOn() { this[switchElementSymbol].classList.remove(this.getOption("classes.off")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.on")); // change color } /** * @private */ function toggleClassOff() { this[switchElementSymbol].classList.remove(this.getOption("classes.on")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.off")); // change color } /** * @private */ function toggleClass() { if (this.getOption("value") === this.getOption("values.on")) { toggleClassOn.call(this); } else { toggleClassOff.call(this); } } /** * @private */ function toggleValues() { if (this.getOption("disabled") === true) { return; } let callback, value; if (this.getOption("value") === this.getOption("values.on")) { value = this.getOption("values.off"); callback = this.getOption("actions.off"); } else { value = this.getOption("values.on"); callback = this.getOption("actions.on"); } this.setOption("value", value); this?.setFormValue(value); if (isFunction(callback)) { callback.call(this); } this.setOption("state", this.state); } /** * @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); } else { this.setOption("disabled", false); this.formDisabledCallback(false); } } /** * @private * @return {initEventHandler} */ function initEventHandler() { const self = this; self.addEventListener("keyup", function (event) { if (event.code === "Space") { self[switchElementSymbol].click(); } }); self.addEventListener("click", function (event) { toggleValues.call(self); }); 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);