import { property } from "lit/decorators.js"; import { FormElementInterface } from "@supersoniks/concorde/core/mixins/FormElement"; import { PublisherManager } from "@supersoniks/concorde/utils"; import { MixinArgsType } from "../_types/types"; type Constructor = new (...args: MixinArgsType[]) => T; const Form = >(superClass: T) => { /** * FormCheckable ajoute un comportement de sélection (checked) a tout FormElement qui utilise cette mixin. * Il est notamment utilisé par sonic-checkbox, sonic-radio et sonic-button * * Par défaut le fonctionnement est celui d'une checkbox classique. * * Dans ce cas, a la sélection, formPublisher[name_prop] = [this.value, ...autres valeurs d'élémen,ts sélectionnés ayant le même nom]; * * En activant le mode radio à l'aide du flag dédié, un seul FormElement ayant la même propriété name peut être sélectionné à la fois. * * Dans ce cas, a la sélection, formPublisher[name_prop] = this.value; */ class FormCheckable extends superClass { /** * Voir la mixin FormElement. */ _value: string | null = ""; @property() get value(): string | null { return this._value; } set value(newValue: string | null) { if (this.value == newValue) return; if (this.hasAttribute("value") && !this.forceAutoFill) newValue = this.getAttribute("value"); if (this._value == newValue) return; if (newValue == null) return; this._value = newValue; if (!this.value) return; // On check l'élément si il est cheched dans le formPublisher const formPublisher = this.getFormPublisher(); if (formPublisher && this.name) { let currentValue = this.getFormPublisherValue(); if (this.radio || this.unique) { this.checked = currentValue == newValue ? true : null; } if (!Array.isArray(currentValue)) { currentValue = []; } if ((currentValue as Array).indexOf(newValue) != -1) this.checked = true; } //On mets à jour la valeur dans la donnée si l'élément est checked if (this.checked == true) this.updateDataValue(); this.requestUpdate(); } @property() forceAutoFill = false; /** * comme radio,mais peut être désélectionné après sélection */ @property({ type: Boolean }) unique: true | null = null; /** Active le mode radio */ @property({ type: Boolean }) radio: true | null = null; /** * propriété checked avec des caractéristiques similaire à un input html classique. */ _checked: true | null | "indeterminate" = null; @property() get checked(): true | null | "indeterminate" { return this._checked; } public set checked(checked: true | null | "indeterminate") { this.setCheckedValue(checked); if (this.checksAll()) { const checkAllPublisher = this.getCheckAllPublisher(); if (checkAllPublisher) { if (this.checked === true) checkAllPublisher.checkMode.set("allChecked"); else if (this.checked === null) { checkAllPublisher.checkMode.set("noneChecked"); const formPublisher = this.getFormPublisher(); if (formPublisher) { this.setFormPublisherValue([]); } } } } this.requestUpdate(); } validateFormElement() { const input: HTMLInputElement = this.shadowRoot?.querySelector( "input" ) as HTMLInputElement; if (!input || input.checkValidity()) return; const formPublisher = this.getFormPublisher(); if (formPublisher) { const value = this.getFormPublisherValue(); if ( (this.unique || this.radio) && value !== null && value !== undefined && value.toString().length > 0 ) return; formPublisher.isFormValid = false; input.reportValidity(); } } checksAll() { return this.hasAttribute("checksAll"); } setCheckedValue(checked: true | null | "indeterminate") { if (this._checked == checked) return; this._checked = checked; this.updateDataValue(); this.requestUpdate(); setTimeout(() => this.updateAllChecked(), 1); // Désactivation du checked sur le publisher // Pas sur de l'utilité mais provoque un bug // if (this.publisher && !this.radio && !this.unique) { // this.publisher.checked = this._checked; // } } handleChange() { const newCheckedValue = this.checked === true ? (!this.radio ? null : true) : true; this.checked = newCheckedValue; const event = new Event("change"); this.dispatchEvent(event); } /** * Voir la mixin FormElement * Le comportement est ici modifié fonction de son mode (checkbox, radio, unique) */ getValueForFormPublisher() { let currentValue = this.getFormPublisherValue(); if (this.radio) { return this.checked === true && this.value != null ? this.value : currentValue; } if (this.unique) { return this.checked === true && this.value != null ? this.value : null; } if (!Array.isArray(currentValue)) { currentValue = []; } const currentValueArray = (currentValue as Array).slice(0); const idx = currentValueArray.indexOf(this.value); if (this.checked === true && idx === -1 && !this.checksAll()) currentValueArray.push(this.value); if (this.checked === null && idx !== -1) { currentValueArray.splice(idx, 1); } return currentValueArray; } /** * Voir la mixin FormElement * Le comportement est modifié de la manière suivante : * L'état du composant (checked) est mis à jour en fonction de la valeur fournie par le publisher associé au composant / en fonction de sont mode (radio, unique) */ setFormValueFromPublisher(value: string | Array | null) { if (this.unique || this.radio) { this.checked = this.value == value ? true : null; return; } if (!Array.isArray(value)) value = []; if (this.checksAll()) { return; } this.checked = value.indexOf(this.value) !== -1 ? true : null; } getCheckAllPublisher() { if (!this.formDataProvider) this.formDataProvider = this.getAncestorAttributeValue("formDataProvider"); const formDataProvider = this.formDataProvider; const name = this.getAttribute("name"); if (!formDataProvider || !name) { return null; } return PublisherManager.get( formDataProvider + "/" + name + "/_available_values_" ); } updateAllChecked = () => { const name = this.getAttribute("name"); const checkAllPublisher = this.getCheckAllPublisher(); const formPublisher = this.getFormPublisher(); if (!checkAllPublisher?.hasCheckAll.get()) { return; } if (!this.checksAll() && checkAllPublisher && formPublisher && name) { if (!(this.getFormPublisherValue() as any)?.length) { checkAllPublisher.checkMode.set("noneChecked"); return; } else if (this.checked === null) { checkAllPublisher.checkMode.set("someUnchecked"); } else if ( checkAllPublisher.checkMode.get() == "noneChecked" || checkAllPublisher.checkMode.get() == null ) { checkAllPublisher.checkMode.set("someUnchecked"); } const currentValues = this.getFormPublisherValue(); const allValues = checkAllPublisher.values.get(); if (allValues && allValues.length) { let checkedCount = allValues.length; for (const p of allValues) { if ((currentValues as Array).indexOf(p) == -1) { checkedCount -= 1; } } if (checkedCount == allValues.length) { checkAllPublisher.checkMode.set("allChecked"); } if (checkedCount == 0) { checkAllPublisher.checkMode.set("noneChecked"); } } if (allValues.indexOf(this.value) == -1 && this.unsetOnDisconnect()) { this.checked = null; } } }; onChecksAllRequest = (value: string) => { this.removeAttribute("allChecked"); this.removeAttribute("indeterminate"); if (value == "allChecked") { this.checked = true; this.setAttribute("allChecked", ""); } if (value == "noneChecked") { this.checked = null; } if (value == "someUnchecked") { if (this.checksAll()) this.checked = "indeterminate"; this.setAttribute("indeterminate", ""); } }; unset() { this.checked = null; } disconnectedCallback(): void { super.disconnectedCallback(); const checkAllPublisher = this.getCheckAllPublisher(); if (checkAllPublisher) { checkAllPublisher.checkMode.offAssign(this.onChecksAllRequest); if (!this.checksAll()) { const values = checkAllPublisher.values.get().slice(0); const idx = values.indexOf(this.value); if (idx != -1) { values.splice(idx, 1); checkAllPublisher.values.set(values); } } } setTimeout(() => this.updateAllChecked(), 1); } connectedCallback(): void { super.connectedCallback(); const formPublisher = this.getFormPublisher(); if (formPublisher && this.name) { const publisherValueForName = this.getFormPublisherValue(); if ( publisherValueForName && Array.isArray(publisherValueForName) && publisherValueForName.indexOf(this.value as string) !== -1 ) { this.checked = true; } } const checkAllPublisher = this.getCheckAllPublisher(); if (checkAllPublisher) { checkAllPublisher.checkMode.onAssign(this.onChecksAllRequest); if (this.checksAll()) { checkAllPublisher.hasCheckAll.set(true); } if (!checkAllPublisher.values.get()) { checkAllPublisher.values.set([]); } if (!this.checksAll()) { checkAllPublisher.values.set([ ...checkAllPublisher.values.get(), this.value, ]); } } if (!this.hasAttribute("checked")) { return; } if (!this.publisher || this.publisher.get().checked !== false) { setTimeout(() => (this.checked = true), 1); } } } return FormCheckable; }; export default Form;