Créer un composant de boîte de dialogue

Présentation générale de la création de mini et méga-modals adaptables aux couleurs, responsifs et accessibles avec l'élément <dialog>.

Dans ce post, je veux partager mes réflexions sur la façon de créer des couleurs adaptatives, Mini et méga-modales réactifs et accessibles avec l'élément <dialog>. Essayez la version de démonstration et visionnez la .

Démonstration des mégas et mini-boîtes de dialogue dans leurs thèmes clair et sombre.

Si vous préférez la vidéo, voici une version YouTube de cet article:

Présentation

La <dialog> convient parfaitement à une action ou à des informations contextuelles intégrées sur la page. Tenez compte du moment où l'expérience utilisateur peut bénéficier d'une même action sur une même page plutôt que sur plusieurs pages l'action: peut-être parce que le formulaire est de petite taille ou que la seule action requise de la part du confirmer ou annuler.

L'élément <dialog> est depuis peu stable dans tous les navigateurs:

Navigateurs pris en charge

  • 37
  • 79
  • 98
  • 15,4

Source

J'ai constaté que l'élément manquait quelques éléments, donc dans cette IUG Défi J'ajoute l'expérience développeur les éléments attendus: des événements supplémentaires, un effet de fond lumineux, des animations personnalisées et méga-type.

Majoration

Les éléments de base d'un élément <dialog> sont simples. L'élément sera sont automatiquement masquées et des styles sont intégrés pour se superposer à votre contenu.

<dialog>
  …
</dialog>

Nous pouvons améliorer cette référence.

Traditionnellement, un élément de boîte de dialogue partage beaucoup avec un modal et souvent les noms sont interchangeables. Je me suis permis d'utiliser l'élément de dialogue à la fois de petites fenêtres pop-up de boîte de dialogue (mini) et des boîtes de dialogue pleine page (méga). J'ai nommé méga et mini, les deux dialogues étant légèrement adaptés aux différents cas d'utilisation. J'ai ajouté un attribut modal-mode pour vous permettre de spécifier le type:

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

Capture d&#39;écran des mini et méga boîtes de dialogue dans les thèmes clair et sombre.

Pas toujours, mais les éléments de dialogue seront généralement utilisés pour rassembler des informations d'interaction. Les formulaires à l'intérieur des éléments de boîte de dialogue sont faits pour le déplacement. ensemble. Il est judicieux d'avoir un élément de formulaire encapsulant le contenu de la boîte de dialogue afin que JavaScript peut accéder aux données saisies par l'utilisateur. En outre, les boutons situés à l'intérieur un formulaire utilisant method="dialog" peut fermer une boîte de dialogue sans JavaScript et transmettre données.

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

Méga boîte de dialogue

Une boîte de dialogue méga contient trois éléments dans le formulaire: <header>, <article>, et <footer> Ceux-ci servent de conteneurs sémantiques et de cibles de style pour de la boîte de dialogue. L'en-tête intitule la modale et propose une conclusion . Cet article concerne les entrées et informations de formulaire. Le pied de page contient une <menu> sur boutons d'action.

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    <header>
      <h3>Dialog title</h3>
      <button onclick="this.closest('dialog').close('close')"></button>
    </header>
    <article>...</article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

Le premier bouton de menu a autofocus et un gestionnaire d'événements intégré onclick. L'attribut autofocus recevra quand la boîte de dialogue est ouverte, et je trouve qu'il est recommandé le bouton Annuler, pas sur le bouton de confirmation. Cela garantit que la confirmation est délibéré et non accidentel.

Mini-boîte de dialogue

La mini-boîte de dialogue est très semblable à la méga-boîte de dialogue, mais il lui manque simplement Élément <header>. Cela lui permet d'être plus petit et plus aligné.

<dialog id="MiniDialog" modal-mode="mini">
  <form method="dialog">
    <article>
      <p>Are you sure you want to remove this user?</p>
    </article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

L'élément de boîte de dialogue constitue une base solide pour un élément de fenêtre d'affichage complet peut collecter des données et les interactions des utilisateurs. Ces principes essentiels peuvent rendre des interactions intéressantes et efficaces sur votre site ou dans votre application.

Accessibilité

L'élément de boîte de dialogue offre une très bonne accessibilité intégrée. Au lieu d'ajouter ces fonctionnalités comme d’habitude, beaucoup existent déjà.

