স্পেশিয়াল নেভিগেশন (দিকনির্দেশক কীপ্যাড ফোকাস)

ওয়েব ফিচার: scrollIntoView()

দিকনির্দেশক ফোকাস নেভিগেশন (তীরচিহ্ন বা ডি-প্যাড ব্যবহার করে) শুধুমাত্র কীবোর্ড এবং বিকল্প-ইনপুট ব্যবহারকারীদের তাদের ভিজ্যুয়াল 2D লেআউটের উপর ভিত্তি করে ইন্টার‍্যাক্টিভ আইটেম নেভিগেট করতে সক্ষম করে। এটি স্পেশিয়াল ইউজার ইন্টারফেসের (যেমন, মিডিয়া রেল, ড্যাশবোর্ড প্যানেল বা নেস্টেড ক্যাটালগ) জন্য অত্যন্ত উপযোগী, তবে ভিউপোর্ট স্ক্রলিংয়ের মতো নেটিভ ব্রাউজার আচরণ যাতে ব্যাহত না হয়, তার জন্য এটি সাবধানে প্রয়োগ করতে হবে।

এই গাইডে কম্পোজিট উইজেটের মধ্যে পারফর্ম্যান্ট, অ্যাক্সেসযোগ্য 2D ডিরেকশনাল নেভিগেশন তৈরি করার বিষয়ে আলোচনা করা হয়েছে।


কোর আর্কিটেকচারাল প্রিন্সিপাল

কাস্টম তীর-কী স্পেশাল ফোকাস প্রয়োগ করার সময়, এই মৌলিক নিয়মগুলি মেনে চলুন:

  1. কম্পোজিট উইজেটে সীমাবদ্ধ করুন: সাধারণ ডকুমেন্ট টেক্সট ফ্লো বা সাধারণ উল্লম্ব তালিকা লেআউটে তীরচিহ্ন ওভাররাইড করবেন না যেখানে ডিফল্ট ক্রমিক ট্যাব নেভিগেশন প্রত্যাশিত। সাধারণ ডকুমেন্ট ফ্লো সংক্রান্ত নির্দেশিকার জন্য অ্যাক্সেসিবিলিটি কোডিং নির্দেশিকা দেখুন।
  2. নেটিভ বাউন্ডারি স্ক্রলিং বজায় রাখুন: প্রেস করা দিকে কোনও ফোকাস করার মতো এলিমেন্ট না থাকলে, ডিফল্ট ব্রাউজার আচরণ প্রতিরোধ করবেন না। ইভেন্ট বাবল হতে দিন যাতে ব্রাউজার ভিউপোর্ট নেটিভভাবে স্ক্রল করতে পারে। শর্তহীনভাবে তীরচিহ্ন কী ওভাররাইড করলে অ্যাক্সেসিবিলিটি (WCAG 1.4.10) নষ্ট হয়।
  3. ফোকাস ভিজিবিলিটি ও অ্যালাইনমেন্ট নিশ্চিত করা: ফোকাস পরিবর্তন হলে, নতুন ফোকাস করা এলিমেন্ট যাতে স্ক্রল করে ভিউতে আসে (যেমন, scrollIntoView({ block: 'nearest', inline: 'nearest' }) ব্যবহার করে) তা নিশ্চিত করুন, যাতে এটি সম্পূর্ণভাবে দৃশ্যমান থাকে।
  4. লেআউট সিস্টেমের সাথে কোঅর্ডিনেট করা: প্রতিটি কীপ্রেস ম্যানেজ করার সময় লাইভ বাউন্ডিং জিওমেট্রি (getBoundingClientRect()) থেকে ফোকাস ক্যান্ডিডেট পজিশন নির্ধারণ করুন। DOM অর্ডার বা নির্দিষ্ট গ্রিড স্ট্রাকচার ভিজ্যুয়াল লেআউটকে উপস্থাপন করে বলে ধরে নেবেন না। আধুনিক লেআউট সংক্রান্ত সাজেশন পেতে CSS লেআউট ও রেসপন্সিভ ডিজাইন দেখুন।

