সারাংশ
<howto-tabs>
দৃশ্যমান বিষয়বস্তুকে একাধিক প্যানেলে আলাদা করে সীমাবদ্ধ করে। একটি সময়ে শুধুমাত্র একটি প্যানেল দৃশ্যমান, যখন সমস্ত সংশ্লিষ্ট ট্যাব সর্বদা দৃশ্যমান। একটি প্যানেল থেকে অন্য প্যানেলে স্যুইচ করতে, সংশ্লিষ্ট ট্যাবটি নির্বাচন করতে হবে।
হয় ক্লিক করে বা তীর কী ব্যবহার করে ব্যবহারকারী সক্রিয় ট্যাবের নির্বাচন পরিবর্তন করতে পারেন।
জাভাস্ক্রিপ্ট অক্ষম করা হলে, সমস্ত প্যানেল সংশ্লিষ্ট ট্যাবের সাথে ইন্টারলিভ করা দেখানো হয়। ট্যাবগুলি এখন শিরোনাম হিসাবে কাজ করে।
রেফারেন্স
ডেমো
উদাহরণ ব্যবহার
<style>
howto-tab {
border: 1px solid black;
padding: 20px;
}
howto-panel {
padding: 20px;
background-color: lightgray;
}
howto-tab[selected] {
background-color: bisque;
}
জাভাস্ক্রিপ্ট না চললে, উপাদানটি মিলবে না :defined
। সেক্ষেত্রে এই স্টাইলটি ট্যাব এবং পূর্ববর্তী প্যানেলের মধ্যে ব্যবধান যোগ করে।
howto-tabs:not(:defined), howto-tab:not(:defined), howto-panel:not(:defined) {
display: block;
}
</style>
<howto-tabs>
<howto-tab role="heading" slot="tab">Tab 1</howto-tab>
<howto-panel role="region" slot="panel">Content 1</howto-panel>
<howto-tab role="heading" slot="tab">Tab 2</howto-tab>
<howto-panel role="region" slot="panel">Content 2</howto-panel>
<howto-tab role="heading" slot="tab">Tab 3</howto-tab>
<howto-panel role="region" slot="panel">Content 3</howto-panel>
</howto-tabs>
কোড
(function() {
কীবোর্ড ইভেন্টগুলি পরিচালনা করতে সাহায্য করার জন্য কী কোডগুলিকে সংজ্ঞায়িত করুন৷
const KEYCODE = {
DOWN: 40,
LEFT: 37,
RIGHT: 39,
UP: 38,
HOME: 36,
END: 35,
};
প্রতিটি নতুন দৃষ্টান্তের জন্য .innerHTML
সাথে পার্সারকে আহ্বান করা এড়াতে, ছায়া DOM-এর বিষয়বস্তুর জন্য একটি টেমপ্লেট সমস্ত <howto-tabs>
দৃষ্টান্ত দ্বারা ভাগ করা হয়।
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
display: flex;
flex-wrap: wrap;
}
::slotted(howto-panel) {
flex-basis: 100%;
}
</style>
<slot name="tab"></slot>
<slot name="panel"></slot>
`;
HowtoTabs হল ট্যাব এবং প্যানেলের জন্য একটি ধারক উপাদান।
<howto-tabs>
এর সমস্ত বাচ্চাদের হয় <howto-tab>
অথবা <howto-tabpanel>
হতে হবে। এই উপাদানটি স্টেটলেস, যার মানে কোন মান ক্যাশে করা হয় না এবং তাই রানটাইম কাজের সময় পরিবর্তন হয়।
class HowtoTabs extends HTMLElement {
constructor() {
super();
ইভেন্ট হ্যান্ডলার যেগুলি এই উপাদানটির সাথে সংযুক্ত নয় তাদের যদি this
অ্যাক্সেসের প্রয়োজন হয় তবে তাদের আবদ্ধ হতে হবে৷
this._onSlotChange = this._onSlotChange.bind(this);
প্রগতিশীল বর্ধনের জন্য, মার্কআপ ট্যাব এবং প্যানেলের মধ্যে বিকল্প হওয়া উচিত। যে উপাদানগুলি তাদের বাচ্চাদের পুনর্বিন্যাস করে তারা ফ্রেমওয়ার্কের সাথে ভালভাবে কাজ করে না। পরিবর্তে শ্যাডো DOM স্লট ব্যবহার করে উপাদানগুলিকে পুনরায় সাজাতে ব্যবহার করা হয়।
this.attachShadow({ mode: 'open' });
ট্যাব এবং প্যানেলের জন্য স্লট তৈরি করতে শেয়ার্ড টেমপ্লেট আমদানি করুন।
this.shadowRoot.appendChild(template.content.cloneNode(true));
this._tabSlot = this.shadowRoot.querySelector('slot[name=tab]');
this._panelSlot = this.shadowRoot.querySelector('slot[name=panel]');
এই উপাদানটিকে নতুন শিশুদের প্রতি প্রতিক্রিয়া জানাতে হবে কারণ এটি aria-labelledby
এবং aria-controls
ব্যবহার করে শব্দার্থকভাবে ট্যাব এবং প্যানেল লিঙ্ক করে। নতুন বাচ্চারা স্বয়ংক্রিয়ভাবে স্লট হয়ে যাবে এবং স্লট চেঞ্জ করে আগুনে পরিণত হবে, তাই MutationObserver এর প্রয়োজন নেই।
this._tabSlot.addEventListener('slotchange', this._onSlotChange);
this._panelSlot.addEventListener('slotchange', this._onSlotChange);
}
connectedCallback()
পুনরায় ক্রমানুসারে ট্যাব এবং প্যানেল গ্রুপ করে এবং নিশ্চিত করে যে ঠিক একটি ট্যাব সক্রিয় আছে।
connectedCallback() {
তীর কী এবং Home / End দিয়ে স্যুইচ করার অনুমতি দেওয়ার জন্য উপাদানটিকে কিছু ম্যানুয়াল ইনপুট ইভেন্ট হ্যান্ডলিং করতে হবে।
this.addEventListener('keydown', this._onKeyDown);
this.addEventListener('click', this._onClick);
if (!this.hasAttribute('role'))
this.setAttribute('role', 'tablist');
সম্প্রতি পর্যন্ত, পার্সার দ্বারা একটি উপাদান আপগ্রেড করার সময় slotchange
ইভেন্টগুলি চালু হয়নি৷ এই কারণে, উপাদানটি হ্যান্ডলারকে ম্যানুয়ালি আহ্বান করে। একবার নতুন আচরণ সমস্ত ব্রাউজারে অবতরণ করলে, নীচের কোডটি সরানো যেতে পারে।
Promise.all([
customElements.whenDefined('howto-tab'),
customElements.whenDefined('howto-panel'),
])
.then(() => this._linkPanels());
}
disconnectedCallback()
ইভেন্ট শ্রোতাদের সরিয়ে দেয় যেগুলি connectedCallback()
যোগ করেছে।
disconnectedCallback() {
this.removeEventListener('keydown', this._onKeyDown);
this.removeEventListener('click', this._onClick);
}
_onSlotChange()
বলা হয় যখনই ছায়া DOM স্লটগুলির একটি থেকে একটি উপাদান যোগ করা বা সরানো হয়।
_onSlotChange() {
this._linkPanels();
}
_linkPanels()
aria-controls এবং aria-labelledby
ব্যবহার করে তাদের সংলগ্ন প্যানেলের সাথে ট্যাবগুলিকে লিঙ্ক করে। উপরন্তু, পদ্ধতি নিশ্চিত করে যে শুধুমাত্র একটি ট্যাব সক্রিয় আছে।
_linkPanels() {
const tabs = this._allTabs();
প্রতিটি প্যানেলকে একটি aria-labelledby
বৈশিষ্ট্য দিন যা এটিকে নিয়ন্ত্রণ করে এমন ট্যাবকে নির্দেশ করে।
tabs.forEach((tab) => {
const panel = tab.nextElementSibling;
if (panel.tagName.toLowerCase() !== 'howto-panel') {
console.error(`Tab #${tab.id} is not a` +
`sibling of a <howto-panel>`);
return;
}
tab.setAttribute('aria-controls', panel.id);
panel.setAttribute('aria-labelledby', tab.id);
});
কোন ট্যাব নির্বাচিত হিসাবে চিহ্নিত করা হয়েছে কিনা তা উপাদানটি পরীক্ষা করে। যদি না হয়, প্রথম ট্যাব এখন নির্বাচন করা হয়.
const selectedTab =
tabs.find((tab) => tab.selected) || tabs[0];
এরপরে, নির্বাচিত ট্যাবে স্যুইচ করুন। _selectTab()
অন্যান্য সমস্ত ট্যাবকে অনির্বাচিত হিসাবে চিহ্নিত করার এবং অন্যান্য সমস্ত প্যানেল লুকানোর যত্ন নেয়।
this._selectTab(selectedTab);
}
_allPanels()
ট্যাব প্যানেলের সমস্ত প্যানেল ফেরত দেয়। এই ফাংশন ফলাফল মুখস্ত করতে পারে যদি DOM কোয়েরি কখনও একটি কর্মক্ষমতা সমস্যা হয়ে ওঠে। মুখস্থ করার খারাপ দিক হল যে গতিশীলভাবে যোগ করা ট্যাব এবং প্যানেলগুলি পরিচালনা করা হবে না।
এটি একটি পদ্ধতি এবং একটি গেটার নয়, কারণ একটি গেটার বোঝায় যে এটি পড়া সস্তা।
_allPanels() {
return Array.from(this.querySelectorAll('howto-panel'));
}
_allTabs()
ট্যাব প্যানেলের সমস্ত ট্যাব ফেরত দেয়।
_allTabs() {
return Array.from(this.querySelectorAll('howto-tab'));
}
_panelForTab()
প্যানেলটি ফেরত দেয় যা প্রদত্ত ট্যাব নিয়ন্ত্রণ করে।
_panelForTab(tab) {
const panelId = tab.getAttribute('aria-controls');
return this.querySelector(`#${panelId}`);
}
_prevTab()
বর্তমানে নির্বাচিত একটির আগে যে ট্যাবটি আসে তা ফেরত দেয়, প্রথমটিতে পৌঁছানোর সময় চারপাশে মোড়ানো হয়।
_prevTab() {
const tabs = this._allTabs();
বর্তমানে নির্বাচিত উপাদানটির সূচী খুঁজে পেতে findIndex()
ব্যবহার করুন এবং পূর্ববর্তী উপাদানটির সূচক পেতে একটি বিয়োগ করুন।
let newIdx = tabs.findIndex((tab) => tab.selected) - 1;
সূচকটি একটি ধনাত্মক সংখ্যা তা নিশ্চিত করতে tabs.length
যোগ করুন এবং প্রয়োজনে মডুলাসটি মোড়ানোর জন্য পান।
return tabs[(newIdx + tabs.length) % tabs.length];
}
_firstTab()
প্রথম ট্যাব ফেরত দেয়।
_firstTab() {
const tabs = this._allTabs();
return tabs[0];
}
_lastTab()
শেষ ট্যাব ফেরত দেয়।
_lastTab() {
const tabs = this._allTabs();
return tabs[tabs.length - 1];
}
_nextTab()
একটি ট্যাব পায় যা বর্তমানে নির্বাচিত একটির পরে আসে, শেষ ট্যাবে পৌঁছানোর সময় চারপাশে মোড়ানো হয়।
_nextTab() {
const tabs = this._allTabs();
let newIdx = tabs.findIndex((tab) => tab.selected) + 1;
return tabs[newIdx % tabs.length];
}
reset()
সমস্ত ট্যাবকে অনির্বাচিত হিসাবে চিহ্নিত করে এবং সমস্ত প্যানেল লুকিয়ে রাখে।
reset() {
const tabs = this._allTabs();
const panels = this._allPanels();
tabs.forEach((tab) => tab.selected = false);
panels.forEach((panel) => panel.hidden = true);
}
_selectTab()
প্রদত্ত ট্যাবটিকে নির্বাচিত হিসাবে চিহ্নিত করে। অতিরিক্তভাবে, এটি প্রদত্ত ট্যাবের সাথে সম্পর্কিত প্যানেলটিকে প্রকাশ করে।
_selectTab(newTab) {
সমস্ত ট্যাব অনির্বাচন করুন এবং সমস্ত প্যানেল লুকান৷
this.reset();
প্যানেলটি পান যা newTab
এর সাথে যুক্ত।
const newPanel = this._panelForTab(newTab);
সেই প্যানেলটি বিদ্যমান না থাকলে, বাতিল করুন।
if (!newPanel)
throw new Error(`No panel with id ${newPanelId}`);
newTab.selected = true;
newPanel.hidden = false;
newTab.focus();
}
_onKeyDown()
ট্যাব প্যানেলের ভিতরে কী প্রেসগুলি পরিচালনা করে।
_onKeyDown(event) {
যদি কীপ্রেসটি একটি ট্যাব উপাদান থেকে উদ্ভূত না হয় তবে এটি প্যানেলের ভিতরে বা খালি জায়গায় একটি কীপ্রেস ছিল। কিছুই করার নেই।
if (event.target.getAttribute('role') !== 'tab')
return;
সাধারণত সহায়ক প্রযুক্তি দ্বারা ব্যবহৃত সংশোধক শর্টকাটগুলি পরিচালনা করবেন না।
if (event.altKey)
return;
সুইচ-কেস নির্ধারণ করবে কোন ট্যাবটিকে সক্রিয় হিসাবে চিহ্নিত করা হবে সেটি চাপানো কীটির উপর নির্ভর করে।
let newTab;
switch (event.keyCode) {
case KEYCODE.LEFT:
case KEYCODE.UP:
newTab = this._prevTab();
break;
case KEYCODE.RIGHT:
case KEYCODE.DOWN:
newTab = this._nextTab();
break;
case KEYCODE.HOME:
newTab = this._firstTab();
break;
case KEYCODE.END:
newTab = this._lastTab();
break;
অন্য কোন কী প্রেস উপেক্ষা করা হয় এবং ব্রাউজারে ফিরে যায়।
default:
return;
}
ব্রাউজারটির কিছু নেটিভ কার্যকারিতা থাকতে পারে তীর কী, হোম বা শেষের সাথে আবদ্ধ। ব্রাউজারকে কোনো পদক্ষেপ নেওয়া থেকে আটকাতে উপাদানটি preventDefault()
কল করে।
event.preventDefault();
নতুন ট্যাবটি নির্বাচন করুন, যা সুইচ-কেসে নির্ধারিত হয়েছে।
this._selectTab(newTab);
}
_onClick()
ট্যাব প্যানেলের ভিতরে ক্লিকগুলি পরিচালনা করে।
_onClick(event) {
ক্লিকটি যদি ট্যাব এলিমেন্টে টার্গেট করা না হয় তবে এটি প্যানেলের ভিতরে বা খালি জায়গায় ক্লিক করা হয়েছে। কিছুই করার নেই।
if (event.target.getAttribute('role') !== 'tab')
return;
যদি এটি একটি ট্যাব উপাদানে থাকে তবে, সেই ট্যাবটি নির্বাচন করুন।
this._selectTab(event.target);
}
}
customElements.define('howto-tabs', HowtoTabs);
howtoTabCounter
তৈরি করা <howto-tab>
দৃষ্টান্তের সংখ্যা গণনা করে। নম্বরটি নতুন, অনন্য আইডি তৈরি করতে ব্যবহৃত হয়।
let howtoTabCounter = 0;
HowtoTab
হল একটি <howto-tabs>
ট্যাব প্যানেলের জন্য একটি ট্যাব। <howto-tab>
মার্কআপে সর্বদা role="heading"
এর সাথে ব্যবহার করা উচিত যাতে জাভাস্ক্রিপ্ট ব্যর্থ হলে শব্দার্থবিদ্যা ব্যবহারযোগ্য থাকে।
অ্যারিয়া-কন্ট্রোল অ্যাট্রিবিউটের মান হিসাবে সেই প্যানেলের আইডি ব্যবহার করে একটি <howto-tab>
ঘোষণা করে যে এটি কোন <howto-panel>
এর সাথে সম্পর্কিত।
একটি <howto-tab>
স্বয়ংক্রিয়ভাবে একটি অনন্য আইডি তৈরি করবে যদি কোনোটি নির্দিষ্ট করা না থাকে।
class HowtoTab extends HTMLElement {
static get observedAttributes() {
return ['selected'];
}
constructor() {
super();
}
connectedCallback() {
যদি এটি কার্যকর করা হয়, জাভাস্ক্রিপ্ট কাজ করছে এবং উপাদানটি tab
তার ভূমিকা পরিবর্তন করে।
this.setAttribute('role', 'tab');
if (!this.id)
this.id = `howto-tab-generated-${howtoTabCounter++}`;
একটি সু-সংজ্ঞায়িত প্রাথমিক অবস্থা সেট করুন।
this.setAttribute('aria-selected', 'false');
this.setAttribute('tabindex', -1);
this._upgradeProperty('selected');
}
একটি সম্পত্তি একটি উদাহরণ মান আছে কিনা পরীক্ষা করুন. যদি তাই হয়, মানটি অনুলিপি করুন এবং উদাহরণ সম্পত্তি মুছুন যাতে এটি ক্লাস প্রপার্টি সেটারের ছায়া না দেয়। অবশেষে, মানটি ক্লাস প্রপার্টি সেটারের কাছে পাস করুন যাতে এটি কোনও পার্শ্ব প্রতিক্রিয়া ট্রিগার করতে পারে। এটি এমন ঘটনাগুলির বিরুদ্ধে সুরক্ষিত সুরক্ষার জন্য যেখানে, উদাহরণস্বরূপ, একটি কাঠামো পৃষ্ঠায় উপাদান যুক্ত করতে পারে এবং এর বৈশিষ্ট্যগুলির একটিতে একটি মান সেট করতে পারে, কিন্তু অলসভাবে এর সংজ্ঞা লোড করে৷ এই গার্ড ব্যতীত, আপগ্রেড করা উপাদানটি সেই সম্পত্তিটি মিস করবে এবং দৃষ্টান্ত সম্পত্তিটি ক্লাস প্রপার্টি সেটারকে কখনও কল করা থেকে বাধা দেবে।
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
বৈশিষ্ট্য এবং তাদের সংশ্লিষ্ট গুণাবলী একে অপরের প্রতিফলন করা উচিত। এই প্রভাবে, selected
জন্য প্রপার্টি সেটার সত্য/মিথ্যা মানগুলি পরিচালনা করে এবং বৈশিষ্ট্যের অবস্থার সাথে প্রতিফলিত করে। এটা মনে রাখা গুরুত্বপূর্ণ যে সম্পত্তি সেটারের কোনো পার্শ্বপ্রতিক্রিয়া নেই। উদাহরণস্বরূপ, সেটার aria-selected
সেট করে না। পরিবর্তে, সেই কাজটি attributeChangedCallback
ঘটে। একটি সাধারণ নিয়ম হিসাবে, প্রপার্টি সেটারগুলিকে খুব বোবা করে তুলুন, এবং যদি কোনও সম্পত্তি বা বৈশিষ্ট্য সেট করার ফলে একটি পার্শ্ব প্রতিক্রিয়া সৃষ্টি হয় (যেমন একটি সংশ্লিষ্ট ARIA বৈশিষ্ট্য সেট করা) attributeChangedCallback()
এ কাজটি করুন। এটি জটিল বৈশিষ্ট্য/সম্পত্তি পুনঃপ্রবেশের পরিস্থিতি পরিচালনা করা এড়াবে।
attributeChangedCallback() {
const value = this.hasAttribute('selected');
this.setAttribute('aria-selected', value);
this.setAttribute('tabindex', value ? 0 : -1);
}
set selected(value) {
value = Boolean(value);
if (value)
this.setAttribute('selected', '');
else
this.removeAttribute('selected');
}
get selected() {
return this.hasAttribute('selected');
}
}
customElements.define('howto-tab', HowtoTab);
let howtoPanelCounter = 0;
HowtoPanel
হল একটি <howto-tabs>
ট্যাব প্যানেলের একটি প্যানেল।
class HowtoPanel extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.setAttribute('role', 'tabpanel');
if (!this.id)
this.id = `howto-panel-generated-${howtoPanelCounter++}`;
}
}
customElements.define('howto-panel', HowtoPanel);
})();