1import { Controller } from "@hotwired/stimulus"; 2 3// Modale d'ATTENTE pour une <dialog> chargée à la demande dans un turbo-frame. 4// 5// â ï¸ UNE MODALE QUI MET DES SECONDES à S'OUVRIR EST UNE MODALE QUI NE S'OUVRE 6// PAS. Le patron « frame vide + `qr-code-modal` qui ouvre la <dialog> dès 7// qu'elle est insérée » n'affiche RIEN tant que le serveur n'a pas répondu : le 8// frame est vide et hors écran. Sur « Rembourser », le serveur lit le SOLDE 9// Stripe de l'association - quelques secondes d'appel réseau pendant lesquelles 10// le clic ne produit aucun effet visible, et pendant lesquelles on reclique. 11// 12// Ce contrôleur ouvre TOUT DE SUITE une doublure (même en-tête, un spinner à la 13// place du contenu) et s'efface dès que le frame répond. 14// 15// â ï¸ DEUX FERMETURES, ET LES DEUX SONT NÃCESSAIRES. 16// 17// · `turbo:before-frame-render` est le chemin NOMINAL, et il est le seul à 18// tomber AVANT l'insertion du contenu - donc avant que la vraie <dialog> ne 19// s'ouvre. Deux modales ouvertes en même temps empilent deux fonds sombres : 20// l'écran clignote au moment précis où l'on regarde le montant. 21// 22// · L'attribut `busy` du frame est le FILET, et il couvre ce que l'événement 23// ne couvre pas : réseau tombé, réponse sans le frame attendu, session 24// expirée. Turbo le pose à l'ouverture de la requête et le retire quand 25// elle est finie, QUOI QU'IL SE PASSE - c'est le seul signal terminal dans 26// tous les cas. Il arrive après le rendu, d'où le premier. 27// 28// â ï¸ ET ELLE SE FERME à LA MAIN. Une doublure qu'on ne peut pas fermer piège 29// l'écran si un cas non prévu la laisse ouverte : elle porte donc son bouton 30// « Annuler » et sa fermeture au clic sur le fond, comme une vraie modale. Le 31// geste qu'elle couvre est un REMBOURSEMENT - un écran figé y coûte plus cher 32// qu'ailleurs. 33// 34// Usage : 35// <div data-controller="deferred-modal" 36// data-deferred-modal-frame-value="refund-modal-frame" 37// data-action="turbo:before-frame-render@document->deferred-modal#hide"> 38// <a href="â¦" data-turbo-frame="refund-modal-frame" 39// data-action="click->deferred-modal#show">Rembourser</a> 40// <dialog data-deferred-modal-target="modal" class="modal">â¦</dialog> 41// </div> 42export default class extends Controller { 43 static targets = ["modal"]; 44 // Id du turbo-frame surveillé. Sans lui, la doublure se fermerait sur le 45 // rendu de N'IMPORTE QUEL frame de la page - et la fiche don en porte 46 // plusieurs (drawer d'édition, onglets). 47 static values = { frame: String }; 48 49 connect() { 50 this.frameObserver = new MutationObserver(() => this.hideWhenFrameIdle()); 51 } 52 53 disconnect() { 54 this.frameObserver.disconnect(); 55 // La <dialog> vit dans le DOM de la page et le cache Turbo la restaure telle 56 // quelle : la laisser ouverte afficherait un spinner sur un écran qui ne 57 // charge plus rien. 58 this.close(); 59 } 60 61 show() { 62 if (!this.hasModalTarget || this.modalTarget.open) return; 63 64 this.modalTarget.showModal(); 65 66 // â ï¸ On observe SANS lire `busy` tout de suite. L'ordre entre ce clic et 67 // l'interception de Turbo n'est pas garanti : l'attribut peut n'être posé 68 // qu'après. On ne se fie donc qu'aux CHANGEMENTS - la pose puis le retrait. 69 const frame = this.watchedFrame; 70 if (frame) this.frameObserver.observe(frame, { attributes: true, attributeFilter: ["busy"] }); 71 } 72 73 hide(event) { 74 if (!this.concernsWatchedFrame(event)) return; 75 76 this.close(); 77 } 78 79 hideWhenFrameIdle() { 80 if (this.watchedFrame?.hasAttribute("busy")) return; 81 82 this.close(); 83 } 84 85 close() { 86 this.frameObserver?.disconnect(); 87 if (this.hasModalTarget && this.modalTarget.open) this.modalTarget.close(); 88 } 89 90 get watchedFrame() { 91 return this.frameValue ? document.getElementById(this.frameValue) : null; 92 } 93 94 concernsWatchedFrame(event) { 95 if (!this.frameValue) return true; 96 97 return event?.target?.id === this.frameValue; 98 } 99}
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.