Restauration de la mise au point...

Comme nous l'avons fait manuellement dans la section Créer un panneau de navigation latéral , il est important que Le fait d'ouvrir et de fermer quelque chose permet de mettre l'accent sur l'ouverture et la fermeture correspondantes. . Lorsque ce panneau de navigation s'ouvre, le curseur est placé sur le bouton de fermeture. Lorsque lorsque l'utilisateur appuie sur le bouton de fermeture, le curseur est restauré sur le bouton qui l'a ouvert.

Pour l'élément de boîte de dialogue, il s'agit du comportement intégré par défaut:

Malheureusement, si vous voulez animer la boîte de dialogue à l'intérieur et à l'extérieur, cette fonctionnalité est perdu. Dans la section JavaScript, je vais le restaurer de Google Cloud.

Reprise de la mise au point

L'élément de boîte de dialogue gère inert sur le document. Avant le inert, JavaScript était utilisé pour surveiller le focus quittant un élément, auquel cas il l'intercepte et le replace.

Navigateurs pris en charge

  • 102
  • 102
  • 112
  • 15.5

Source

Après le inert, n'importe quelle partie du document peut être "figée" à condition qu'elles soient cibles ou qui sont interactives avec la souris. Au lieu de piéger le focus est dirigé vers la seule partie interactive du document.

Ouvrir un élément et effectuer la mise au point automatique

Par défaut, l'élément de boîte de dialogue sélectionne le premier élément sélectionnable dans le balisage de la boîte de dialogue. Si ce n'est pas le meilleur élément que l'utilisateur utilise par défaut, utilisez l'attribut autofocus. Comme indiqué précédemment, je pense qu'il est recommandé pour le mettre sur le bouton d'annulation et non sur le bouton de confirmation. Cela garantit que cette confirmation est délibérée et n'est pas accidentelle.

Fermer avec la touche Échap

Il est important de faciliter la fermeture de cet élément potentiellement intrusive. Heureusement, l'élément de boîte de dialogue gère automatiquement la touche Échap, ce qui vous permet de la charge de l'orchestration.

Styles

Il existe un moyen simple de styliser l'élément de boîte de dialogue et un chemin d'accès difficile. La facilité s'effectue en ne modifiant pas la propriété d'affichage de la boîte de dialogue, avec ses limites. Je m'efforce de fournir des animations personnalisées pour ouvrir et fermer la boîte de dialogue, en reprenant la propriété display, etc.

Appliquer un style avec des accessoires ouverts

Pour accélérer les couleurs adaptatives et la cohérence globale de la conception, j'ai importé ma bibliothèque de variables CSS Open Props. Dans en plus des variables fournies sans frais, j'importe aussi normalize ainsi que d'autres buttons. Tous deux OpenProps fournit en tant qu'importations facultatives. Ces importations m'aident à me concentrer sur la personnalisation une boîte de dialogue et une démonstration sans avoir besoin de beaucoup de styles pour la prendre en charge et lui donner l'apparence bien.

Appliquer un style à l'élément <dialog>

Posséder la propriété d'affichage

Le comportement d'affichage et de masquage par défaut d'un élément de boîte de dialogue active ou désactive l'affichage de block à none. Cela signifie malheureusement qu'il ne peut pas être animé à l'intérieur et à l'extérieur, uniquement à l'intérieur. j'aimerais effectuer une animation à l'intérieur et à l'extérieur. La première étape est définir ma propre display:

dialog {
  display: grid;
}

En modifiant, et donc en détenant la valeur de la propriété d'affichage, comme indiqué dans les au-dessus de l'extrait CSS, vous devez gérer une grande quantité de styles pour pour assurer une bonne expérience utilisateur. Tout d'abord, l'état par défaut d'une boîte de dialogue est fermé. Vous pouvez représenter cet état visuellement et empêcher la boîte de dialogue reçoivent des interactions avec les styles suivants:

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

La boîte de dialogue est désormais invisible et il n'est pas possible d'interagir avec elle lorsqu'elle n'est pas ouverte. Plus tard Je vais ajouter du code JavaScript pour gérer l'attribut inert dans la boîte de dialogue, en veillant à ce que que les utilisateurs de clavier et de lecteurs d'écran ne peuvent pas non plus accéder à la boîte de dialogue masquée.

