1/** 2 * @author Maxime Louf 3 * inspiré de https://www.w3.org/WAI/content-assets/wai-aria-practices/patterns/disclosure/examples/js/disclosure-button.js 4 * sous licence W3C pour une partie du code https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 5 * @version 1.0.0 6 * 7 * @param buttonNode HTMLElement - L'élément HTML button cible 8 * @param mode string - 'default', 'unique' 9 * @param delay number - Délai pour l'application du display: none; Utile pour les animations CSS (non compatible avec l'animation 'slide') 10 * @param slide boolean - Ajoute une animation 'slide' 11 * @param slideDuration number - La vitesse de l'animation, requiert 'slide' à true 12 * @export {DisclosureButton, DisclosureButtonClose} 13 */ 14 15"use strict"; 16 17// Facultatif - permet d'animer le disclosure 18import {slideUp, slideDown} from './slideToggle.js'; 19 20export class DisclosureButton { 21 constructor(buttonNode, delay, slide, slideDuration, hasOutsideClickToCloseComponent = true, displayProperty = 'block') { 22 this.buttonNode = buttonNode; 23 this.controlledNode = ''; 24 this.slide = slide ?? this.buttonNode.getAttribute("data-slide") ?? false; 25 this.slideDuration = slideDuration ?? this.buttonNode.getAttribute("data-slide-duration") ?? 400; 26 this.delay = delay ?? this.buttonNode.getAttribute("data-delay") ?? 0; 27 this.id = this.buttonNode.getAttribute("aria-controls"); 28 this.hasOutsideClickToCloseComponent = hasOutsideClickToCloseComponent; 29 this.displayProperty = displayProperty; 30 31 if (this.id) { 32 this.controlledNode = document.getElementById(this.id); 33 } 34 35 // Only add aria-expanded if the button does not have the 'js-has-modal' class 36 if (!this.buttonNode.classList.contains('js-has-modal') && !this.buttonNode.hasAttribute('aria-expanded')) { 37 this.buttonNode.setAttribute("aria-expanded", "false"); 38 this.currentState = this.buttonNode.getAttribute('aria-expanded'); 39 } 40 else { 41 this.currentState = false; 42 } 43 44 // Check if controlledNode has the class 'is-visible' 45 if (this.controlledNode && this.controlledNode.classList.contains('is-visible')) { 46 this.currentState = true; 47 } 48 49 // On vérifie si l'attribut aria-expanded et à true et si l'élément à afficher n'est pas visible 50 // Dans ce cas, on affiche l'élément 51 if (this.isOpen() && !this.isVisible()) { 52 this.showContent() 53 } 54 55 // On applique les écouteurs d'événements 56 this.buttonNode.addEventListener("click", this.onClick.bind(this)); 57 this.buttonNode.addEventListener("focus", this.onFocus.bind(this)); 58 this.buttonNode.addEventListener("blur", this.onBlur.bind(this)); 59 this.buttonNode.addEventListener("keydown", this.onKeydown.bind(this)); 60 this.controlledNode.addEventListener("keydown", this.onKeydown.bind(this)); 61 } 62 63 /** 64 * Etat courant du bouton 65 */ 66 isOpen () { 67 return (this.currentState === 'true'); 68 } 69 70 /** 71 * Etat courant de l'affichage de l'élément controllé 72 */ 73 isVisible () { 74 return (window.getComputedStyle(this.controlledNode).display !== 'none'); 75 } 76 77 /** 78 * Affiche le contenu du nÅud controllé 79 */ 80 showContent() { 81 const self = this; 82 if (self.controlledNode && self.slide) { 83 new slideDown(self.controlledNode, self.slideDuration); 84 } else if (self.controlledNode) { 85 self.controlledNode.style.display = this.displayProperty; 86 // On rajoute un timeout insignifiant pour permettre à l'animation CSS d'être visible 87 setTimeout(function () { 88 self.controlledNode.classList.add('is-visible'); 89 }, 10); 90 } 91 this.currentState = true; 92 if (!this.buttonNode.classList.contains('js-has-modal')) { 93 this.buttonNode.setAttribute("aria-expanded", "true"); 94 } 95 else { 96 this.buttonNode.classList.add('is-opened'); 97 } 98 99 // On appelle la fonction outsideClick quand le composant est visible 100 if (this.hasOutsideClickToCloseComponent) { 101 this.outsideClick(this.buttonNode, this.controlledNode); 102 } 103 } 104 105 /** 106 * Cache le contenu du nÅud controllé 107 */ 108 hideContent() { 109 const controlledNode = this.controlledNode; 110 if (controlledNode && this.slide) { 111 new slideUp(controlledNode, this.slideDuration); 112 } else if (controlledNode) { 113 controlledNode.classList.remove('is-visible'); 114 setTimeout(function () { 115 controlledNode.style.display = "none"; 116 }, this.delay); 117 } 118 this.currentState = false;
119 if (!this.buttonNode.classList.contains('js-has-modal')) { 120 this.buttonNode.setAttribute("aria-expanded", "false"); 121 } 122 else { 123 this.buttonNode.classList.remove('is-opened'); 124 } 125 } 126 127 /** 128 * Gestions des événements du clavier 129 */ 130 onKeydown(event) { 131 switch(event.key) { 132 case 'Esc': 133 case 'Escape': 134 this.hideContent(); 135 } 136 } 137 138 /** 139 * Gestion du clic en dehors du composant 140 */ 141 outsideClick(element, controlledElement) { 142 const outsideClickListener = event => { 143 if (!element.contains(event.target) && !controlledElement.contains(event.target)) { 144 this.hideContent(); 145 removeClickListener(); 146 } 147 } 148 149 const removeClickListener = () => { 150 document.removeEventListener('click', outsideClickListener); 151 } 152 153 document.addEventListener('click', outsideClickListener); 154 } 155 156 /** 157 * Affiche/Cache le contenu du nÅud controllé en fonction de l'état précédent 158 */ 159 toggleExpand() { 160 if (this.buttonNode.getAttribute("aria-expanded") === "true" || this.buttonNode.classList.contains('is-opened')) { 161 this.hideContent(); 162 } else { 163 this.showContent(); 164 } 165 } 166 167 /* EVENT HANDLERS */ 168 169 onClick() { 170 this.toggleExpand(); 171 } 172 173 onFocus() { 174 this.buttonNode.classList.add("is-focus"); 175 } 176 177 onBlur() { 178 this.buttonNode.classList.remove("is-focus"); 179 } 180 181 /** la fonction destroy() remplace le nÅud actuel par un clone. Ce qui a pour effet d'enlever les événements attachés au préalable. 182 * Pensez à refaire un document.querySelector pour récupérer le nouveau nÅud créé. */ 183 destroy() { 184 const current = this.buttonNode; 185 const clone = current.cloneNode(true); 186 current.parentNode.replaceChild(clone, current); 187 } 188} 189 190/* 191 * @constructor CloseDisclosureButton 192 */ 193export class CloseDisclosureButton { 194 constructor(buttonNode) { 195 this.buttonNode = buttonNode; 196 this.id = this.buttonNode.getAttribute("aria-controls"); 197 198 this.buttonNode.addEventListener("click", this.onClick.bind(this)); 199 } 200 201 /* EVENT HANDLERS */ 202 203 onClick() { 204 if (!this.buttonNode.classList.contains('js-has-modal')) { 205 document.querySelector(`[aria-expanded="true"][aria-controls="${this.id}"]`)?.click(); 206 } 207 else { 208 document.querySelector(`.is-opened[aria-controls="${this.id}"]`)?.click(); 209 } 210 } 211 212 destroy() { 213 this.buttonNode.removeEventListener("click", this.onClick.bind(this)); 214 } 215}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.