ড্রপডাউন মেনু তৈরি করুন

ড্রপডাউন মেনু তৈরি করুন

এই নির্দেশিকায় যেকোনও পপ-আপ মেনু কভার করা হয়: ড্রপডাউন, প্রসঙ্গ, ওভারফ্লো এবং মেনু বার যা সেগুলি খোলে। অ্যাপ্লিকেশন কমান্ড মেনু হল একটি উদাহরণ, যেখানে আইটেমগুলি ইন্টারফেস পরিবর্তন করে (তৈরি, সেভ, পছন্দ)।

দুটি স্বতন্ত্র পছন্দ। এখানে অন্য সবকিছু দুটির ক্ষেত্রেই প্রযোজ্য।

  • এলিমেন্ট, আইটেম প্রতি। কমান্ড হল <button type="button">; গন্তব্য হল <a href> যা এখনও নতুন ট্যাবে খোলে, URL হিসেবে কপি করে এবং স্ক্রিপ্ট ছাড়াই কাজ করে। ড্রপডাউন এগুলি অবাধে মিশিয়ে দিতে পারে: দুটি লিঙ্ক এবং ছয়টি কমান্ড, অথবা একটি কমান্ড এবং ছয়টি লিঙ্ক, নিচে কিছুই পরিবর্তন করে না।
  • ইন্টার‍্যাকশন মডেল, ড্রপডাউন প্রতি। Home/End এবং Esc তীরচিহ্ন কী এটিকে একটি মেনু করে তোলে: ফোকাসগ্রুপ যোগ করুন, এটি প্রতিটি আইটেমে menuitem অনুমান করতে দিন, বোতাম বা লিঙ্ক যাই হোক না কেন এবং এটিকে একটির মতো দেখান (একটি অবিচ্ছিন্ন বার বা ড্রপডাউন ইন্ডিকেটর সহ একটি ট্রিগার; নিচে "মেনুবার বা আলাদা মেনু বোতাম" দেখুন)। ব্যবহারকারীরা Tab-এর মাধ্যমে যে লিঙ্কগুলি দেখেন তার একটি সংক্ষিপ্ত তালিকা হল নেভিগেশন: কোনও ফোকাসগ্রুপ, role="menu", role="menuitem" বা aria-haspopup="menu" নেই; পপওভার, Esc এবং ফোকাস রিস্টোরেশন রাখুন এবং নেভিগেশন বার গাইড দেখুন।

কন্টেন্ট কখনও ভূমিকা নির্ধারণ করে না। লিঙ্কের "প্রোডাক্ট" ড্রপডাউন একটি মেনু হতে পারে; কমান্ডের "অ্যাকাউন্ট" ড্রপডাউন নেভিগেশন হতে পারে না।

শুধুমাত্র একটি ট্যাব স্টপ ও তীরচিহ্ন-কী নেভিগেশন সহ কম্পোজিট উইজেটের জন্য মেনু প্যাটার্ন ব্যবহার করুন। <div focusgroup="menu nomemory"> ব্যবহার করুন, HTML <menu> এলিমেন্ট নয়। শেয়ার করা মার্কআপে role="menu" বা role="menuitem" যোগ করবেন না: নেটিভ focusgroup সেইসব ভূমিকা অনুমান করে এবং নির্দিষ্ট ভূমিকা সেই অনুমানকে ব্লক করে। নিচে কাজ করে না এমন ব্রাউজারের ফলব্যাকে শুধু সেগুলি যোগ করুন। nomemory প্রথম আইটেম থেকে আবার খোলা শুরু করে। Focusgroup ফোকাস সরায়; স্ক্রিপ্ট প্রিভিউ, অ্যাক্টিভেশন ও ফোকাস রিস্টোরেশন ম্যানেজ করে।

মেনু খুলুন ও পজিশন করুন

commandfor এবং command="toggle-popover"-এর সাহায্যে প্রতিটি ট্রিগারকে একটি কমান্ড ইনভোকার করুন। ব্রাউজার ক্লিক, Enter এবং Space-এ পপওভার খোলে এবং বন্ধ করে এবং ট্রিগারটিকে তার পপওভারের লাইট-ডিসমিস গ্রুপের মধ্যে রাখে, তাই খোলা ট্রিগারে ক্লিক করলে এটি বন্ধ হয়ে যায়। popovertarget বা popovertargetaction ব্যবহার করবেন না; এই প্যাটার্নটি পরিবর্তে commandfor ব্যবহার করে।

প্রতিটি পপওভার ট্রিগার হওয়ার সাথে সাথেই সেটি প্লেস করুন। ব্রাউজার শুধুমাত্র তখনই একটি অন্তর্নিহিত aria-details সম্পর্ক যোগ করে যখন পপওভারটি পরবর্তী ভাইবোন না হয়, তাই এই প্লেসমেন্ট অতিরিক্ত অ্যাট্রিবিউট ছাড়াই এটি এড়িয়ে যায়।

<!-- Command invoker: the browser toggles the popover on click, Enter, and Space -->
<button id="file-trigger" class="trigger" type="button" aria-haspopup="menu" aria-expanded="false"
  commandfor="file-menu" command="toggle-popover">
  File
</button>
<!-- Immediately after its trigger, so browsers add no implicit aria-details -->
<div id="file-menu" popover="auto">
  <!-- Nested, not on the popover: Chromium otherwise drops focusgroup keyboard behavior -->
  <div focusgroup="menu nomemory" aria-labelledby="file-trigger">
    <button type="button">New file</button>
    <button type="button">Save</button>
    <button id="export-trigger" type="button" aria-haspopup="menu" aria-expanded="false"
      commandfor="export-menu" command="toggle-popover">
      Export <span aria-hidden="true">›</span>
    </button>
    <!-- Submenu: popover="auto" directly after its trigger, inside the parent focusgroup -->
    <div id="export-menu" popover="auto">
      <div focusgroup="menu nomemory" aria-labelledby="export-trigger">
        <button type="button">PDF</button>
        <button type="button">Plain Text</button>
      </div>
    </div>
  </div>