Attribuer à la boîte de dialogue un thème de couleurs adaptatifs

Méga boîte de dialogue montrant les thèmes clair et sombre, montrant les couleurs de la surface.

Lorsque color-scheme active votre document dans un navigateur fourni en s'adaptant aux préférences système claires et sombres, l’élément de boîte de dialogue plus que cela. Open Props propose des surfaces de couleurs qui s'adaptent automatiquement des préférences système claires et sombres, de la même manière que lorsque vous utilisez color-scheme. Ces sont parfaits pour créer des calques dans une conception et j’aime utiliser la couleur pour m’aider qui illustrent visuellement cette apparence de surfaces de calques. La couleur d'arrière-plan est var(--surface-1); Pour vous placer au-dessus de cette couche, utilisez var(--surface-2):

dialog {
  …
  background: var(--surface-2);
  color: var(--text-1);
}

@media (prefers-color-scheme: dark) {
  dialog {
    border-block-start: var(--border-size-1) solid var(--surface-3);
  }
}

D'autres couleurs adaptatives seront ajoutées ultérieurement pour les éléments enfants, comme l'en-tête et pied de page. Je les considère comme supplémentaires pour un élément de dialogue, mais c’est très important dans pour créer une conception de dialogue convaincante et bien conçue.

Taille de la boîte de dialogue responsive

Par défaut, la boîte de dialogue délègue sa taille à son contenu, ce qui est généralement très bien. Mon objectif est de limiter max-inline-size à une taille lisible (--size-content-3 = 60ch) ou à 90% de la largeur de la fenêtre d'affichage. Ce garantit que la boîte de dialogue ne s'affiche pas d'un bord à l'autre sur un appareil mobile, sur un écran d'ordinateur de bureau difficile à lire. Ensuite, j'ajoute une max-block-size afin que la boîte de dialogue ne dépasse pas la hauteur de la page. Cela signifie également que nous spécifier l'emplacement de la zone déroulante de la boîte de dialogue, pour le cas où il s'agirait d'une grande "Dialog".

