UNPKG

jb-checkbox

Version:
242 lines (232 loc) 7.81 kB
import { renderHTML } from './render'; import CSS from './jb-checkbox.scss'; import { ValidationHelper, ValidationItem, ValidationResult, type WithValidation } from 'jb-validation'; import { type JBFormInputStandards } from 'jb-form'; import { ElementsObject, ValidationValue } from './types.js'; export * from './types.js'; export class JBCheckboxWebComponent extends HTMLElement implements WithValidation, JBFormInputStandards<boolean> { static get formAssociated() { return true; } #value = false; //when we call on before change we save new value here so when user use event.target.value he will see new value but after the event bubble done we null it. //it mostly defined here for react eco-system #ChangeEventPreservedValue: boolean | null = null; elements!: ElementsObject; #disabled = false; #internals?: ElementInternals; get value(): boolean { if (this.#ChangeEventPreservedValue !== null) { return this.#ChangeEventPreservedValue; } return this.#value; } set value(value: boolean) { if (this.#value !== value) { this.#value = value; } this.#updateDomForValueChange(); if (this.#internals) { if(value){ (this.#internals as any).states?.add("checked"); }else{ (this.#internals as any).states?.delete("checked"); } if (typeof this.#internals.setFormValue == "function") { this.#internals.setFormValue(`${value}`); } } } #validation = new ValidationHelper({ clearValidationError: this.clearValidationError.bind(this), getValue: () => (this.value), getValidations: this.#GetInsideValidationsCallback.bind(this), getValueString: () => (this.value ? 'true' : 'false'), setValidationResult: this.#setValidationResult.bind(this), showValidationError: this.showValidationError.bind(this) }) get validation() { return this.#validation; } get name() { return this.getAttribute('name') || ''; } initialValue = false; get isDirty(): boolean { return this.#value !== this.initialValue; } #required = false; set required(value: boolean) { this.#required = value; this.#validation.checkValiditySync({ showError: false }); } get required() { return this.#required; } isAutoValidationDisabled = false; get disabled() { return this.#disabled; } set disabled(value: boolean) { this.#disabled = value; if (value) { //TODO: remove as any when typescript support (this.#internals as any).states?.add("disabled"); } else { (this.#internals as any).states?.delete("disabled"); } } constructor() { super(); if (typeof this.attachInternals == "function") { //some browser don't support attachInternals this.#internals = this.attachInternals(); } this.initWebComponent(); } connectedCallback(): void { // standard web component event that called when all of dom is bound this.callOnLoadEvent(); this.initProp(); this.callOnInitEvent(); } callOnLoadEvent(): void { const event = new CustomEvent('load', { bubbles: true, composed: true }); this.dispatchEvent(event); } callOnInitEvent(): void { const event = new CustomEvent('init', { bubbles: true, composed: true }); this.dispatchEvent(event); } initWebComponent(): void { const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true, }); const html = `<style>${CSS}</style>` + '\n' + renderHTML(); const element = document.createElement('template'); element.innerHTML = html; shadowRoot.appendChild(element.content.cloneNode(true)); this.elements = { componentWrapper: shadowRoot.querySelector('.jb-checkbox-web-component')!, label: shadowRoot.querySelector('.label-wrapper slot')!, svgWrapper: shadowRoot.querySelector('.svg-wrapper')!, svg: shadowRoot.querySelector('.check-box-svg')!, }; this.registerEventListener(); } registerEventListener(): void { this.elements.componentWrapper.addEventListener('click', () => this.#onComponentClick()); } initProp() { this.value = this.getAttribute('value') === "true" || false; } static get observedAttributes(): string[] { return ["label", 'value', 'name', 'disabled',]; } attributeChangedCallback(name: string, oldValue: string, newValue: string): void { // do something when an attribute has changed this.onAttributeChange(name, newValue); } onAttributeChange(name: string, value: string): void { switch (name) { case 'value': this.value = Boolean(value); break; case 'label': this.elements.label.innerText = value; break; case 'disabled': if (value == '' || value === "true") { this.disabled = true; } else if (value == "false" || value == null || value == undefined) { this.disabled = false; } break; } } #onComponentClick(): void { if (this.#disabled) { return; } this.#ChangeEventPreservedValue = !this.#value; const isEventPrevented = this.#dispatchOnBeforeChangeEvent(); this.#ChangeEventPreservedValue = null; if (!isEventPrevented) { this.value = !this.#value; const DispatchedEvent = this.#dispatchOnChangeEvent(); if (DispatchedEvent.defaultPrevented) { this.value = !this.#value; } } } #dispatchOnBeforeChangeEvent(): boolean { const event = new CustomEvent('before-change', { cancelable: true }); this.dispatchEvent(event); const prevented = event.defaultPrevented; return prevented; } #dispatchOnChangeEvent() { const event = new Event('change', { bubbles: true, cancelable: true, composed: true }); this.dispatchEvent(event); return event; } /** * @public */ //TODO: find a way to manage focus and keyboard control focus() { //public method } #updateDomForValueChange() { if (this.value) { this.elements.svg.classList.add('--active'); } else { this.elements.svg.classList.remove('--active'); } } /** * @description this method called on every checkValidity calls and update validation result of #internal */ #setValidationResult(result: ValidationResult<ValidationValue>) { if (result.isAllValid) { this.#internals.setValidity({}, ''); } else { const states: ValidityStateFlags = {}; let message = ""; result.validationList.forEach((res) => { if (!res.isValid) { if (res.validation.stateType) { states[res.validation.stateType] = true; } if (message == '') { message = res.message; } } }); this.#internals.setValidity(states, message); } } #GetInsideValidationsCallback(): ValidationItem<ValidationValue>[] { if (this.#required) { return [{ validator: (value) => value !== false, message: "چک میبایست فعال شود" }]; } return []; } showValidationError(message: string) { //TODO: implement it } clearValidationError() { //TODO: implement it } get validationMessage() { return this.#internals.validationMessage; } checkValidity() { return this.#validation.checkValiditySync({ showError: false }).isAllValid; } reportValidity() { return this.#validation.checkValiditySync({ showError: true }).isAllValid; } } const myElementNotExists = !customElements.get('jb-checkbox'); if (myElementNotExists) { window.customElements.define('jb-checkbox', JBCheckboxWebComponent); }