</div>

প্রতিটি মেনুর নাম দিন এবং aria-labelledby-এর মাধ্যমে সেটির ট্রিগার নির্দেশ করুন। CSS-এ আলঙ্কারিক সাবমেনু ইন্ডিকেটর রাখুন অথবা aria-hidden="true" যোগ করুন। aria-expanded="true"-এর সাথে ট্রিগারকে একটি দৃশ্যমান অ্যাক্টিভ স্টাইল দিন যাতে ব্যবহারকারীরা বলতে পারেন কোন মেনু খোলা বা প্রিভিউ করা হয়েছে।

টপ-লেভেল ট্রিগারের জন্য একটি ফর্ম বেছে নিন। ভূমিকা, কীবোর্ড মডেল এবং স্টাইলিং অবশ্যই মিলতে হবে: দৃশ্যত আলাদা বোতাম প্রতিটি একটি Tab স্টপকে নির্দেশ করে।

  • মেনুবার। role="menubar" কন্টেনারে (পপওভারে কখনও নয়), ট্রিগারের মধ্যে কোনও ফাঁক না রেখে একটি অবিচ্ছিন্ন বার হিসাবে স্টাইল করা হয়েছে। একটি Tab স্টপ; ArrowLeft/ArrowRight ট্রিগারের মধ্যে সরান; হোভার সুইচ মেনু খোলে। অ্যাপ্লিকেশন কমান্ড বারের জন্য ব্যবহার করুন।
  • আলাদা মেনু বোতাম। প্রতিটি ট্রিগার হল নিজস্ব Tab স্টপ এবং এটি একটি স্বতন্ত্র বোতামের মতো দেখায়। কোনও role="menubar" নেই, ট্রিগারের মধ্যে কোনও তীরচিহ্ন নেই, ফোকাসে কোনও প্রিভিউ নেই, হোভার সুইচিং নেই। নেভিগেট করলে <nav>-এ র‍্যাপ করুন, অন্যথায় কোনও ভূমিকা নেই। টুলবার ও পৃষ্ঠা হেডারের জন্য ব্যবহার করুন।

এই গাইডের বাকি অংশে মেনুবার ফর্ম সম্পর্কে বর্ণনা করা হয়েছে। আলাদা বোতামের জন্য, প্রতিটি মেনুর মধ্যে সবকিছু রাখুন এবং মেনুবার-লেভেল আচরণ ড্রপ করুন।

CSS অ্যাঙ্কর পজিশনিংয়ের মাধ্যমে মেনুর অবস্থান ঠিক করা

CSS অ্যাঙ্কর পজিশনিং ব্যবহার করুন, জাভাস্ক্রিপ্ট কোঅর্ডিনেট নয়। command="toggle-popover" এবং showPopover({ source: trigger }) উভয়ই একটি ইমপ্লিসিট অ্যাঙ্কর সেট করে, তাই প্রতি-মেনু anchor-name বা position-anchor নিয়ম যোগ করবেন না। মেনুর আচরণ অ্যাঙ্কর পজিশনিংয়ের উপর নির্ভর করলে চলবে না।

/* Top-level menus sit below their trigger. The implicit anchor comes from the invoker or source. */
[popover] {
  position-area: block-end span-inline-end;
  inset: auto;
}

/* A popover nested in another popover is a submenu: beside its parent item, flipping when it would overflow. */
[popover] [popover] {
  position-area: inline-end span-block-end;
  position-try-fallbacks: flip-inline, flip-block, --below;
}

/* Last resort for narrow viewports: directly below the trigger, indented, so the submenu never
   covers its trigger. */
@position-try --below {
  position-area: block-end span-inline-end;
  margin-inline-start: 1rem;
  /* Fit the area: the submenu's minimum width must not stop this fallback from fitting. */
  min-inline-size: 0;
  max-inline-size: calc(100% - 1rem);
}

--below-কে শেষে রাখুন যাতে কোনও দিকই ফিট না হলে তবেই এটি প্রযোজ্য হয়। এটি সরাসরি ট্রিগারের নিচে খোলে, তাই একটি সংকীর্ণ স্ক্রিনে এটি ট্রিগারের নিচে থাকা একই ধরনের আইটেমগুলিকে কভার করে; ট্রিগারের পাশে থাকার জন্য এটি গ্রহণযোগ্য ট্রেড-অফ। এছাড়াও, সাবমেনুকে (min-inline-size: 0, max-inline-size মাইনাস ইন্ডেন্ট) এরিয়াতে সঙ্কুচিত হতে দিন। উপযুক্ত নয় এমন ফলব্যাক এড়িয়ে যাওয়া হয় এবং কোনওটিই উপযুক্ত না হলে ব্রাউজার বেস প্লেসমেন্ট বজায় রাখে, তাই কয়েক পিক্সেল বেশি চওড়া সাবমেনু নিঃশব্দে এর ট্রিগার কভার করে।

সাধারণ পপওভারে ফ্লিপ ফলব্যাক কীভাবে কাজ করে তা জানতে, ভিউ পোর্ট-অ্যাওয়ার ড্রপডাউন দেখুন। সেই নির্দেশিকা একটি বোতাম গ্রুপকে পজিশন করে, কোনও মেনুকে নয়; মেনুর ক্ষেত্রে, সাবমেনু প্লেসমেন্ট এবং উপরে --below ফলব্যাক অগ্রাধিকার পায়।

অ্যানিমেট খোলা ও বন্ধ করা

