การควบคุมกล่องโต้ตอบและป๊อปโอเวอร์แบบประกาศ

ฟีเจอร์บนเว็บ: คำสั่ง Invoker Popover <dialog>

ใช้ Invoker Commands API เพื่อสลับระดับการเข้าถึงขององค์ประกอบ <dialog> และ [popover] จากปุ่ม HTML โดยตรง ซึ่งช่วยลดความจำเป็นในการใช้ Listener เหตุการณ์ JavaScript ที่กำหนดเอง

เมื่อใช้แอตทริบิวต์ commandfor (รหัสเป้าหมาย) และ command (การดำเนินการ) กับ <button> เบราว์เซอร์จะจัดการการเปลี่ยนแปลงสถานะเปิด/ปิด การจัดการโฟกัส และการเชื่อมโยงการช่วยเหลือพิเศษ (เช่น aria-expanded) โดยอัตโนมัติ เราขอแนะนำให้ใช้วิธีการประกาศนี้เนื่องจากจะช่วยนำโค้ด Boilerplate ที่เปราะบางออก ช่วยให้มั่นใจได้ว่าการโต้ตอบจะทำงานได้ทันทีเมื่อแยกวิเคราะห์ HTML และรับประกันประสบการณ์ของผู้ใช้ที่มีประสิทธิภาพและเข้าถึงได้โดยค่าเริ่มต้น

การใช้ป๊อปโอเวอร์แบบประกาศ

คุณสามารถเปิดและปิดป๊อปโอเวอร์ได้โดยใช้ปุ่มเดียว

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

กลยุทธ์สำรอง

เนื่องจากคำสั่ง Invoker และป๊อปโอเวอร์ยังไม่ได้รับการรองรับอย่างแพร่หลาย คุณจึงต้องใช้ Polyfill เป็นทางเลือกสำรองสำหรับเบราว์เซอร์รุ่นเก่า

คำสั่ง Invoker ของ Polyfill

บังคับ: ตรวจหาการรองรับฟีเจอร์โดยตรวจสอบพร็อพเพอร์ตี้ commandForElement ในต้นแบบ HTMLButtonElement อย่าตรวจสอบออบเจ็กต์ window หรือ document คุณต้องนำเข้า Polyfill แบบไดนามิกเฉพาะเมื่อไม่มีฟีเจอร์เนทีฟเท่านั้น อย่าโหลด Polyfill โดยไม่มีเงื่อนไข บังคับ: ฟังเหตุการณ์ "command" โดยตรงในองค์ประกอบเป้าหมาย เนื่องจากเหตุการณ์ "command" แบบเนทีฟไม่แสดงเป็นบับเบิล

ตัวเลือกที่ 1: การใช้ Bundler ติดตั้ง Polyfill ผ่าน npm (npm install invokers-polyfill) วิธีนี้เหมาะสำหรับโปรเจ็กต์ที่ใช้ Bundler (เช่น 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');
}

ตัวเลือกที่ 2: การใช้ CDN สำหรับโปรเจ็กต์ที่ไม่มี Bundler ให้นำเข้า Polyfill แบบไดนามิกจาก 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>

ข้อจำกัดของ Polyfill สำหรับ Invoker บังคับ: Polyfill นี้ไม่จัดการสถานะ ARIA (เช่น aria-expanded) ของปุ่มคำสั่งเหมือนที่เบราว์เซอร์แบบเนทีฟทำ เราขอแนะนำอย่างยิ่งให้คุณจัดการสถานะเหล่านี้ด้วยตนเองเพื่อให้มั่นใจว่าเว็บไซต์ของคุณเข้าถึงได้อย่างเต็มรูปแบบ

หากระบบไม่รองรับ Invoker Commands API เหตุการณ์ command จะไม่เริ่มทำงาน เพื่อให้รองรับเบราว์เซอร์ที่ทันสมัยทั้งหมดได้อย่างเต็มรูปแบบ เราขอแนะนำให้ใช้ invokers-polyfill จาก https://github.com/keithamus/invokers-polyfill ผ่าน npm install หรือ CDN