dialog {
  …
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

Vous voyez que j'ai max-block-size deux fois ? La première utilise 80vh, un objet physique bloc de fenêtre d'affichage. Ce que je veux vraiment, c'est que la boîte de dialogue reste dans un flux relatif, pour les utilisateurs internationaux, j'utilise donc la logique, la plus récente, prise en charge dans l'unité dvb dans la deuxième déclaration.

Positionnement de la méga-boîte de dialogue

Pour vous aider à positionner un élément de boîte de dialogue, il est utile de décomposer ses deux le fond en plein écran et le conteneur de la boîte de dialogue. Le fond doit couvrira tout, offrant un effet d'ombre pour confirmer que cette boîte de dialogue est au premier plan et le contenu derrière est inaccessible. Le conteneur de boîte de dialogue est libre de se centrer sur ce fond et prendre la forme dont son contenu a besoin.

Les styles suivants fixent l'élément de boîte de dialogue à la fenêtre, en l'étirant à chaque et utilise margin: auto pour centrer le contenu:

dialog {
  …
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
Styles de boîte de dialogue "Méga" mobiles

Pour les petites fenêtres d'affichage, le style de cette mégamodale pleine page est légèrement différent. Je La marge inférieure est définie sur 0. Le contenu de la boîte de dialogue s'affiche alors au bas de la la fenêtre d'affichage. Après quelques ajustements de style, je peux transformer la boîte de dialogue en fiche d'action, plus près des pouces de l'utilisateur:

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

Capture d&#39;écran des outils de développement avec superposition de l&#39;espacement des marges 
  sur les ordinateurs de bureau et les mobiles 
méga-boîte de dialogue lorsqu’ils sont ouverts.

Positionnement de la mini-boîte de dialogue

Lorsque j'utilise une fenêtre d'affichage plus grande, comme sur un ordinateur de bureau, j'ai choisi de placer les mini boîtes de dialogue par-dessus l'élément qui les a appelés. Pour cela, j'ai besoin de JavaScript. Vous trouverez technique que j'utilise cliquez ici, mais je pense que cela dépasse le cadre de cet article. Sans JavaScript, le une mini boîte de dialogue apparaît au centre de l'écran, tout comme la boîte de dialogue "méga".

Faites ressortir votre audience

Enfin, ajoutez du style à la boîte de dialogue pour qu'elle ressemble à une surface douce au-dessus de la page. La douceur est obtenue en arrondissant les coins des dialogues. La profondeur est obtenue avec l'une des ombres conçues avec soin d'Open Props. props:

dialog {
  …
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

Personnaliser le pseudo-élément Backdrop

J'ai choisi de travailler très légèrement avec le fond, en ajoutant simplement un effet de flou backdrop-filter à la boîte de dialogue Méga:

Navigateurs pris en charge

  • 76
  • 79
  • 103
  • 18

Source

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

J'ai également choisi d'effectuer une transition vers backdrop-filter, en espérant que les navigateurs permettra la transition de l'élément Backdrop à l'avenir:

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

Capture d&#39;écran de la méga boîte de dialogue superposée à un arrière-plan flouté avec des avatars colorés.

Bonus de style

J'appelle cette section « suppléments » car c'est davantage lié à l'élément de dialogue que l'élément de boîte de dialogue en général.

Confinement du défilement

Lorsque la boîte de dialogue est affichée, l'utilisateur peut toujours faire défiler la page située derrière, ce que je ne veux pas:

Habituellement, overscroll-behavior est la solution que j'utilise habituellement, mais conformément aux caractéristiques, cela n'a aucun effet sur la boîte de dialogue, car il ne s'agit pas d'un port de défilement, c'est-à-dire un conteneur de défilement. Il n'y a donc rien à empêcher. je pourrais utiliser JavaScript pour surveiller les nouveaux événements de ce guide, tels que "fermé" et « ouvert », et activer overflow: hidden sur le document, ou je pourrais attendre que :has() soit stable dans tous les navigateurs:

Navigateurs pris en charge

  • 105
  • 105
  • 121
  • 15,4

Source

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

Désormais, lorsqu'une grande boîte de dialogue est ouverte, le document HTML contient overflow: hidden.

La mise en page <form>

En plus d'être un élément très important pour recueillir l'interaction les informations de l'utilisateur, je les utilise ici pour mettre en page l'en-tête, le pied de page et de l'article. Avec cette mise en page, j'envisage d'articuler l'enfant de l'article comme que vous pouvez faire défiler. J'y arrive avec grid-template-rows L'élément d'article reçoit la valeur 1fr et le formulaire lui-même a la même valeur maximale comme élément de boîte de dialogue. Qu'est-ce qui permet de définir une hauteur et une taille de ligne fermes ? permet de contraindre l'élément de l'article et de le faire défiler en cas de dépassement:

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

Capture d&#39;écran des outils de développement superposant les informations de mise en page en grille sur les lignes.

Appliquer un style à la boîte de dialogue <header>

Le rôle de cet élément est de fournir un titre pour le contenu de la boîte de dialogue et l'offre un bouton de fermeture facile à trouver. Une couleur de surface lui est également attribuée pour faire apparaître se trouver derrière le contenu de l'article de la boîte de dialogue. Ces exigences conduisent à un Flexbox des éléments alignés verticalement et espacés par rapport à leurs bords, et certains des marges intérieures et des blancs pour laisser de la place au titre et aux boutons de fermeture:

dialog > form > header {
  display: flex;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  background: var(--surface-2);
  padding-block: var(--size-3);
  padding-inline: var(--size-5);
}

@media (prefers-color-scheme: dark) {
  dialog > form > header {
    background: var(--surface-1);
  }
}

Capture d&#39;écran des outils pour les développeurs Chrome superposant les informations de mise en page Flexbox sur l&#39;en-tête de la boîte de dialogue.

Appliquer un style au bouton de fermeture de l'en-tête

Étant donné que la démo utilise les boutons "Open Props" (Ouvrir les accessoires), le bouton de fermeture est personnalisé en un bouton centré sur une icône ronde comme ceci:

dialog > form > header > button {
  border-radius: var(--radius-round);
  padding: .75ch;
  aspect-ratio: 1;
  flex-shrink: 0;
  place-items: center;
  stroke: currentColor;
  stroke-width: 3px;
}

Capture d&#39;écran des outils pour les développeurs Chrome superposant les informations sur la taille et la marge intérieure du bouton de fermeture de l&#39;en-tête.

Appliquer un style à la boîte de dialogue <article>

L'élément article a un rôle particulier dans cette boîte de dialogue: il s'agit d'un espace destiné à défile dans le cas d'une boîte de dialogue allongée ou longue.

Pour ce faire, l'élément de formulaire parent a défini des limites qui définissent les contraintes à atteindre pour cet élément de l'article s'il reçoit trop grande. Définissez overflow-y: auto pour que les barres de défilement ne s'affichent que si nécessaire. contiennent des caractères de défilement avec overscroll-behavior: contain, et le reste seront personnalisés:

dialog > form > article {
  overflow-y: auto; 
  max-block-size: 100%; /* safari */
  overscroll-behavior-y: contain;
  display: grid;
  justify-items: flex-start;
  gap: var(--size-3);
  box-shadow: var(--shadow-2);
  z-index: var(--layer-1);
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: light) {
  dialog > form > article {
    background: var(--surface-1);
  }
}

Le rôle du pied de page est de contenir des menus de boutons d'action. Flexbox est utilisé pour d'aligner le contenu à la fin de l'axe aligné du pied de page, puis un espacement laisser de la place aux boutons.

dialog > form > footer {
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: dark) {
  dialog > form > footer {
    background: var(--surface-1);
  }
}

Capture d&#39;écran des outils pour les développeurs Chrome superposant les informations de mise en page Flexbox sur l&#39;élément de pied de page.

menu est utilisé pour contenir les boutons d'action de la boîte de dialogue. Elle utilise un code d'encapsulation mise en page Flexbox avec gap pour laisser de l'espace entre les boutons. Éléments de menu une marge intérieure, comme <ul>. Je supprime également ce style, car je n'en ai pas besoin.

dialog > form > footer > menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  padding-inline-start: 0;
}

dialog > form > footer > menu:only-child {
  margin-inline-start: auto;
}

Capture d&#39;écran des outils pour les développeurs Chrome superposant les informations Flexbox sur les éléments du menu de pied de page.

Animation

Les éléments de boîte de dialogue sont souvent animés, car ils entrent et sortent de la fenêtre. Intégrer dans les boîtes de dialogue un mouvement de soutien à l'entrée et à la sortie aide les utilisateurs s’orienter dans le flux.

Normalement, l'élément de boîte de dialogue ne peut être animé que vers l'intérieur, et non vers l'extérieur. En effet, le navigateur active la propriété display sur l'élément. Précédemment, le guide l'affichage en mode Grille et ne le configure jamais sur aucune. Cela vous permet de s'animent à l'intérieur et à l'extérieur.

Open Props est fourni avec de nombreuses images clés des animations, ce qui facilite l'orchestration doit être facile et lisible. Voici les objectifs de l'animation approche que j'ai adoptée:

  1. Le mouvement réduit est la transition par défaut, un fondu à l'ouverture et à la fermeture d'opacité simple.
  2. Si le mouvement est autorisé, des animations de glissement et de mise à l'échelle sont ajoutées.
  3. La mise en page responsive pour mobile de la méga boîte de dialogue est ajustée pour glisser vers l'extérieur.

Une transition par défaut sécurisée et efficace

Bien qu'Open Props ait des images clés pour créer un fondu à l'ouverture et à la fermeture, approche multicouche des transitions par défaut avec des animations d'images clés des mises à niveau potentielles. Précédemment, nous avons stylisé la visibilité de la boîte de dialogue avec opacité, orchestration de 1 ou 0 en fonction de l'attribut [open]. À entre 0% et 100%, indiquer au navigateur la durée et le type lissage de vitesse souhaité:

dialog {
  transition: opacity .5s var(--ease-3);
}

Ajouter du mouvement à la transition

Si l'utilisateur est d'accord avec les mouvements, les boîtes de dialogue "méga" et "mini" doivent glisser. à l'entrée et à la réduction à mesure qu'elles sortent. Pour ce faire, vous pouvez utiliser prefers-reduced-motion requête média et quelques accessoires ouverts:

@media (prefers-reduced-motion: no-preference) {
  dialog {
    animation: var(--animation-scale-down) forwards;
    animation-timing-function: var(--ease-squish-3);
  }

  dialog[open] {
    animation: var(--animation-slide-in-up) forwards;
  }
}

Adapter l'animation de sortie aux appareils mobiles

Plus tôt dans la section consacrée aux styles, le style "méga-boîte de dialogue" est adapté aux mobiles ces appareils s'apparentent davantage à une feuille d'action, comme si une petite feuille de papier avait glissé vers le haut à partir du bas de l'écran et reste fixé en bas. La balance l'animation de sortie n'est pas adaptée à ce nouveau design. quelques requêtes média et quelques Propositions ouvertes:

@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    animation: var(--animation-slide-out-down) forwards;
    animation-timing-function: var(--ease-squish-2);
  }
}