ঐচ্ছিক। ১৫০ মিলিসেকেন্ডের কম, opacity এবং শুধু একটি ছোট translate, সাবমেনুর ক্ষেত্রেও একই, @media (prefers-reduced-motion: no-preference)-এর মধ্যে। toggle-এ ফোকাস সরে যায়, তাই ট্রানজিশন কখনও কীবোর্ড ব্যবহারকারীদের দেরি করায় না।

@media (prefers-reduced-motion: no-preference) {
  /* Closing: this rule is in effect once :popover-open is removed. */
  [popover] {
    opacity: 0;
    translate: 0 -.25rem;
    transition: opacity .15s, translate .15s, display .15s allow-discrete, overlay .15s allow-discrete;
  }
  /* Opening: faster. */
  [popover]:popover-open {
    opacity: 1;
    translate: 0 0;
    transition-duration: .1s;
    @starting-style { opacity: 0; translate: 0 -.25rem; }
  }
  /* Switching between open menubar menus is instant, as in native menubars. */
  [role="menubar"]:has(> [popover]:popover-open) > [popover] {
    transition: none;
  }
}

@starting-style হল [popover]-এর পুনরাবৃত্তি নয়: বন্ধ করা পপওভার হল display: none এবং ট্রানজিশন রেন্ডার না করা অবস্থা থেকে রান করে না, তাই এটি খোলার জন্য আগের ভ্যালু প্রদান করে। এটি ছাড়া, খোলা স্ন্যাপ এবং শুধুমাত্র বন্ধ অ্যানিমেট করে। display এবং overlay-এর সাথে allow-discrete, বন্ধ করার ট্রানজিশন শেষ না হওয়া পর্যন্ত পপওভার রেন্ডার করে এবং উপরের লেয়ারে রাখে।

কীবোর্ড ও ফোকাস

ফোকাসগ্রুপের মধ্যে দ্বিতীয় roving-tabindex সিস্টেম যোগ করবেন না। স্ক্রিপ্ট শুধুমাত্র ইনভোকার এবং ফোকাসগ্রুপ যা করে না তা কভার করে:

  • মেনুবারের জন্য একটি ট্যাব স্টপ। Tab প্রথম টপ-লেভেল ট্রিগারে প্রবেশ করে। ArrowLeft এবং ArrowRight টপ-লেভেল ট্রিগারের মধ্যে পরিবর্তন করে এবং যেকোনও প্রান্তে র‍্যাপ করে। ট্রিগারে Home এবং End প্রথম বা শেষ আইটেমে এর মেনুতে প্রবেশ করে।
    • মনে রাখবেন: কোন ট্রিগার Tab-এ ফিরে আসে। APG-তে প্রথম বা শেষ ট্রিগার ব্যবহার করা যায়। গাইড প্রথমটি নির্দিষ্ট করে, কিন্তু ডেমো ArrowLeft এবং ArrowRight-এর সাথে ট্যাব স্টপ সরায়, তাই কীবোর্ড নেভিগেশনের পরে Shift+Tab শেষ ট্রিগারে ফিরে আসে। পয়েন্টার ব্যবহার করলে এটি সরে যায় না, তাই একই ক্রম অন্য ট্রিগারে ল্যান্ড করতে পারে। যেকোনও একটি আচরণ গ্রহণযোগ্য; একটি বেছে নিন এবং সেটি সামঞ্জস্যপূর্ণ রাখুন।
  • প্রিভিউ। টপ-লেভেল ট্রিগারে ফোকাস করলে, এটি মেনু দেখায় এবং aria-expanded="true" সেট করে। ফোকাস ট্রিগারে থাকে এবং কোনও আইটেম ফোকাস করা হয় না। ট্রিগার অস্পষ্ট করলে শুধু প্রিভিউয়ের জন্য উপলভ্য মেনু বন্ধ হয়ে যায়।
  • প্রিভিউতে Enter ও Space কী প্রেস করুন। তারা প্রিভিউ বন্ধ করে দেয় এবং ট্রিগারে ফোকাস বজায় রাখে। নেটিভ toggle-popover অ্যাকশন এটি ম্যানেজ করে, তাই স্ক্রিপ্টকে অবশ্যই এইসব কী ইন্টারসেপ্ট করলে চলবে না।
  • প্রিভিউতে ArrowDown প্রেস করুন। এটি একটি খোলা মেনুতে রূপান্তর করে এবং প্রথম আইটেমে ফোকাস করে।
  • খোলা বা প্রিভিউ করা ট্রিগারে পয়েন্টার ক্লিক। এটি নেটিভভাবে বন্ধ করে; এটি মেনুতে প্রবেশ করে না।
    • সাবমেনু ট্রিগারে পরিচিত পার্থক্য। সাবমেনু ট্রিগারে পয়েন্টার ক্লিক করলে সেটিও নেটিভভাবে টগল করে, তাই এটি এমন সাবমেনু বন্ধ করতে পারে যা হোভার আগে থেকেই প্রিভিউ করেছে অথবা এমন একটি খুলতে পারে যা এখনও দেখা যায়নি। নেটিভ মেনু শুধুমাত্র এটি খোলে এবং কখনও বন্ধ করে না। এটি ক্ষতিকর নয়, তাই গাইড এটি গ্রহণ করে।
  • বন্ধ থাকা ট্রিগারে পয়েন্টার ক্লিক। এটি নেটিভভাবে খোলে। ফোকাস ট্রিগারে থাকে; কোনও আইটেম ফোকাস করা হয় না। শুধুমাত্র কীবোর্ড খুললে মেনুতে প্রবেশ করুন।
  • কোনও আইটেমের উপর পয়েন্টার। রিং ছাড়াই এটি ফোকাস করে, তাই হোভার এবং ফোকাস হল একটি আইটেম।
  • মেনু খোলা থাকাকালীন অন্য টপ-লেভেল ট্রিগারের উপর পয়েন্টার। এটি বন্ধ করে এবং যেটির উপর মাউস নিয়ে যাওয়া হয়েছে সেটির প্রিভিউ দেখায়, রিং ছাড়াই এর ট্রিগার ফোকাস করে। কিছুই খোলা নেই: হোভার করলে কিছু হয় না।
  • সাবমেনু ট্রিগারের উপর পয়েন্টার। কিছুক্ষণ পরে এর সাবমেনু প্রিভিউ করে (পয়েন্টার ফোকাস একই প্রিভিউ-অন-ফোকাস রান করে)। একই ধরনের আইটেম এটি বন্ধ করে দেয়।
  • মেনুর মধ্যে। ArrowUp এবং ArrowDown চালু থাকা আইটেমের মধ্যে পরিবর্তন করে। Home এবং End প্রথম এবং শেষ চালু করা আইটেমে চলে যায়, যা স্ক্রিপ্ট ম্যানেজ করে।
  • খোলা টপ-লেভেল মেনুতে ArrowLeft বা ArrowRight. এটি বন্ধ করে এবং ট্রিগারে ফোকাস করে সংলগ্ন মেনুর প্রিভিউ দেখায়, যেকোনও প্রান্তে র‍্যাপ করে। ArrowDown তারপর এটি লিখে। বাঁদিক ও ডানদিক কখনও মেনুর মধ্যে থাকে না।
  • সাবমেনু ট্রিগারে ArrowRight. সাবমেনু খোলে এবং এর প্রথম আইটেমে ফোকাস করে।
  • চাইল্ড সাবমেনু ছাড়া সাবমেনু আইটেমে ArrowRight। হায়ারার্কি বন্ধ করে এবং পরবর্তী টপ-লেভেল মেনুর প্রিভিউ দেখায়, তবে ফোকাস ট্রিগারেই থাকে। ArrowDown তারপর এটি লিখে।
  • সাবমেনুতে ArrowLeft বা Escape. শুধুমাত্র সেই সাবমেনু বন্ধ করে এবং এর ট্রিগারে ফোকাস ফিরিয়ে আনে। অভিভাবক মেনু খোলা থাকে।
  • কোনও কমান্ডে Enter বা Space প্রেস করুন। এটি চালু করে এবং খোলা থাকা সব মেনু বন্ধ করে দেয়।
  • ট্যাব। মেনু সিস্টেম থেকে বেরিয়ে যায়। ফোকাস ট্র্যাপ করবেন না বা প্রতিটি কমান্ডের মাধ্যমে স্টেপ করবেন না। বন্ধ করার সময় লুকানো কন্টেন্টের মধ্যে ফোকাস থাকলে চলবে না।

