ডিক্লারেটিভ ডায়ালগ ও পপওভার কন্ট্রোল

HTML বোতাম থেকে সরাসরি <dialog> এবং [popover] এলিমেন্টের দৃশ্যমানতা টগল করতে Invoker Commands API ব্যবহার করুন, যার ফলে কাস্টম JavaScript ইভেন্ট লিসেনারের প্রয়োজন হয় না।

commandfor (টার্গেট আইডি) এবং command (অ্যাকশন) অ্যাট্রিবিউট <button>-এ প্রয়োগ করার মাধ্যমে, ব্রাউজার অটোমেটিক খোলা/বন্ধ করার স্ট্যাটাস পরিবর্তন, ফোকাস ম্যানেজমেন্ট এবং অ্যাক্সেসিবিলিটি বাইন্ডিং (যেমন aria-expanded) ম্যানেজ করে। এই ডিক্লারেটিভ অ্যাপ্রোচ সাজেস্ট করা হয় কারণ এটি ভঙ্গুর বয়লারপ্লেট কোড সরিয়ে দেয়, 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>

ফলব্যাক স্ট্র্যাটেজি

ইনভোকার কমান্ড এবং পপওভার এখনও সর্বজনীনভাবে কাজ করে না, তাই পুরনো ব্রাউজারের জন্য আপনাকে অবশ্যই ফলব্যাক হিসেবে পলিফিল ব্যবহার করতে হবে।

পলিফিলিং ইনভোকার কমান্ড

বাধ্যতামূলক: HTMLButtonElement প্রোটোটাইপে commandForElement প্রপার্টি চেক করে ফিচার শনাক্তকরণ সংক্রান্ত সহায়তা। উইন্ডো বা ডকুমেন্ট অবজেক্ট চেক করবেন না। নেটিভ ফিচার না থাকলে তবেই আপনাকে ডাইনামিক্যালি পলিফিল ইমপোর্ট করতে হবে। কোনও শর্ত ছাড়াই পলিফিল লোড করবেন না। বাধ্যতামূলক: টার্গেট এলিমেন্টে সরাসরি 'কমান্ড' ইভেন্ট শুনুন কারণ নেটিভ 'কমান্ড' ইভেন্ট বাবল করে না।

বিকল্প ১: বান্ডলার ব্যবহার করা 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 ব্যবহার করা বান্ডলার ছাড়া প্রোজেক্টের জন্য, <script type="module">-এর মধ্যে CDN থেকে সরাসরি পলিফিল ডাইনামিকভাবে ইমপোর্ট করুন।

<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 Polyfill-এর সীমাবদ্ধতা বাধ্যতামূলক: এই পলিফিল কমান্ড বোতামের ARIA স্টেট (যেমন, aria-expanded) নেটিভ ব্রাউজারের মতো করে ম্যানেজ করে না। আপনার সাইট যাতে সম্পূর্ণভাবে অ্যাক্সেসযোগ্য হয় তা নিশ্চিত করতে, আপনাকে এইসব স্টেট নিজে ম্যানেজ করার জন্য বিশেষভাবে উৎসাহিত করা হচ্ছে।

Invoker Commands API কাজ না করলে, command ইভেন্ট ট্রিগার হবে না। সব আধুনিক ব্রাউজার জুড়ে সম্পূর্ণ সহায়তা পেতে, npm install বা CDN-এর মাধ্যমে https://github.com/keithamus/invokers-polyfill থেকে invokers-polyfill ব্যবহার করার সাজেশন দেওয়া হয়।

এই পলিফিল কাস্টম অ্যাকশন (-- দিয়ে শুরু) সম্পূর্ণভাবে সাপোর্ট করে এবং নেটিভ API-এর মতো ঠিক একইভাবে 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);
  }
 });

পপওভারের জন্য ফলব্যাক ও ব্রাউজার সাপোর্ট

পপওভার API মূলত প্রোগ্রেসিভ এনহ্যান্সমেন্ট, তবে এর সংজ্ঞায়িত আচরণ — টপ-লেয়ার প্রোমোশন, লাইট-ডিসমিস এবং popovertarget ইনভোকেশন — শুধুমাত্র CSS-এর সমতুল্য নয়। পুরনো ব্রাউজারের ক্ষেত্রে পলিফিল বা ম্যানুয়াল ফলব্যাক প্রয়োজন হয়, যদি আপনি কোনওটিই শিপ করতে না চান।

Polyfill: পুরনো ব্রাউজারে popover অ্যাট্রিবিউট কাজ করানোর জন্য, শর্তসাপেক্ষে @oddbird/popover-polyfill লোড করুন। বাধ্যতামূলক: HTMLElement.prototype-এ popover প্রপার্টি চেক করে ফিচার শনাক্ত করুন এবং নেটিভ সাপোর্ট না থাকলে শুধুমাত্র তখনই পলিফিল লোড করুন — শর্তহীনভাবে এটি লোড করবেন না।

বান্ডলার বা ইমপোর্ট ম্যাপের মাধ্যমে:

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

বান্ডলার ছাড়া, <script type="module">-এর মধ্যে CDN থেকে ইমপোর্ট করুন:

<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-এর সাথে ডিফল্ট পজিশনিংয়ের উপর নির্ভর করুন।