অ্যাক্সেসযোগ্য ফোকাস ম্যানেজমেন্ট

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

  • অ্যাক্টিভ আইটেমে tabindex="0" আছে।
  • সব ইনঅ্যাক্টিভ আইটেমে tabindex="-1" আছে।
  • নেভিগেট করার সময়, tabindex="0" অ্যাট্রিবিউট শিফ্ট করে এবং focus() কল করে প্রোগ্রাম্যাটিক্যালি ফোকাস সরান।

ভিজ্যুয়াল ক্যান্ডিডেট বেছে নেওয়া

অসমমিত, র‍্যাপ করা বা গ্রিড লেআউটে, ইন্ডেক্স-ভিত্তিক ম্যাপিং কাজ করে না। পরিবর্তে, বর্তমান কীপ্রেসের জন্য লাইভ ভিজ্যুয়াল জিওমেট্রি থেকে ক্যান্ডিডেট বের করুন:

  • ক্যান্ডিডেট: উইজেট কন্টেনারের ফোকাস করা যায় এমন ডিসেন্ডেন্ট ব্যবহার করুন যা checkVisibility() পাস করে। লুকানো এলিমেন্টের (0, 0)-এ শূন্য-সাইজের রেক্ট থাকতে পারে এবং ভুলবশত up বা left টার্গেট হওয়া চলবে না।
  • জ্যামিতি: 'কী' প্রেস করার সময় ফোকাস করা এলিমেন্ট ও প্রতিটি ক্যান্ডিডেটের জন্য getBoundingClientRect() পড়ুন। এইসব রেক্ট গ্লোবালি ক্যাশে করবেন না: লেআউট অনেক কারণে পরিবর্তন হতে পারে এবং ব্যবহারকারীর পেস করা কী ইভেন্ট ম্যানেজ করার সময় অল্প সংখ্যক জিওমেট্রি রিড করা সস্তা।
  • দিক ফিল্টার: কোনও ক্যান্ডিডেট প্রেস করা দিকে তখনই থাকে যখন তার কাছাকাছি প্রান্তটি সেই অক্ষের বর্তমান এলিমেন্টের দূরবর্তী প্রান্তে বা তার বাইরে থাকে, যা সাব-পিক্সেল রাউন্ডিংয়ের জন্য সামান্য টলারেন্সের অনুমতি দেয়। যেমন, right-এর প্রার্থীর কাছে অবশ্যই candidate.left >= current.right - tolerance থাকতে হবে। কোনও প্রার্থী সঠিক দিকে আছেন কিনা তা নির্ধারণ করতে কেন্দ্রগুলির তুলনা করবেন না।
  • প্রথমে অ্যালাইন করা ক্যান্ডিডেট: যেসব ক্যান্ডিডেট ডিরেকশন ফিল্টার পাস করে, তাদের মধ্যে এমন ক্যান্ডিডেটকে বেছে নিন যাদের লম্ব অক্ষের এক্সটেনশন বর্তমান এলিমেন্টের এক্সটেনশনকে ওভারল্যাপ করে। নেভিগেশন অক্ষে সবচেয়ে ছোট ব্যবধান বেছে নিন, সবচেয়ে ছোট লম্ব কেন্দ্র অফসেটের সাথে টাই ব্রেক করুন। কোনও আইটেম অস্বাভাবিকভাবে চওড়া হলেও এটি সংলগ্ন ভিজ্যুয়াল সারি বা কলামে নেভিগেশন রাখে।
  • ফলব্যাক স্কোরিং: লম্ব অক্ষে কোনও প্রার্থী ওভারল্যাপ না করলে, বাকি প্রার্থীদের gap + w * perpendicularCentreOffset হিসেবে স্কোর করুন এবং ন্যূনতমটি বেছে নিন। শুরু করার জন্য w >= 2 ওয়েট ব্যবহার করুন, তারপর লেআউটের জন্য এটি টিউন করুন; বেশি ওয়েট থাকলে, আরও কাছাকাছি অ্যালাইন করা ক্যান্ডিডেটকে বেশি গুরুত্ব দেওয়া হয়।
  • কোনও প্রার্থী নেই: কিছু ফেরত দেওয়া হবে না। ইভেন্ট হ্যান্ডলারকে অবশ্যই preventDefault() কল করতে হবে না, যা ব্রাউজারকে নেটিভভাবে স্ক্রল করার অনুমতি দেয়।