toggle-popover মেনু খোলার আগে command ইভেন্ট ফায়ার করে, তাই সেখানে মেনুতে ফোকাস সরানো যায় না। পরিবর্তে toggle ইভেন্টে ফোকাস সরান। HTML শুধুমাত্র স্ট্যাকের প্রথম popover="auto"-এর জন্য ফোকাস ফিরিয়ে আনে, তাই নেস্টেড লেভেলকে অবশ্যই তাদের ট্রিগারে স্পষ্টভাবে ফোকাস ফিরিয়ে আনতে হবে।

ইন্টার‍্যাকশন প্রয়োগ করা

আপনার নিজের মার্কআপের জন্য উপরের আচরণগুলি প্রয়োগ করুন। তীরচিহ্ন, Home/End, র‍্যাপ-অ্যারাউন্ড এবং Tab-টু-এক্সিট হ্যান্ডলিং হল সাধারণ keydown কোড। কিছু অংশ ভুল হওয়ার সম্ভাবনা বেশি, ইনপুটের ধরন অনুযায়ী নিচে গ্রুপ করা হয়েছে।

কীবোর্ড ও ফোকাস হ্যান্ডলিং

  1. একটি খোলা পাথ। কীবোর্ড কলের মাধ্যমে খোলা হচ্ছে trigger.click(), তাই ইনভোকারের toggle-popover হল একমাত্র উপায় যার মাধ্যমে মেনু খোলা যায়। মেনু চিহ্নিত করুন যাতে toggle হ্যান্ডলার এটিতে প্রবেশ করে, শুধুমাত্র কীবোর্ড খোলে। পয়েন্টার খোলা থাকলে, toggle রিং ছাড়াই ট্রিগারে ফোকাস করে, কারণ pointerdown ফোকাস করতে বাধা দেয় এবং তীরচিহ্ন 'কী' অবশ্যই কাজ করবে।
  2. ফোকাসে প্রিভিউ। ট্রিগার ফোকাস করলে showPopover({ source: trigger }) কল করে যাতে মেনু এতে অ্যাঙ্কর করে। ট্রিগারের নিজস্ব মেনুর ভিতর থেকে বা অন্য উইন্ডো থেকে ফিরে আসা ফোকাস উপেক্ষা করুন (উইন্ডো blur-এ ফ্ল্যাগ, উইন্ডো focus-এর পরে টিক চিহ্ন মুছে ফেলা বা অ্যাপ-সুইচিং শেষ মেনু আবার খোলে)। ফোকাস সরানো না হলে blur-এ প্রিভিউ করা মেনু লুকান।
  3. command-এ নয়, toggle-এ ফোকাস সরান। মেনু খোলার আগে command ফায়ার হয়।
  4. ট্রিগারে ফোকাস ফিরিয়ে আনার কাজটি নিজেই করুন। hidePopover()-এর আগে ট্রিগারে ফোকাস করুন। পয়েন্টার অ্যাক্টিভেশনের পরে, রিং (focus({ focusVisible: false })) ছাড়াই ফোকাস ফিরিয়ে আনুন অথবা মাউস ক্লিক করার পরে ট্রিগার কীবোর্ড ফোকাস আউটলাইন দেখায়; কীবোর্ড অ্যাক্টিভেশন রিং দেখায়। event.detail === 0-এর সাথে ক্লিক কীবোর্ড থেকে এসেছে।
  5. একবারে একটি লেভেল বন্ধ করুন। সাবমেনুতে Escape এবং ArrowLeft-এ stopPropagation() কল করুন অথবা পেরেন্টের লিসেনারও এটি বন্ধ করে দেয়।