JavaScript

Vous devez ajouter plusieurs éléments avec JavaScript:

// dialog.js
export default async function (dialog) {
  // add light dismiss
  // add closing and closed events
  // add opening and opened events
  // add removed event
  // removing loading attribute
}

Ces ajouts sont nés de la volonté de fermer le champ de vision (en cliquant sur la boîte de dialogue un fond d'écran), des animations et d'autres événements pour accélérer les données du formulaire.

Ajout de la fonctionnalité Ignorer

Cette tâche est simple et complète un excellent élément de dialogue qui n'est pas en cours d'animation. L'interaction est obtenue en regardant les clics sur la boîte de dialogue et en tirant parti des événements bouillonnement pour évaluer l'élément sur lequel l'utilisateur a cliqué, close() s'il s'agit de l'élément de niveau supérieur:

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

Notez dialog.close('dismiss'). L'événement est appelé et une chaîne est fournie. Cette chaîne peut être récupérée par un autre code JavaScript pour obtenir des informations sur la façon dont la boîte de dialogue a été fermée. Vous constaterez que j'ai également fourni des chaînes fermées chaque fois que j'appelle la fonction à partir de différents boutons, afin de fournir à mon application des informations sur l’interaction de l’utilisateur.

Ajouter des événements de fermeture et de fermeture

L'élément de boîte de dialogue est fourni avec un événement de fermeture: il est émis immédiatement lorsque l'événement La fonction close() de boîte de dialogue est appelée. Puisque nous animons cet élément, Il est utile d'avoir des événements avant et après l'animation, pour récupérer données ou réinitialiser le formulaire de la boîte de dialogue. Je l'utilise ici pour gérer l'ajout inert sur la boîte de dialogue fermée, que j'utilise dans la démonstration pour le modifier la liste des avatars si l'utilisateur a envoyé une nouvelle image.

Pour ce faire, créez deux événements nommés closing et closed. Ensuite, écouter l'événement de fermeture intégré sur la boîte de dialogue. À partir de là, définissez la boîte de dialogue sur inert et envoyez l'événement closing. La prochaine étape consiste à attendre que des animations et des transitions pour terminer l'exécution sur la boîte de dialogue, puis déclencher Événement closed.

const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')

export default async function (dialog) {
  …
  dialog.addEventListener('close', dialogClose)
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

La fonction animationsComplete, également utilisée lors de la création d'un toast , renvoie une promesse basée sur le la réalisation de l'animation et les promesses de transition. C'est pourquoi dialogClose est une commande asynchrone function; il peut ensuite await la promesse est renvoyée et d’avancer en toute confiance vers l’événement de clôture.

Ajouter des événements d'ouverture et d'ouverture

Ces événements ne sont pas aussi faciles à ajouter, car l'élément de boîte de dialogue intégré pour fournir un événement ouvert comme il le fait avec « close ». J'utilise un MutationObserver pour mieux comprendre l'évolution des attributs de la boîte de dialogue. Dans cet observateur, Je surveillerai les modifications apportées à l'attribut "open" et je gérerai les événements personnalisés en conséquence.

De la même manière que nous avons commencé les événements de bouclage et de clôture, créez deux nouveaux événements appelé opening et opened. À l'endroit où nous avons précédemment écouté la fermeture de la boîte de dialogue cette fois, utilisez un observateur de mutation créé pour surveiller l'événement .

…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')

export default async function (dialog) {
  …
  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })
}

