Fixed #4169 - TabMenu: disabled item defect & accessibility improvements

pull/4176/head
Tuğçe Küçükoğlu 2023-07-20 11:09:12 +03:00
parent e91e977a62
commit 671fb20b44
1 changed files with 76 additions and 77 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<div :class="cx('root')" v-bind="ptm('root')" data-pc-name="tabmenu"> <div :class="cx('root')" v-bind="ptm('root')" data-pc-name="tabmenu">
<ul ref="nav" :class="cx('menu')" role="menubar" :aria-labelledby="ariaLabelledby" :aria-label="ariaLabel" v-bind="ptm('menu')"> <ul ref="nav" :class="cx('menu')" role="menubar" :aria-labelledby="ariaLabelledby" :aria-label="ariaLabel" v-bind="ptm('menu')">
<template v-for="(item, i) of focusableItems" :key="label(item) + '_' + i.toString()"> <template v-for="(item, i) of model" :key="label(item) + '_' + i.toString()">
<router-link v-if="item.to && !disabled(item)" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom> <router-link v-if="item.to && !disabled(item)" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom>
<li <li
v-if="visible(item)" v-if="visible(item)"
@ -22,9 +22,9 @@
:class="cx('action')" :class="cx('action')"
:aria-label="label(item)" :aria-label="label(item)"
:aria-disabled="disabled(item)" :aria-disabled="disabled(item)"
:tabindex="isExactActive ? '0' : '-1'" :tabindex="-1"
@click="onItemClick($event, item, i, navigate)" @click="onItemClick($event, item, i, navigate)"
@keydown="onKeydownItem($event, i, navigate)" @keydown="onKeydownItem($event, item, i, navigate)"
v-bind="getPTOptions('action', i)" v-bind="getPTOptions('action', i)"
> >
<component v-if="$slots.itemicon" :is="$slots.itemicon" :item="item" :class="[cx('icon'), item.icon]" /> <component v-if="$slots.itemicon" :is="$slots.itemicon" :item="item" :class="[cx('icon'), item.icon]" />
@ -41,13 +41,13 @@
:class="[cx('menuitem', { item, index: i }), item.class]" :class="[cx('menuitem', { item, index: i }), item.class]"
role="presentation" role="presentation"
@click="onItemClick($event, item, i)" @click="onItemClick($event, item, i)"
@keydown="onKeydownItem($event, i)" @keydown="onKeydownItem($event, item, i)"
v-bind="getPTOptions('menuitem', i)" v-bind="getPTOptions('menuitem', i)"
:data-p-highlight="d_activeIndex === i" :data-p-highlight="d_activeIndex === i"
:data-p-disabled="disabled(item)" :data-p-disabled="disabled(item)"
> >
<template v-if="!$slots.item"> <template v-if="!$slots.item">
<a ref="tabLink" v-ripple role="menuitem" :href="item.url" :class="cx('action')" :target="item.target" :aria-label="label(item)" :aria-disabled="disabled(item)" :tabindex="setTabIndex(i)" v-bind="getPTOptions('action', i)"> <a ref="tabLink" v-ripple role="menuitem" :href="item.url" :class="cx('action')" :target="item.target" :aria-label="label(item)" :aria-disabled="disabled(item)" :tabindex="-1" v-bind="getPTOptions('action', i)">
<component v-if="$slots.itemicon" :is="$slots.itemicon" :item="item" :class="[cx('icon'), item.icon]" /> <component v-if="$slots.itemicon" :is="$slots.itemicon" :item="item" :class="[cx('icon'), item.icon]" />
<span v-else-if="item.icon" :class="[cx('icon'), item.icon]" v-bind="getPTOptions('icon', i)" /> <span v-else-if="item.icon" :class="[cx('icon'), item.icon]" v-bind="getPTOptions('icon', i)" />
<span :class="cx('label')" v-bind="getPTOptions('label', i)">{{ label(item) }}</span> <span :class="cx('label')" v-bind="getPTOptions('label', i)">{{ label(item) }}</span>
@ -86,6 +86,10 @@ export default {
}, },
mounted() { mounted() {
this.updateInkBar(); this.updateInkBar();
const activeItem = this.findActiveItem();
activeItem && (activeItem.tabIndex = '0');
}, },
updated() { updated() {
this.updateInkBar(); this.updateInkBar();
@ -129,39 +133,28 @@ export default {
index: index index: index
}); });
}, },
onKeydownItem(event, index) { onKeydownItem(event, item, index, navigate) {
let i = index;
let foundElement = {};
const tabLinkRef = this.$refs.tabLink;
switch (event.code) { switch (event.code) {
case 'ArrowRight': { case 'ArrowRight': {
foundElement = this.findNextItem(this.$refs.tab, i); this.navigateToNextItem(event.target);
i = foundElement.i; event.preventDefault();
break; break;
} }
case 'ArrowLeft': { case 'ArrowLeft': {
foundElement = this.findPrevItem(this.$refs.tab, i); this.navigateToPrevItem(event.target);
i = foundElement.i;
break;
}
case 'End': {
foundElement = this.findPrevItem(this.$refs.tab, this.model.length);
i = foundElement.i;
event.preventDefault(); event.preventDefault();
break; break;
} }
case 'Home': { case 'Home': {
foundElement = this.findNextItem(this.$refs.tab, -1); this.navigateToFirstItem(event.target);
i = foundElement.i; event.preventDefault();
break;
}
case 'End': {
this.navigateToLastItem(event.target);
event.preventDefault(); event.preventDefault();
break; break;
} }
@ -169,73 +162,88 @@ export default {
case 'Space': case 'Space':
case 'Enter': { case 'Enter': {
if (event.currentTarget) { this.onItemClick(event, item, index, navigate);
event.currentTarget.click();
}
event.preventDefault(); event.preventDefault();
break; break;
} }
case 'Tab': { case 'Tab': {
this.setDefaultTabIndexes(tabLinkRef); this.onTabKey();
break; break;
} }
default: default:
break; break;
} }
if (tabLinkRef[i] && tabLinkRef[index]) {
tabLinkRef[index].tabIndex = '-1';
tabLinkRef[i].tabIndex = '0';
tabLinkRef[i].focus();
}
}, },
findNextItem(items, index) { navigateToNextItem(target) {
let i = index + 1; const nextItem = this.findNextItem(target);
if (i >= items.length) { nextItem && this.setFocusToMenuitem(target, nextItem);
return { nextItem: items[items.length], i: items.length };
}
let nextItem = items[i];
if (nextItem) return DomHandler.getAttribute(nextItem, 'data-p-disabled') ? this.findNextItem(items, i) : { nextItem, i };
else return null;
}, },
findPrevItem(items, index) { navigateToPrevItem(target) {
let i = index - 1; const prevItem = this.findPrevItem(target);
if (i < 0) { prevItem && this.setFocusToMenuitem(target, prevItem);
return { nextItem: items[0], i: 0 }; },
navigateToFirstItem(target) {
const firstItem = this.findFirstItem(target);
firstItem && this.setFocusToMenuitem(target, firstItem);
},
navigateToLastItem(target) {
const lastItem = this.findLastItem(target);
lastItem && this.setFocusToMenuitem(target, lastItem);
},
findNextItem(item) {
const nextItem = item.parentElement.nextElementSibling;
return nextItem ? (DomHandler.getAttribute(nextItem, 'data-p-disabled') === true ? this.findNextItem(nextItem.children[0]) : nextItem.children[0]) : null;
},
findPrevItem(item) {
const prevItem = item.parentElement.previousElementSibling;
return prevItem ? (DomHandler.getAttribute(prevItem, 'data-p-disabled') === true ? this.findPrevItem(prevItem.children[0]) : prevItem.children[0]) : null;
},
findFirstItem() {
const firstSibling = DomHandler.findSingle(this.$refs.nav, '[data-pc-section="menuitem"][data-p-disabled="false"]');
return firstSibling ? firstSibling.children[0] : null;
},
findLastItem() {
const siblings = DomHandler.find(this.$refs.nav, '[data-pc-section="menuitem"][data-p-disabled="false"]');
return siblings ? siblings[siblings.length - 1].children[0] : null;
},
findActiveItem() {
const activeItem = DomHandler.findSingle(this.$refs.nav, '[data-pc-section="menuitem"][data-p-disabled="false"][data-p-highlight="true"]');
return activeItem ? activeItem.children[0] : null;
},
setFocusToMenuitem(target, focusableItem) {
target.tabIndex = '-1';
focusableItem.tabIndex = '0';
focusableItem.focus();
},
onTabKey() {
const activeItem = DomHandler.findSingle(this.$refs.nav, '[data-pc-section="menuitem"][data-p-disabled="false"][data-p-highlight="true"]');
const focusedItem = DomHandler.findSingle(this.$refs.nav, '[data-pc-section="action"][tabindex="0"]');
if (focusedItem !== activeItem.children[0]) {
activeItem && (activeItem.children[0].tabIndex = '0');
focusedItem.tabIndex = '-1';
} }
let prevItem = items[i];
if (prevItem) return DomHandler.getAttribute(prevItem, 'data-p-disabled') ? this.findPrevItem(items, i) : { prevItem, i };
else return null;
}, },
visible(item) { visible(item) {
return typeof item.visible === 'function' ? item.visible() : item.visible !== false; return typeof item.visible === 'function' ? item.visible() : item.visible !== false;
}, },
disabled(item) { disabled(item) {
return typeof item.disabled === 'function' ? item.disabled() : item.disabled; return typeof item.disabled === 'function' ? item.disabled() : item.disabled === true;
}, },
label(item) { label(item) {
return typeof item.label === 'function' ? item.label() : item.label; return typeof item.label === 'function' ? item.label() : item.label;
}, },
setDefaultTabIndexes(tabLinkRef) {
setTimeout(() => {
tabLinkRef.forEach((item) => {
item.tabIndex = DomHandler.getAttribute(item.parentElement, 'data-p-highlight') ? '0' : '-1';
});
}, 300);
},
setTabIndex(index) {
return this.d_activeIndex === index ? '0' : '-1';
},
updateInkBar() { updateInkBar() {
let tabs = this.$refs.nav.children; let tabs = this.$refs.nav.children;
let inkHighlighted = false; let inkHighlighted = false;
@ -256,15 +264,6 @@ export default {
} }
} }
}, },
computed: {
focusableItems() {
return (this.model || []).reduce((result, item) => {
this.visible(item) && !this.disabled(item) && result.push(item);
return result;
}, []);
}
},
directives: { directives: {
ripple: Ripple ripple: Ripple
} }