পয়েন্টার ম্যানেজ করা

  1. পয়েন্টার ক্লিক টগল। pointerdown-এ preventDefault() কল করুন যাতে ক্লিক করলে ফোকাস সরে না যায় এবং ক্লিক করার মাঝপথে প্রিভিউ শুরু হয়। কোন ট্রিগার প্রেস করা হয়েছে তা মনে রাখুন। বন্ধ বা প্রিভিউ করা ট্রিগারে ক্লিক করলে সেটি নেটিভভাবে খোলে বা বন্ধ হয় এবং কখনও মেনুতে প্রবেশ করে না; শুধুমাত্র কীবোর্ড খুললে এটি প্রবেশ করে। ক্লিক ইন্টারসেপ্ট করবেন না।
  2. ফোকাস অনুসরণ করে হোভার করুন। :hover ও :focus-visible আলাদাভাবে স্টাইল করুন। pointermove-এ কোনও আইটেমের উপর focus({ focusVisible: false }) কল করুন; যেসব ব্রাউজার বিকল্পটি উপেক্ষা করে সেগুলি পয়েন্টার ফোকাসের জন্য কোনও রিং আঁকে না।
  3. হোভার সুইচ মেনু খোলে। অন্য একটি টপ-লেভেল মেনু খোলা থাকাকালীন টপ-লেভেল ট্রিগারের pointerenter-এ: এটি লুকান, রিং ছাড়াই হোভার করা ট্রিগারে ফোকাস করুন এবং ফোকাসে প্রিভিউ (উপরে দেখুন) এটি প্রিভিউ করে। কিছুই খোলা নেই: কিছুই করবেন না।
  4. নিরাপদ ত্রিভুজ। একটি নির্দিষ্ট CSS ব্রিজ খুব সংকীর্ণ: এটি শুধুমাত্র তখনই কাজ করে যখন পয়েন্টারটি প্যারেন্টের প্রান্তের কাছাকাছি শুরু হয়। CSS-কে ব্রিজ আঁকতে দিন এবং পয়েন্টার রিপোর্ট করার জন্য শুধু স্ক্রিপ্ট ব্যবহার করুন:
    • শুধুমাত্র matchMedia('(hover: hover)').matches হলে এটি চালান; এটি টাচ ও কীবোর্ডের সাথে প্রাসঙ্গিক নয়।
    • পয়েন্টার সাবমেনু ট্রিগার থেকে সরে গেলে শুরু করুন, যার সাবমেনু খোলা আছে। --below সাবমেনু এড়িয়ে যান, যার কোনও সাইড এজ নেই।
    • সাবমেনুতে --bridge-x ও --bridge-y (সাবমেনুর সাপেক্ষে px-এ পয়েন্টার), --bridge-edge (সাবমেনুর কাছাকাছি-প্রান্ত x: পয়েন্টারটি এর বাঁদিকে থাকলে 0, এটি ফ্লিপ করলে এর প্রস্থ) এবং --bridge-height সেট করুন এবং ব্রিজ চালু করতে data-bridge সেট করুন।
    • পয়েন্টার সাবমেনুর কোনও আইটেমে পৌঁছালে বা সাবমেনু বন্ধ হয়ে গেলে, প্রায় ৩০০ মিলি সেকেন্ড পরে বন্ধ করুন। লিসেনার ও অ্যাট্রিবিউট সরিয়ে দিন। এর পরে আর কিছু রান করে না।
    • CSS সাবমেনুকে একটি ::before দেয় যা পয়েন্টার থেকে কাছাকাছি কোণ পর্যন্ত একটি ত্রিভুজে ক্লিপ করা থাকে। এটি পয়েন্টারের পাথে থাকা একই ধরনের আইটেমের উপরে থাকে, তাই সেগুলি কখনই হোভার পায় না যা সাবমেনু বন্ধ করে দেয়। এছাড়াও, clip-path হিট টেস্ট সীমিত করে, যাতে অভিভাবক মেনুর বাকি অংশ ইন্টার‍্যাক্টিভ থাকে।
    • সাবমেনুর জন্য overflow: visible (ডিফল্ট হিসেবে একটি পপওভার ক্লিপ) প্রয়োজন এবং সিউডো-এলিমেন্ট এটিকে ছাড়িয়ে যায়, তাই সেই পরিমাণ অনুযায়ী শেপের পয়েন্ট অফসেট করুন।
    • ফলো-আপ: একটি বাঁকা পথ (shape() কার্ভ কমান্ড) একটি সোজা ত্রিভুজের চেয়ে আসল পয়েন্টার আর্ককে আরও ভালভাবে অনুসরণ করে।
[popover] [popover] {
  overflow: visible;

  &[data-bridge]::before {
    content: "";
    position: absolute;
    inset: -100vh -100vw; /* Room for the triangle, which can extend far outside the submenu. */
    clip-path: shape(
      from calc(100vw + var(--bridge-x)) calc(100vh + var(--bridge-y)),
      line to calc(100vw + var(--bridge-edge)) 100vh,
      line to calc(100vw + var(--bridge-edge)) calc(100vh + var(--bridge-height)),
      close
    );
  }
}
const canHover = matchMedia('(hover: hover)');