const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

La fonction de rappel de l'observateur de mutation est appelée lorsque la boîte de dialogue sont modifiés et fournissent la liste des modifications sous forme de tableau. Itérer sur l'attribut change et l'élément attributeName doit être ouvert. Ensuite, vérifiez Si l'élément possède ou non l'attribut: indique si la boîte de dialogue est désormais ouverte. S'il a été ouvert, supprimez l'attribut inert et placez le curseur. à un élément demandant autofocus ou le premier élément button trouvé dans la boîte de dialogue. Enfin, comme la formule de bouclage et l'événement fermé, envoyer l'événement d'ouverture immédiatement, attendre que les animations pour terminer, puis envoyer l'événement ouvert.

Ajouter un événement supprimé

Dans les applications monopages, les boîtes de dialogue sont souvent ajoutées et supprimées en fonction des routes ou d'autres besoins et états de l'application. Il peut être utile de nettoyer les événements ou des données lorsqu'une boîte de dialogue est supprimée.

Pour ce faire, vous pouvez utiliser un autre observateur de mutation. Cette fois, au lieu de observant des attributs sur un élément de boîte de dialogue, nous allons observer les enfants du corps et surveiller la suppression des éléments de boîte de dialogue.

…
const dialogRemovedEvent = new Event('removed')

