PageSourceSearch

https://www.isere.fr/themes/custom/theme-eolas-base/eolas_base/assets/utils/disclosure-button.js

js isere.fr collected 2026-10-01 10:28:17 UTC 6,923 bytes, 215 lines download raw bytes

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.