// Call on pointerleave of a submenu trigger, with that trigger's submenu.
function startBridge(submenu, event) {
  if (!canHover.matches || !submenu.matches(':popover-open')) return;
  const rect = submenu.getBoundingClientRect();
  if (event.clientX >= rect.left && event.clientX <= rect.right) return; // --below: no side edge.
  const style = submenu.style;
  style.setProperty('--bridge-edge', `${event.clientX < rect.left ? 0 : rect.width}px`);
  style.setProperty('--bridge-height', `${rect.height}px`);
  const update = ({ clientX, clientY, target }) => {
    if (target !== submenu && submenu.contains(target)) return stop(); // Reached an item.
    style.setProperty('--bridge-x', `${clientX - rect.left}px`);
    style.setProperty('--bridge-y', `${clientY - rect.top}px`);
  };
  const timer = setTimeout(() => stop(), 300);
  const stop = () => {
    clearTimeout(timer);
    document.removeEventListener('pointermove', update);
    submenu.removeEventListener('toggle', stop);
    submenu.removeAttribute('data-bridge');
  };
  update(event);
  submenu.dataset.bridge = '';
  document.addEventListener('pointermove', update);
  submenu.addEventListener('toggle', stop, { once: true });
}

হোভার ফলো ফোকাস (আইটেম ২) একটি সাধারণ pointermove হ্যান্ডলার হিসেবে থাকে: এর জন্য কোনও ত্রিভুজ লজিকের প্রয়োজন নেই।

এছাড়াও:

  • ফোকাসের বাইরে। মেনু সিস্টেমের বাইরে ফোকাস ল্যান্ড করলে খোলা থাকা প্রতিটি মেনু বন্ধ করুন (একটি document focusin লিসেনার)।
  • কমান্ড অ্যাক্টিভেশন। এটি ঘোষণা করুন, তারপর রুট মেনু লুকান এবং এর ট্রিগারে ফোকাস ফিরিয়ে আনুন, অ্যাক্টিভেশন কোনও পয়েন্টার থেকে এলে রিং ছাড়াই। চেক করা যায় এমন আইটেমের (menuitemcheckbox, menuitemradio) ক্ষেত্রে, বন্ধ করার আগে প্রায় ৩০০ ms অপেক্ষা করুন যাতে পরিবর্তিত টিক চিহ্ন দেখা যায়। শুধুমাত্র :focus-visible-এ অ্যাক্টিভ হিসেবে বন্ধ ট্রিগার স্টাইল করুন; অন্যথায় রিস্টোর করা (রিংলেস) ফোকাস পয়েন্টার বেছে নেওয়ার পরে এটিকে হাইলাইট করে রাখে।
  • aria-expanded. এটি শুধুমাত্র toggle লিসনারে সেট করুন। এটি প্রিভিউ ও খোলা অবস্থা কভার করে এবং মেনু প্রিভিউ করা বা খোলা থাকলে ট্রিগারের aria-expanded "true" থাকে।
  • সহায়তাকারী। নিচে দেওয়া ফলব্যাকগুলি আপনার মার্কআপের জন্য আপনার সংজ্ঞায়িত হেল্পারগুলি অনুমান করে: triggers (সব [commandfor] বোতাম), menuFor(trigger), isOpen(menu), closeAndRestore(menu, { fromPointer }) (ট্রিগার ফোকাস করুন, fromPointer হলে রিং ছাড়াই, তারপর menu.hidePopover()), enterOnOpen (একবার খোলার পরে প্রবেশ করার জন্য মেনুর একটি WeakSet) এবং pointerTrigger (পয়েন্টার দ্বারা শেষবার প্রেস করা ট্রিগার)।
  • সাবমেনুর জন্য popover="auto" ব্যবহার করুন, যা পেরেন্টের focusgroup-এর মধ্যে সাবমেনু ট্রিগারের পরে রাখা হয়। কখনও popover="manual" নয়।
  • নেস্টিংয়ের মাধ্যমে ব্রাউজার পেরেন্ট মেনু খোলা রাখতে, একই লেভেলের সাবমেনু বন্ধ করতে, রুট মেনু বন্ধ হলে চাইল্ড সাবমেনু বন্ধ করতে এবং বাইরে ক্লিক করে লাইট ডিসমিস ম্যানেজ করতে পারে।
  • সাবমেনু ট্রিগার aria-haspopup="menu" ও aria-expanded রাখে; toggle লিসেনার aria-expanded সিঙ্ক করে রাখে।
  • কীবোর্ড স্ক্রিপ্ট উপরে বর্ণিত নেস্টেড Escape এবং ArrowLeft ম্যানেজ করে।
  • সাবমেনু ট্রিগার কখনও কোনও কমান্ডও রান করে না: একটি কন্ট্রোল একই সাথে অ্যাক্টিভেট ও খুলতে পারে না এবং Enter-এর অর্থ দুটিই হতে পারে না। ডিফল্ট অ্যাকশনকে প্রথম সাবমেনু আইটেম হিসেবে রাখুন অথবা ট্রিগারের পাশে একটি আলাদা কমান্ড আইটেম যোগ করুন। সম্মিলিত কন্ট্রোল ব্যবহার করতে বাধ্য করা হলে, অ্যাক্সেসিবিলিটি ট্রেড-অফ হিসেবে এটি ডকুমেন্ট করুন।

বিভাজক ও চেক করা যায় এমন আইটেম

  • ফোকাসগ্রুপের মধ্যে <hr> দিয়ে আইটেম গ্রুপ আলাদা করুন: একটি নেটিভ separator, ফোকাস করা যায় না, ফোকাসগ্রুপ এবং ফলব্যাক দ্বারা এড়িয়ে যাওয়া হয়েছে। স্টাইল করা <div> কখনও নয়।
  • বুলিয়ান বিকল্প হল aria-checked সহ <button type="button"> এবং একটি স্পষ্ট role="menuitemcheckbox"। Focusgroup এইসব ভূমিকা অনুমান করে না, তাই এটিই একমাত্র জায়গা যেখানে শেয়ার করা মার্ক-আপ কোনও আইটেমের ভূমিকা সেট করে।
  • সেটের একটি হল <button type="button">, যার মধ্যে role="menuitemradio" ও aria-checked আছে, এটি role="group"-এর মধ্যে আছে, যার মধ্যে aria-label আছে, একই ফোকাসগ্রুপে।
  • স্ক্রিপ্ট টগল aria-checked।
  • Enter চেকবক্সে টগল করে এবং মেনু বন্ধ করে; Space টগল করে এবং খোলা থাকে (APG)। যেকোনও কী প্রেস করলে রেডিও বন্ধ হয়ে যায়।
  • [aria-checked="true"]::before থেকে টিক চিহ্ন রেন্ডার করুন, আলাদা আইকন এলিমেন্ট নয় (নিচে CSS দেখুন)।

