Declarative Dialog and Popover Control

از Invoker Commands API برای تغییر وضعیت نمایان بودن عناصر <dialog> و [popover] مستقیماً از دکمه‌های HTML استفاده کنید و نیاز به شنوندگان رویداد جاوا اسکریپت سفارشی را ازبین ببرید.

با اعمال مشخصه‌های commandfor (شناسه هدف) و command (کنش) به <button>، مرورگر به‌طور خودکار تغییرات وضعیت باز/بسته، مدیریت تمرکز، و پیوندهای دسترس‌پذیری (مانند aria-expanded) را مدیریت می‌کند. این رویکرد اعلانی توصیه می‌شود زیرا کد متن استاندارد شکننده را حذف می‌کند، اطمینان حاصل می‌کند که تعاملات بلافاصله پس‌از تجزیه HTML کاربردی هستند، و تجربه کاربری قوی و دسترس‌پذیر به‌صورت بومی را تضمین می‌کند.

پیاده‌سازی Popover اظهاری

بااستفاده از یک دکمه می‌توان پنجره‌های پاپ‌اور را باز و بسته کرد.

<!-- 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>

اگر لازم است باز و بسته شدن را با دکمه‌های جداگانه کنترل کنید، می‌توانید از فرمان‌های show-popover و hide-popover استفاده کنید.

<!-- 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>

پیاده‌سازی کادرهای گفتگوی مدال اعلانی

برخلاف منوهای بالاپَر، کادرهای گفتگوی مدال معمولاً از دکمه‌های جداگانه‌ای برای باز و بسته شدن استفاده می‌کنند. از فرمان show-modal به‌طور خاص زمانی استفاده کنید که لازم است کادر گفتگویی را به‌صورت مدال باز کنید.

<!-- 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>

راهبردهای جایگزین

چون «فرمان‌های فراخواننده» و «پاپ‌اورها» هنوز به‌طور جهانی پشتیبانی نمی‌شوند، باید از شبیه‌سازهای مرورگر به‌عنوان جایگزین برای مرورگرهای قدیمی‌تر استفاده کنید.

Polyfilling Invoker Commands

اجباری: با بررسی ویژگی commandForElement در نمونه اولیه HTMLButtonElement، پشتیبانی را شناسایی کنید. شیء سند یا پنجره را بررسی نکنید. باید شبیه‌ساز مرورگر را به‌صورت پویا فقط زمانی وارد کنید که ویژگی بومی وجود نداشته باشد. شبیه‌ساز مرورگر را بدون قیدوشرط بار نکنید. اجباری: مستقیماً در عنصر هدف به رویداد «command» گوش دهید زیرا رویداد «command» بومی حباب‌سازی نمی‌کند.

گزینه ۱: استفاده از بسته‌بندی شبیه‌ساز مرورگر را ازطریق npm (npm install invokers-polyfill) نصب کنید. این رویکرد برای پروژه‌هایی است که از بسته‌بندی (مانند Vite یا Webpack) یا نقشه‌های واردات استفاده می‌کنند. برای همه راه‌اندازی‌های دیگر، از گزینه CDN در زیر استفاده کنید.

// 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');
}

گزینه ۲: استفاده از CDN برای پروژه‌های بدون بسته‌بندی، شبیه‌ساز مرورگر را مستقیماً از CDN داخل <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>

محدودیت‌های شبیه‌ساز مرورگر Invokers اجباری: این شبیه‌ساز مرورگر حالت‌های ARIA (مثلاً aria-expanded) دکمه فرمان را به‌گونه‌ای که مرورگرهای بومی انجام می‌دهند مدیریت نمی‌کند. اکیداً توصیه می‌شود که این حالت‌ها را خودتان مدیریت کنید تا مطمئن شوید سایتتان کاملاً دسترس‌پذیر است.

اگر Invoker Commands API پشتیبانی نشود، رویداد command اجرا نخواهد شد. برای پشتیبانی کامل در همه مرورگرهای مدرن، توصیه می‌شود از invokers-polyfill از https://github.com/keithamus/invokers-polyfill ازطریق npm install یا CDN استفاده کنید.

این شبیه‌ساز مرورگر به‌طور کامل از کنش‌های سفارشی (با شروع از --) پشتیبانی می‌کند و رویداد command را دقیقاً مانند API بومی ارسال می‌کند.

وارد کردن پویا (بهینه‌سازی عملکرد)

برای بهترین عملکرد، فقط درصورتی باید شبیه‌ساز مرورگر را بار کنید که مرورگر به‌طور بومی از میانای برنامه‌سازی کاربردی پشتیبانی نکند. این کار باعث صرفه‌جویی در پهنای باند و کاهش زمان اجرای اسکریپت برای کاربران در مرورگرهای جدید می‌شود.

توجه: این شبیه‌ساز مرورگر وضعیت‌های ARIA (مانند aria-pressed یا aria-expanded) را برای فرمان‌های سفارشی مدیریت نمی‌کند. باید این وضعیت‌ها را به‌صورت دستی در شنونده رویداد همگام‌سازی کنید تا مطمئن شوید سایتتان دسترس‌پذیر است.

// 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);
  }
});

جایگزین دستی (الگوی سنتی)

اگر ترجیح می‌دهید از شبیه‌ساز مرورگر استفاده نکنید، می‌توانید از ترکیبی از واگذاری رویداد برای ارسال رویدادها و ثبت فرمان برای مدیریت کنش‌ها استفاده کنید. این یک الگوی معماری رایج در توسعه سنتی جاوا اسکریپت است که همچنان بسیار کارآمد و مقیاس‌پذیر است.

// 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);
  }
 });

پشتیبانی مرورگر و بازگشت به حالت قبلی برای Popover

«میانای برنامه‌سازی کاربردی Popover» عمدتاً ارتقای پیش‌رو است، اما رفتارهای تعریف‌کننده آن (ارتقای لایه بالایی، رد کردن با کلیک در خارج از عنصر، و فراخوانی popovertarget) معادل CSS-only ندارند. مرورگرهای قدیمی‌تر به شبیه‌ساز مرورگر نیاز دارند، یا اگر ترجیح می‌دهید شبیه‌ساز مرورگر ارسال نکنید، به بازگشت دستی نیاز دارند.

شبیه‌ساز مرورگر: برای پشتیبانی از مشخصه popover در مرورگرهای قدیمی‌تر، @oddbird/popover-polyfill را به‌صورت شرطی بار کنید. اجباری: ویژگی را با بررسی کردن ویژگی popover در HTMLElement.prototype شناسایی کنید و شبیه‌ساز مرورگر را فقط زمانی بار کنید که پشتیبانی بومی وجود ندارد — آن را بدون قیدوشرط بار نکنید.

با بسته‌بندی یا نقشه واردات:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

بدون بسته‌بندی، از CDN داخل <script type="module"> وارد کنید:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

هشدار سبک‌بندی: شبیه‌ساز مرورگر نمی‌تواند کلاس کاذب واقعی :popover-open را تعریف کند، بنابراین به‌جای آن کلاس .\:popover-open را اعمال می‌کند. اجباری: این دو را با :is() یا :where() ترکیب کنید، درغیراین‌صورت مرورگرهایی که فاقد :popover-open هستند کل قانون را نادیده می‌گیرند:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

یا برای بازگشت به نسخه قدیمی بدون شبیه‌ساز مرورگر، از position: fixed استفاده کنید و مختصات را به‌صورت دستی ازطریق getBoundingClientRect() محاسبه کنید یا اگر برای مورد استفاده قابل‌قبول است، به موقعیت‌یابی پیش‌فرض با inset: auto تکیه کنید.