প্রয়োগ করার পদ্ধতি আলাদা হতে পারে, তবে ডিরেকশন টেস্ট এজ-ভিত্তিক রাখুন এবং ওয়েটেড ফলব্যাকের আগে ওভারল্যাপ সংক্রান্ত পছন্দ প্রয়োগ করুন। এটি অ্যালগরিদমের মূল বিষয়; ক্যাশে বা নির্দিষ্ট ওয়েটকে সর্বজনীনভাবে সঠিক হিসেবে উপস্থাপন করা এড়িয়ে চলুন।


'কী' প্রেস ইন্টিগ্রেশন ও স্ক্রল কো-অপারেশন

আপনার ইভেন্ট রাউটারকে অবশ্যই স্ট্যান্ডার্ড ভিউপোর্ট স্ক্রোলিং প্রতিরোধ করতে হবে শুধুমাত্র যখন একটি বৈধ স্পেশিয়াল ফোকাস ট্রানজিশন ঘটে:

  • পরিবর্তক কী হোল্ড করা থাকলে, e.defaultPrevented আগে থেকেই সত্য হলে অথবা সবচেয়ে ভিতরের ইভেন্ট টার্গেট নিজেই তীরচিহ্ন কী ব্যবহার করলে, ইভেন্ট ইন্টারসেপ্ট করবেন না। শুধু e.target-এর পরিবর্তে e.composedPath()[0] ব্যবহার করুন, কারণ e.target-কে শ্যাডো হোস্টে রিটার্গেট করা যেতে পারে। একটি প্র্যাকটিক্যাল এডিটেবল/কন্ট্রোল চেক হল target.matches(':read-write, select, input[type="range"], [role="slider"]'); বন্ধ শ্যাডো রুট চেক করা যাবে না।
  • উইজেট কন্টেনারে লিসেনারকে স্কোপ করুন, যাতে একাধিক স্পেশিয়াল উইজেট একটি পৃষ্ঠায় সহাবস্থান করতে পারে।
  • ফোকাস করা এলিমেন্ট নিজেই প্রার্থী হলে তবেই কাজ করুন। আইটেমে নিজস্ব ফোকাস করা যায় এমন কন্ট্রোল থাকলে, হয় সেই কন্ট্রোলগুলিকেও ক্যান্ডিডেট হিসেবে সেট করুন অথবা সেগুলি যেমন আছে তেমনই থাকতে দিন; closest()-এর সাথে পেরেন্ট রিজলভ করবেন না।
  • কোনও প্রার্থীকে খুঁজে পেলে, preventDefault()-কে কল করুন, রোভিং tabindex আপডেট করুন, focus()-কে কল করুন এবং scrollIntoView({ block: 'nearest', inline: 'nearest' }) ব্যবহার করুন।
  • কোনও ক্যান্ডিডেট খুঁজে না পাওয়া গেলে, preventDefault() কল না করেই ফিরে যান যাতে নেটিভ স্ক্রলিং চলতে থাকে।

স্পেশিয়াল নেভিগেশনের জন্য h/j/k/l অথবা W/A/S/D-এর মতো সিঙ্গল-ক্যারেক্টার শর্টকাট যোগ করা এড়িয়ে চলুন। তীরচিহ্নযুক্ত কীগুলি ক্যারেক্টার কী নয় এবং কোনও ক্যান্ডিডেট না থাকলে নেটিভ স্ক্রলিং বজায় রাখলে ভিউপোর্ট মুভমেন্ট অপ্রয়োজনীয়ভাবে ব্লক করা এড়ানো যায়।