এখানে কভার করা হয়নি

  • অ্যাক্টিভেশন সংক্রান্ত মতামত। নেটিভ মেনু বন্ধ হওয়ার আগে বেছে নেওয়া আইটেম ফ্ল্যাশ করে। প্রয়োজন হলে, hidePopover()-এর আগে ~১০০ মিলি সেকেন্ডের ভিজ্যুয়াল কনফার্মেশন দেখান; ফোকাস রিস্টোরেশন বা ঘোষণা করতে দেরি করবেন না।
  • টাইপঅ্যাহেড। Focusgroup-এ কোনওটিই নেই। প্রয়োজন হলে, প্রিন্ট করা যায় এমন keydown-এ বর্তমান লেভেলের চালু করা আইটেম লেবেল ম্যাচ করুন এবং বর্তমান আইটেমের পরে পরবর্তী ম্যাচ ফোকাস করুন, র‍্যাপ করা হচ্ছে। Space ইন্টারসেপ্ট করবেন না।

শেষ করার আগে যাচাই করুন

নির্দেশাবলী আচরণ নিশ্চিত করতে পারে না, তাই ফলাফল পরীক্ষা করুন। পৃষ্ঠাটি খুলুন এবং আসল কীবোর্ড ও মাউস দিয়ে এগুলির প্রতিটি চেক করুন; কোনওটি কাজ না করলে সেটি ঠিক করে আবার পরীক্ষা করুন:

  • কীবোর্ড। Tab একবার প্রথম ট্রিগারে ল্যান্ড করে; ArrowDown প্রবেশ করে; Escape একটি লেভেল বন্ধ করে এবং এর ট্রিগারে ফোকাস ফিরিয়ে আনে; Tab প্রতিটি মেনু বন্ধ করে।
  • পয়েন্টার খোলা আছে। ট্রিগারে ক্লিক করলে, ট্রিগারে ফোকাস সহ সেটির মেনু খুলে যায় এবং কোনও রিং থাকে না; আবার ক্লিক করলে সেটি বন্ধ হয়ে যায়।
  • পয়েন্টার কমান্ড। কোনও কমান্ডে ক্লিক করলে প্রতিটি মেনু বন্ধ হয়ে যায় এবং ট্রিগারে কোনও ফোকাস রিং দেখায় না।
  • সাবমেনু। সাবমেনু ট্রিগারের মাঝখান থেকে সাবমেনু আইটেমে আড়াআড়িভাবে গেলে সেটি খোলা থাকে; সম্পর্কহীন কোনও আইটেমে গেলে সেটি বন্ধ হয়ে যায়।
  • সরু স্ক্রিন। ৩৭৫ পিক্সেল সাইজে কোনও সাবমেনু তার নিজস্ব ট্রিগারকে কভার করে না।
  • টিক চিহ্ন। চেক করা আইটেমে টিক চিহ্ন দেখানো হয়।
  • কিছুই বাকি নেই। কনসোলে কোনও সমস্যা নেই এবং ট্রিগারে কোনও popovertarget, popovertargetaction বা aria-details নেই।

মেনু আইটেম কন্টেন্ট অ্যালাইন করুন

কোনও সারিতে আইকন, লেবেল এবং শর্টকাট বা সাবমেনু ইন্ডিকেটর থাকলে, আইকন বা টিক চিহ্নের জন্য নির্দিষ্ট সাইজের লিডিং স্লট রিজার্ভ করুন এবং ট্রেলিং কন্টেন্ট ইনলাইন এন্ডে পুশ করুন। আইকনের সাইজ জানা থাকে, তাই কোনও আইটেমে আইকন থাকুক বা না থাকুক, একটি নির্দিষ্ট স্লট লেবেলগুলিকে সারিবদ্ধ রাখে। সারির জন্য subgrid ব্যবহার করবেন না: যেকোনও অতিরিক্ত ইনলাইন চাইল্ড (যেমন, ব্যাজ) নিজস্ব গ্রিড আইটেম হয়ে যায় এবং লেআউট ভেঙে দেয়। সহায়ক প্রযুক্তি থেকে শর্টকাট টেক্সট লুকান যখন এটি শুধুমাত্র এমন একটি কী পুনরাবৃত্তি করে যা আগে থেকেই আইটেমটি পরিচালনা করে।

[focusgroup~="menu"] {
  --menu-icon-size: 1.25rem;
}

/* Target items only. A closed submenu popover inside the focusgroup must keep display: none. */
[focusgroup~="menu"] > button,
[focusgroup~="menu"] > [role="group"] > button {
  display: flex;
  align-items: center;
  gap: .5rem;
}

/* The leading slot holds the icon or check mark. */
[focusgroup~="menu"] button::before {
  content: "";
  flex: none;
  inline-size: var(--menu-icon-size);
}

/* Check mark for checked items. This selector must be at least as specific as the base
   ::before rule above, or the empty content wins and the check mark never shows. */
[focusgroup~="menu"] button[aria-checked="true"]::before {
  content: "✓";
}

/* Shortcut or submenu indicator at the inline end. */
[focusgroup~="menu"] .hint {
  margin-inline-start: auto;
}

