Gebruik de Invoker Commands API om de zichtbaarheid van <dialog>- en [popover]-elementen rechtstreeks vanuit HTML-knoppen aan of uit te zetten. Zo heb je geen aangepaste JavaScript-gebeurtenislisteners meer nodig.
Door de kenmerken commandfor (doel-ID) en command (actie) toe te passen op een <button>, verwerkt de browser automatisch wijzigingen in de status openen/sluiten, focusbeheer en toegankelijkheidsbindingen (zoals aria-expanded). Deze declaratieve aanpak wordt aanbevolen omdat deze kwetsbare boilerplate-code verwijdert, ervoor zorgt dat interacties onmiddellijk functioneel zijn na HTML-parsering en een robuuste, native toegankelijke gebruikerservaring garandeert.
Declaratieve pop-overs implementeren
Pop-overs kunnen worden geopend en gesloten met één knop.
<!-- MANDATORY: The commandfor attribute links the invoker to the ID of the target element so the browser knows what to control. -->
<!-- MANDATORY: The command attribute specifies the action to perform. Use 'toggle-popover' to handle both open and close states automatically. -->
<button commandfor="my-popover" command="toggle-popover">
Toggle Popover
</button>
<!-- MANDATORY: The target element must have the popover attribute to be controlled as a popover. -->
<div id="my-popover" popover>
<p>Popover content goes here.</p>
</div>
Als je het openen en sluiten met afzonderlijke knoppen wilt bedienen, kun je de opdrachten show-popover en hide-popover gebruiken.
<!-- MANDATORY: Use 'show-popover' to explicitly open the popover. It will not close the popover if clicked again. -->
<button commandfor="my-explicit-popover" command="show-popover">
Show Popover
</button>
<div id="my-explicit-popover" popover="manual">
<p>This popover is explicitly opened and closed by separate buttons.</p>
<!-- MANDATORY: Use 'hide-popover' to explicitly close the targeted popover. -->
<button commandfor="my-explicit-popover" command="hide-popover">
Hide Popover
</button>
</div>
Declaratieve modale dialoogvensters implementeren
In tegenstelling tot pop-overs gebruiken modale dialoogvensters meestal afzonderlijke knoppen voor openen en sluiten. Gebruik de opdracht show-modal specifiek als je een dialoogvenster als modaal wilt openen.
<!-- MANDATORY: Use command="show-modal" to trigger the dialog as a modal, trapping focus and preventing interaction with the rest of the page. -->
<!-- MANDATORY: The commandfor attribute connects this button to the dialog ID. -->
<button commandfor="confirm-dialog" command="show-modal">
Open Confirmation
</button>
<dialog id="confirm-dialog">
<p>Are you sure you want to proceed?</p>
<!-- MANDATORY: Use command="close" to dismiss the dialog safely. -->
<button commandfor="confirm-dialog" command="close">
Cancel
</button>
</dialog>
Reservestrategieën
Omdat aanroeperopdrachten en pop-overs nog niet universeel worden ondersteund, MOET je polyfills gebruiken als fallbacks voor oudere browsers.
Polyfilling Invoker Commands
VERPLICHT: Detecteer ondersteuning voor de functie door te controleren op de property commandForElement in het prototype HTMLButtonElement. Check NIET het venster- of documentobject. Je MOET de polyfill alleen dynamisch importeren als de native functie ontbreekt. Laad de polyfill NIET onvoorwaardelijk.
Verplicht: Luister rechtstreeks naar de gebeurtenis 'command' op het doelelement, omdat de systeemeigen gebeurtenis 'command' niet bubbelt.
Optie 1: Een bundler gebruiken
Installeer de polyfill via npm (npm install invokers-polyfill). Deze aanpak is bedoeld voor projecten die een bundler (zoals Vite of Webpack) of importkaarten gebruiken. Gebruik voor alle andere instellingen de CDN-optie hieronder.
// MANDATORY: Feature detect 'commandForElement' on HTMLButtonElement.prototype.
// Conditionally load the invokers-polyfill only in browsers lacking native support.
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill');
}
Optie 2: Een CDN gebruiken
Voor projecten zonder bundelaar importeer je de polyfill dynamisch rechtstreeks vanuit een CDN in een <script type="module">.
<script type="module">
// MANDATORY: Feature detect 'commandForElement' on HTMLButtonElement.prototype.
// Conditionally load the invokers-polyfill from a CDN only in browsers lacking native support.
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('https://esm.run/invokers-polyfill');
}
</script>
Beperkingen van Invokers Polyfill
VERPLICHT: Deze polyfill verwerkt de ARIA-statussen (bijv. aria-expanded) van de opdrachtknop niet op dezelfde manier als systeemeigen browsers. We raden je sterk aan deze statussen zelf te verwerken om te zorgen dat je site volledig toegankelijk is.
Als de Invoker Commands API niet wordt ondersteund, wordt de gebeurtenis command niet geactiveerd. Voor volledige ondersteuning in alle moderne browsers raden we je aan de invokers-polyfill van https://github.com/keithamus/invokers-polyfill te gebruiken via npm install of CDN.
Deze polyfill ondersteunt aangepaste acties volledig (beginnend met --) en stuurt de gebeurtenis command op dezelfde manier als de native API.
Dynamisch importeren (prestatieoptimalisatie)
Voor de beste prestaties laad je de polyfill alleen als de browser de API niet standaard ondersteunt. Dit bespaart bandbreedte en verkort de uitvoeringstijd van scripts voor gebruikers in moderne browsers.
OPMERKING: Deze polyfill beheert geen ARIA-statussen (zoals aria-pressed of aria-expanded) voor aangepaste opdrachten. Je moet deze statussen handmatig synchroniseren in je event-listener om te zorgen dat je site toegankelijk is.
// 1. Conditionally load the polyfill
const hasNativeSupport = 'commandForElement' in HTMLButtonElement.prototype;
if (!hasNativeSupport) {
// Wrap in an async IIFE to avoid top-level await issues in older browsers
(async () => {
try {
await import('https://esm.run/invokers-polyfill');
} catch (err) {
console.error('Error loading fallback:', err);
}
})();
}
// 2. Manually manage ARIA states in your listener
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const source = event.source; // The button that triggered the command
if (command === '--spin') {
const isSpun = target.classList.toggle('is-spun');
// Polyfill tip: Manually update ARIA to match the new state
source?.setAttribute('aria-pressed', isSpun);
}
});
Handmatige terugval (traditioneel patroon)
Als u liever geen polyfill gebruikt, kunt u een combinatie van gebeurtenisdelegatie gebruiken om gebeurtenissen te verzenden en een opdrachtregister om de acties te verwerken. Dit is een veelgebruikt architectuurpatroon in traditionele JavaScript-ontwikkeling dat zeer efficiënt en schaalbaar blijft.
// 1. **Optional:** Define a registry of requested actions for cleaner logic
const commandRegistry = {
'--spin': (target) => target.classList.toggle('is-spun'),
'--grow': (target) => target.classList.toggle('is-grown'),
'--reset': (target) => target.classList.remove('is-spun', 'is-grown'),
};
// 2. If CommandEvent doesn't exist, we assume no native support and provide the fallback
if (!globalThis.CommandEvent) {
globalThis.CommandEvent = class CommandEvent extends Event {
constructor(type, { source, command, ...options } = {}) {
super(type, options);
this.source = source;
this.command = command;
}
}
}
// 3. The fallback: Dispatch events manually if native support is missing
document.addEventListener('click', (event) => {
const button = event.composedPath().find((el) => el.matches?.("button[commandfor]"));
if (!button) return;
const target = document.getElementById(button.getAttribute('commandfor'));
const command = button.getAttribute('command');
if (target && command) {
target.dispatchEvent(new CommandEvent('command', {
command,
source: button,
}));
}
});
// 4. **Mandatory:** Register the unified listener directly on the target element
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const action = commandRegistry[command];
if (action) {
action(target);
}
});
Fallbacks en browserondersteuning voor Popover
De Popover API is voornamelijk progressive enhancement, maar de bepalende gedragingen (promotie van de bovenste laag, licht sluiten en aanroepen van popovertarget) hebben geen equivalent dat alleen CSS gebruikt. Oudere browsers hebben een polyfill nodig, of een handmatige fallback als je er liever geen verzendt.
Polyfill: Als je het kenmerk popover wilt ondersteunen in oudere browsers, laad je @oddbird/popover-polyfill voorwaardelijk. VERPLICHT: Voer een functiedetectie uit door te controleren op de property popover op HTMLElement.prototype en laad de polyfill alleen als systeemeigen ondersteuning ontbreekt. Laad de polyfill NIET onvoorwaardelijk.
Met een bundler of importtoewijzing:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Zonder bundler importeer je vanuit een CDN in een <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Opmerking over de stijl: De polyfill kan de echte pseudoklasse :popover-open niet definiëren, dus wordt in plaats daarvan de klasse .\:popover-open toegepast. VERPLICHT: Combineer de 2 met :is() of :where(), anders negeren browsers zonder :popover-open de hele regel:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Als alternatief kun je voor een verouderde fallback zonder polyfill position: fixed gebruiken en de coördinaten handmatig berekenen via getBoundingClientRect() of vertrouwen op de standaardpositionering met inset: auto als dat acceptabel is voor de use case.