Polyfill นี้รองรับการกระทำที่กำหนดเองอย่างเต็มรูปแบบ (เริ่มต้นด้วย --) และส่งเหตุการณ์ command เหมือนกับ API แบบเนทีฟทุกประการ

การนำเข้าแบบไดนามิก (การเพิ่มประสิทธิภาพ)

เพื่อประสิทธิภาพที่ดีที่สุด คุณควรโหลด Polyfill เฉพาะในกรณีที่เบราว์เซอร์ไม่รองรับ API ในตัว ซึ่งจะช่วยประหยัดแบนด์วิดท์และลดเวลาในการเรียกใช้สคริปต์สำหรับผู้ใช้ในเบราว์เซอร์ที่ทันสมัย

หมายเหตุ: Polyfill นี้ไม่ได้จัดการสถานะ ARIA (เช่น aria-pressed หรือ aria-expanded) สำหรับคำสั่งที่กำหนดเอง คุณต้องซิงค์สถานะเหล่านี้ด้วยตนเองใน Listener เหตุการณ์เพื่อให้มั่นใจว่าเว็บไซต์ของคุณเข้าถึงได้

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

การใช้โฆษณาสำรองด้วยตนเอง (รูปแบบดั้งเดิม)

หากไม่ต้องการใช้ Polyfill คุณสามารถใช้การมอบสิทธิ์เหตุการณ์เพื่อส่งเหตุการณ์และรีจิสทรีคำสั่งเพื่อจัดการการดำเนินการร่วมกันได้ นี่เป็นรูปแบบสถาปัตยกรรมที่พบบ่อยในการพัฒนา JavaScript แบบดั้งเดิมซึ่งยังคงมีประสิทธิภาพสูงและปรับขนาดได้

// 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 API ส่วนใหญ่เป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง แต่ลักษณะการทำงานที่กำหนดไว้ เช่น การโปรโมตเลเยอร์บนสุด การปิดเมื่อคลิกนอกองค์ประกอบ และการเรียกใช้ popovertarget ไม่มี CSS ที่เทียบเท่า เบราว์เซอร์รุ่นเก่าต้องใช้ Polyfill หรือการสำรองข้อมูลด้วยตนเองหากคุณไม่ต้องการจัดส่ง

Polyfill: หากต้องการรองรับแอตทริบิวต์ popover ในเบราว์เซอร์รุ่นเก่า ให้โหลด @oddbird/popover-polyfill แบบมีเงื่อนไข จำเป็น: ตรวจหาฟีเจอร์โดยการตรวจสอบพร็อพเพอร์ตี้ popover ใน HTMLElement.prototype และโหลด Polyfill เฉพาะเมื่อไม่มีการรองรับแบบเนทีฟ อย่าโหลดโดยไม่มีเงื่อนไข

เมื่อใช้ Bundler หรือแผนที่การนำเข้า ให้ทำดังนี้

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

หากไม่มี Bundler ให้นำเข้าจาก 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>

ข้อควรระวังเกี่ยวกับรูปแบบ: Polyfill ไม่สามารถกำหนดคลาสจำลอง :popover-open จริงได้ จึงใช้คลาส .\:popover-open แทน จำเป็น: รวมทั้ง 2 รายการเข้าด้วยกันโดยใช้ :is() หรือ :where() ไม่เช่นนั้นเบราว์เซอร์ที่ไม่มี :popover-open จะทิ้งกฎทั้งหมด

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

หรือสำหรับทางเลือกสำรองแบบเดิมที่ไม่มี Polyfill ให้ใช้ position: fixed และคำนวณพิกัดด้วยตนเองผ่าน getBoundingClientRect() หรือใช้การกำหนดตำแหน่งเริ่มต้นด้วย inset: auto หากยอมรับได้สำหรับกรณีการใช้งาน