বোতামের ভিতরে <span class="hint" aria-hidden="true">-এ একটি ট্রেলিং ইন্ডিকেটর বা শর্টকাট রাখুন; সাবমেনু ট্রিগার আগে থেকেই এর পপ-আপ ঘোষণা করে।

প্রোগ্রেসিভ এনহ্যান্সমেন্ট ও ফলব্যাক

focusgroup-এর জন্য ফলব্যাক ও ব্রাউজার সাপোর্ট

মার্কআপে focusgroup="menu nomemory" রাখুন। 'focusGroup' in HTMLElement.prototype মিথ্যা হলে, Up, Down, Home, এবং End ইনস্টল করুন, প্রতিটি মেনু কন্টেনারে role="menu" সেট করুন এবং কোনও ভূমিকা নেই এমন বোতামে role="menuitem" সেট করুন; চেক করা যায় এমন আইটেম, <hr> এবং role="group" ছেড়ে দিন। নেটিভ focusgroup থাকলে এটি কখনও ইনস্টল করবেন না। বাঁদিক, ডানদিক, প্রিভিউ, খোলা ও বন্ধ করা হল লেখক স্ক্রিপ্ট।

if (!('focusGroup' in HTMLElement.prototype)) {
  document.querySelectorAll('[focusgroup~="menu"]').forEach(group => {
    group.setAttribute('role', 'menu');
    group.querySelectorAll('button:not([role])').forEach(item => {
      item.setAttribute('role', 'menuitem');
    });
    group.addEventListener('keydown', event => {
      if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
      // Own items only: a nested submenu has its own focusgroup, a role="group" wrapper does not.
      const items = [...group.querySelectorAll('button')]
        .filter(item => item.closest('[focusgroup]') === group)
        .filter(item => !item.disabled && item.getAttribute('aria-disabled') !== 'true');
      const current = items.indexOf(document.activeElement);
      if (current < 0) return;
      const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 :
        (current + (event.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
      event.preventDefault();
      event.stopImmediatePropagation();
      items[next].focus({ preventScroll: true });
    }, true);
  });
}

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

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

অ্যাঙ্কর পজিশনিংয়ের জন্য ফলব্যাক ও ব্রাউজার সাপোর্ট

CSS অ্যাঙ্কর পজিশনিং ছাড়া, একটি পপওভার UA-এর কেন্দ্রিক ভিউপোর্ট প্লেসমেন্টে ফিরে আসে। কোনও মেনুর জন্য এটি কখনও শিপ করবেন না: এটি মেনুকে তার ট্রিগার থেকে আলাদা করে এবং একটি কেন্দ্রিক সাবমেনু তার পেরেন্টকে কভার করে। @supports (position-area: block-end)-এর পিছনে অ্যাঙ্কর করা নিয়ম গেট করুন এবং স্ক্রিপ্ট থেকে মেনু পজিশন করুন যখন এটি ব্যর্থ হয়:

  • মেনুটি এর ট্রিগারের নিচে (শীর্ষ স্তর) বা এর পাশে (সাবমেনু) রাখুন, অন্য দিকে ফ্লিপ করুন এবং এটি উপচে পড়লে ভিউপোর্টে ক্ল্যাম্প করুন।
  • মেনু খোলার সময় toggle হ্যান্ডলারে এটি রান করুন এবং মেনুতে left ও top হিসেবে ফলাফল প্রয়োগ করুন। inset: auto ও margin: 0-এর সাথে position: fixed ব্যবহার করুন যাতে কোঅর্ডিনেট ভিউপোর্ট-রিলেটিভ হয়।
  • এই ওভাররাইডগুলি @supports not (position-area: block-end)-এর মধ্যে রাখুন, যাতে অ্যাঙ্কর পজিশনিং সহ ব্রাউজারগুলি প্রভাবিত না হয়।

আগের ইমপ্লিমেন্টেশন টার্গেট করলে, যেখানে showPopover({ source }) কোনও ইমপ্লিসিট অ্যাঙ্কর (ToggleEvent.prototype-এ কোনও source নেই) তৈরি করে না, সেখানে প্রতিটি মেনুর জন্য CSS নিয়ম লেখার পরিবর্তে, স্ক্রিপ্ট থেকে প্রতিটি ট্রিগারকে একটি অনন্য anchor-name এবং প্রতিটি মেনুকে একটি ম্যাচিং position-anchor দিন।

যেসব ব্রাউজারে অ্যাঙ্কর পজিশনিং একেবারেই নেই, সেগুলির ক্ষেত্রে পলিফিল রুটের জন্য ভিউ পোর্ট-অ্যাওয়ার ড্রপডাউন দেখুন।

ইনভোকার কমান্ডের জন্য ফলব্যাক ও ব্রাউজার সহায়তা

ইনভোকার ছাড়া, command="toggle-popover" কিছুই করে না, তাই স্ক্রিপ্টকে অবশ্যই মেনু টগল করতে হবে।

  • ইন্টারফেসের ফিচার শনাক্ত করুন, ইনস্ট্যান্স নয়: 'commandForElement' in HTMLButtonElement.prototype।
  • প্রতিটি ট্রিগারে click, preventDefault() কল করুন যাতে ইনভোকার সহ ব্রাউজার দুবার টগল করতে না পারে।
  • মেনু খোলা থাকলে, closeAndRestore(menu) দিয়ে এটি বন্ধ করুন। অন্যথায় menu.showPopover({ source: trigger }) নম্বরে কল করুন।
  • enterOnOpen-এ মেনু চিহ্নিত করুন, যদি না ক্লিকটি প্রেস করা ট্রিগার (pointerTrigger) থেকে আসে, যাতে শুধুমাত্র কীবোর্ড মেনুতে প্রবেশ করে।
  • Enter ও Space এখনও কাজ করে কারণ সেগুলি বোতামে ক্লিক জেনারেট করে।