export default async function (dialog) {
  …
  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })
}

const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

Le rappel de l'observateur de mutation est appelé chaque fois que des enfants sont ajoutés ou supprimés. à partir du corps du document. Les mutations spécifiques surveillées Les removedNodes qui ont le nodeName sur une boîte de dialogue. Si une boîte de dialogue a été supprimée, les événements de clic et de fermeture sont supprimés pour libérer de la mémoire, et l'événement personnalisé supprimé est envoyé.

Supprimer l'attribut de chargement

Pour empêcher l'animation de lecture de la boîte de dialogue de sortie lorsqu'elle est ajoutée à la page ou lors du chargement de la page, un attribut de chargement a été ajouté à la boîte de dialogue. La le script suivant attend la fin de l'exécution des animations de boîte de dialogue, puis supprime l'attribut. L'animation de la boîte de dialogue de cacher une animation autrement distrayante.

export default async function (dialog) {
  …
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

En savoir plus sur le problème permettant d'empêcher l'animation d'images clés lors du chargement de la page en cliquant ici.

L'union fait la force

Voici dialog.js dans son intégralité, maintenant que nous avons expliqué chaque section individuellement:

// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')
const dialogRemovedEvent = new Event('removed')

// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

// wait for all dialog animations to complete their promises
const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

// page load dialogs setup
export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
  dialog.addEventListener('close', dialogClose)

  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })

  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })

  // remove loading attribute
  // prevent page load @keyframes playing
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

Utiliser le module dialog.js

La fonction exportée du module s'attend à être appelée et transmise à une boîte de dialogue qui souhaite ajouter ces événements et cette fonctionnalité:

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

Les deux boîtes de dialogue ont été mises à jour avec le chargement de correctifs et d'autres événements à traiter.

Écouter les nouveaux événements personnalisés

Chaque élément de boîte de dialogue mis à niveau peut désormais écouter cinq nouveaux événements, comme ceci:

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

Voici deux exemples de gestion de ces événements:

const dialogOpening = ({target:dialog}) => {
  console.log('Dialog opening', dialog)
}

const dialogClosed = ({target:dialog}) => {
  console.log('Dialog closed', dialog)
  console.info('Dialog user action:', dialog.returnValue)

  if (dialog.returnValue === 'confirm') {
    // do stuff with the form values
    const dialogFormData = new FormData(dialog.querySelector('form'))
    console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))

    // then reset the form
    dialog.querySelector('form')?.reset()
  }
}

Dans la démonstration que j'ai créée avec l'élément "Dialog", j'utilise l'événement fermé les données du formulaire pour ajouter un nouvel élément avatar à la liste. Le timing est bon dans que l'animation de sortie de la boîte de dialogue est terminée, et que certains scripts sont animés. dans le nouvel avatar. Les nouveaux événements permettent d'orchestrer l'expérience utilisateur. peut être plus fluide.

Remarquez dialog.returnValue: cet élément contient la chaîne de fermeture transmise lorsque l'événement l'événement close() de la boîte de dialogue est appelé. Dans l'événement dialogClosed, il est essentiel de savoir si la boîte de dialogue a été fermée, annulée ou confirmée. Si c'est confirmé, le récupère les valeurs du formulaire et le réinitialise. La réinitialisation est utile pour Lorsque la boîte de dialogue s'affiche à nouveau, elle est vide et prête pour un nouvel envoi.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 😃

Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web.

Créer une démonstration, me envoyer des tweets et je l'ajouterai à la section des remix de la communauté ci-dessous.

Remix de la communauté

Ressources