import { html, css, TemplateResult, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { OmniElement } from '../core/OmniElement.js'; /** * Control to switch a value on or off. * * @import * ```js * import '@capitec/omni-components/switch'; * ``` * * @example * ```html * * * ``` * * @element omni-switch * * Registry of all properties defined by the component. * * @slot - Content to render inside the component. * * @fires {CustomEvent<{ old: Boolean; new: Boolean; }>} value-change - Dispatched when the switch checked state is changed. * * @cssprop --omni-switch-label-font-color - Label font color. * @cssprop --omni-switch-label-font-family - Label font family. * @cssprop --omni-switch-label-font-size - Label font size. * @cssprop --omni-switch-label-font-weight - Label font weight. * @cssprop --omni-switch-label-spacing - Label left margin spacing. * * @cssprop --omni-switch-input-hint-label-font-color - Hint text font color. * @cssprop --omni-switch-input-hint-label-font-family - Hint text font family. * @cssprop --omni-switch-input-hint-label-font-size - Hint text font size. * @cssprop --omni-switch-input-hint-label-font-weight - Hint text font weight. * * @cssprop --omni-switch-input-error-label-font-color - Error text font color. * @cssprop --omni-switch-input-error-label-font-family - Error text font family. * @cssprop --omni-switch-input-error-label-font-size - Error text font size. * @cssprop --omni-switch-input-error-label-font-weight - Error text font weight. * * @cssprop --omni-switch-track-width - Track width. * @cssprop --omni-switch-track-height - Track height. * @cssprop --omni-switch-track-background-color - Track background color. * @cssprop --omni-switch-track-border-radius - Track border radius. * @cssprop --omni-switch-track-inset - Track inset margins. * * @cssprop --omni-switch-knob-height - Knob height. * @cssprop --omni-switch-knob-width - Knob width. * @cssprop --omni-switch-knob-background-color - Knob background color. * @cssprop --omni-switch-knob-box-shadow - Knob box shadow. * * @cssprop --omni-switch-knob-hover-box-shadow - Knob hover box shadow. * * @cssprop --omni-switch-checked-track-background-color - Track checked background color. * @cssprop --omni-switch-checked-hover-knob-box-shadow - Knob checked hover box shadow. * @cssprop --omni-switch-disabled-track-background-color - Track disabled background color. * @cssprop --omni-switch-checked-knob-background-color - Knob checked background color. * * @cssprop --omni-switch-disabled-knob-background-color - Knob disabled background color.. * @cssprop --omni-switch-disabled-knob-box-shadow - Knob disabled hover box shadow. * */ @customElement('omni-switch') export class Switch extends OmniElement { /** * Text label. * @attr */ @property({ type: String, reflect: true }) label?: string; /** * Data associated with the component. * @attr */ @property({ type: Object, reflect: true }) data?: object; /** * Hint message to assist the user. * @attr */ @property({ type: String, reflect: true }) hint?: string; /** * An error message to guide users to correct a mistake. * @attr */ @property({ type: String, reflect: true }) error?: string; /** * Indicator if the component is checked or not. * @attr */ @property({ type: Boolean, reflect: true }) checked?: boolean; /** * Indicator if the component is disabled. * @attr */ @property({ type: Boolean, reflect: true }) disabled?: boolean; override focus() { this.shadowRoot?.getElementById('track')?.focus(); } _click(event: MouseEvent): void { // Ignore the event if the component is disabled. if (this.disabled) { return event.stopImmediatePropagation(); } // Switch the component checked state. this._switchChecked(); } _switchChecked(): void { // Record the previous state. const oldValue = this.checked; // Invert the checked state. this.checked = !oldValue; this.dispatchEvent( new CustomEvent('value-change', { detail: { old: oldValue, new: this.checked }, bubbles: true }) ); } _keyDown(event: KeyboardEvent): void { // Ignore the event if the component is disabled. if (this.disabled) { return event.stopImmediatePropagation(); } // Intercept space and enter key events to switch the component checked state. const keyCode = (event.code || '').toUpperCase(); if (keyCode === 'SPACE' || keyCode === 'ENTER' || keyCode === 'NUMPADENTER') { // Switch the component checked state. this._switchChecked(); // Prevent the key event from propagating further. return event.preventDefault(); } } static override get styles() { return [ super.styles, css` /* CONTAINER STYLES */ .container { display: flex; align-items: center; } /* LABEL STYLES */ .container > .label { color: var(--omni-switch-label-font-color, var(--omni-font-color)); font-family: var(--omni-switch-label-font-family, var(--omni-font-family)); font-size: var(--omni-switch-label-font-size, var(--omni-font-size)); font-weight: var(--omni-switch-label-font-weight, var(--omni-font-weight)); margin-left: var(--omni-switch-label-spacing, 8px); cursor: default; } .container > .label > .hint { color: var(--omni-switch-input-hint-label-font-color, var(--omni-hint-font-color)); font-family: var(--omni-switch-input-hint-label-font-family, var(--omni-font-family)); font-size: var(--omni-switch-input-hint-label-font-size, 0.86em); font-weight: var(--omni-switch-input-hint-label-font-weight, 300); padding-top: 4px; } .container > .label > .error { color: var(--omni-switch-input-error-label-font-color, var(--omni-error-font-color)); font-family: var(--omni-switch-input-error-label-font-family, var(--omni-font-family)); font-size: var(--omni-switch-input-error-label-font-size, 0.86em); font-weight: var(--omni-switch-input-error-label-font-weight, 300); padding-top: 4px; } /* SWITCH BUTTON STYLES */ .container > #content { box-sizing: border-box; cursor: pointer; display: grid; align-items: center; } .container > #content > .track { width: var(--omni-switch-track-width, 23px); height: var(--omni-switch-track-height, 12px); grid-row: 1; grid-column: 1; background-color: var(--omni-switch-track-background-color, var(--omni-inactive-color)); border-radius: var(--omni-switch-track-border-radius, 16px); margin-left: var(--omni-switch-track-inset, 8px); margin-right: var(--omni-switch-track-inset, 8px); outline: 0; } .container > #content > .knob { height: var(--omni-switch-knob-height, 14px); grid-row: 1; grid-column: 1; position: relative; } .container > #content > .knob > div { width: var(--omni-switch-knob-width, 14px); height: 100%; background-color: var(--omni-switch-knob-background-color, var(--omni-background-color)); border-radius: 50%; box-shadow: var(--omni-switch-knob-box-shadow, 0 2px 4px 0 rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.15)); position: absolute; left: 0px; margin-top: auto; margin-bottom: auto; transition: 0.15s left ease-in-out; } .container > #content:hover > .knob > div { box-shadow: var(--omni-switch-knob-hover-box-shadow, 0 0 3px 3px var(--omni-box-shadow-color)); } /* CHECKED STATE STYLES */ .container.checked > #content > .track { background-color: var(--omni-switch-checked-track-background-color, var(--omni-accent-color)); } .container.checked > #content > .knob > div { background-color: var(--omni-switch-checked-knob-background-color, var(--omni-primary-color)); left: calc(100% - var(--omni-switch-knob-width, 14px)); box-shadow: none; } .container.checked > #content:hover > .knob > div { background-color: var(--omni-switch-checked-knob-background-color, var(--omni-primary-color)); left: calc(100% - var(--omni-switch-knob-width, 14px)); box-shadow: var(--omni-switch-checked-hover-knob-box-shadow, 0 0 3px 3px var(--omni-box-shadow-color)); } /* DISABLED STATE STYLES */ .container.disabled > #content { cursor: default; } .container.disabled > #content > .track, .container.disabled > #content:hover > .track { background-color: var(--omni-switch-disabled-track-background-color, var(--omni-disabled-background-color)); } .container.disabled > #content:hover > .knob > div, .container.disabled > #content > .knob > div { background-color: var(--omni-switch-disabled-knob-background-color, var(--omni-disabled-background-color)); box-shadow: var(--omni-switch-disabled-knob-box-shadow, 0 2px 4px 0 rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.15)); } ` ]; } override render(): TemplateResult { return html`
`; } } declare global { interface HTMLElementTagNameMap { 'omni-switch': Switch; } }