Fixed #2895 - Improve Accordion implementation for Accessibility

pull/2898/head
mertsincan 2022-08-29 02:15:56 +01:00
parent 67320fb0d8
commit 3bdaf4a2b9
7 changed files with 446 additions and 91 deletions

View File

@ -28,6 +28,18 @@ const AccordionProps = [
type: "string",
default: "pi-chevron-down",
description: "Icon of a expanded tab."
},
{
name: "tabindex",
type: "number",
default: "0",
description: "Index of the element in tabbing order."
},
{
name: "selectOnFocus",
type: "boolean",
default: "false",
description: "When enabled, the focused tab is activated."
}
];
@ -63,6 +75,22 @@ const AccordionEvents = [
description: "Closed tab index"
}
]
},
{
name: "tab-click",
description: "Callback to invoke when an active tab is clicked.",
arguments: [
{
name: "originalEvent",
type: "object",
description: "Original event"
},
{
name: "index",
type: "number",
description: "Index of the clicked tab"
}
]
}
];

View File

@ -5,6 +5,48 @@ const AccordionTabProps = [
default: "null",
description: "Orientation of tab headers."
},
{
name: "headerStyle",
type: "any",
default: "null",
description: "Inline style of the tab header."
},
{
name: "headerClass",
type: "any",
default: "null",
description: "Style class of the tab header."
},
{
name: "headerProps",
type: "any",
default: "null",
description: "Uses to pass all properties of the HTMLDivElement to the tab header."
},
{
name: "headerActionProps",
type: "any",
default: "null",
description: "Uses to pass all properties of the HTMLAnchorElement to the focusable anchor element inside the tab header."
},
{
name: "contentStyle",
type: "any",
default: "null",
description: "Inline style of the tab content."
},
{
name: "contentClass",
type: "any",
default: "null",
description: "Style class of the tab content."
},
{
name: "contentProps",
type: "any",
default: "null",
description: "Uses to pass all properties of the HTMLDivElement to the tab content."
},
{
name: "disabled",
type: "boolean",

View File

@ -18,6 +18,11 @@ export interface AccordionTabOpenEvent {
*/
export interface AccordionTabCloseEvent extends AccordionTabOpenEvent { }
/**
* @extends AccordionTabOpenEvent
*/
export interface AccordionClickEvent extends AccordionTabOpenEvent { }
export interface AccordionProps {
/**
* When enabled, multiple tabs can be activated at the same time.
@ -39,6 +44,14 @@ export interface AccordionProps {
* Icon of an expanded tab.
*/
collapseIcon?: string | undefined;
/**
* Index of the element in tabbing order.
*/
tabindex?: number | undefined;
/**
* When enabled, the focused tab is activated.
*/
selectOnFocus?: boolean | undefined;
}
export interface AccordionSlots {
@ -64,6 +77,11 @@ export declare type AccordionEmits = {
* @param {AccordionTabCloseEvent} event - Custom tab close event.
*/
'tab-close': (event: AccordionTabCloseEvent) => void;
/**
* Callback to invoke when an active tab is clicked.
* @param {AccordionClickEvent} event - Custom tab click event.
*/
'tab-click': (event: AccordionClickEvent) => void;
}
declare class Accordion extends ClassComponent<AccordionProps, AccordionSlots, AccordionEmits> { }

View File

@ -1,17 +1,18 @@
<template>
<div class="p-accordion p-component">
<div v-for="(tab,i) of tabs" :key="getKey(tab,i)" :class="getTabClass(i)">
<div :class="getTabHeaderClass(tab, i)">
<a role="tab" class="p-accordion-header-link" @click="onTabClick($event, tab, i)" @keydown="onTabKeydown($event, tab, i)" :tabindex="isTabDisabled(tab) ? null : '0'"
:aria-expanded="isTabActive(i)" :id="getTabAriaId(i) + '_header'" :aria-controls="getTabAriaId(i) + '_content'">
<span :class="isTabActive(i) ? getHeaderCollapseIcon() : getHeaderExpandIcon()"></span>
<div v-for="(tab,i) of tabs" :key="getKey(tab,i)" :class="getTabClass(i)" :data-index="i">
<div :style="getTabProp(tab, 'headerStyle')" :class="getTabHeaderClass(tab, i)" v-bind="getTabProp(tab, 'headerProps')">
<a :id="getTabHeaderActionId(i)" class="p-accordion-header-link p-accordion-header-action" :tabindex="getTabProp(tab, 'disabled') ? -1 : tabindex"
role="button" :aria-disabled="getTabProp(tab, 'disabled')" :aria-expanded="isTabActive(i)" :aria-controls="getTabContentId(i)"
@click="onTabClick($event, tab, i)" @keydown="onTabKeyDown($event, tab, i)" v-bind="getTabProp(tab, 'headerActionProps')">
<span :class="getTabHeaderIconClass(i)" aria-hidden="true"></span>
<span class="p-accordion-header-text" v-if="tab.props && tab.props.header">{{tab.props.header}}</span>
<component :is="tab.children.header" v-if="tab.children && tab.children.header"></component>
</a>
</div>
<transition name="p-toggleable-content">
<div class="p-toggleable-content" v-if="lazy ? isTabActive(i) : true" v-show="lazy ? true: isTabActive(i)"
role="region" :id="getTabAriaId(i) + '_content'" :aria-labelledby="getTabAriaId(i) + '_header'">
<div v-if="lazy ? isTabActive(i) : true" v-show="lazy ? true: isTabActive(i)" :id="getTabContentId(i)" :style="getTabProp(tab, 'contentStyle')" :class="getTabContentClass(tab)"
role="region" :aria-labelledby="getTabHeaderActionId(i)" v-bind="getTabProp(tab, 'contentProps')">
<div class="p-accordion-content">
<component :is="tab"></component>
</div>
@ -22,11 +23,12 @@
</template>
<script>
import {UniqueComponentId} from 'primevue/utils';
import {UniqueComponentId,DomHandler} from 'primevue/utils';
import Ripple from 'primevue/ripple';
export default {
name: 'Accordion',
emits: ['tab-close', 'tab-open', 'update:activeIndex'],
emits: ['update:activeIndex', 'tab-open', 'tab-close', 'tab-click'],
props: {
multiple: {
type: Boolean,
@ -47,10 +49,19 @@ export default {
collapseIcon: {
type: String,
default: 'pi-chevron-down'
},
tabindex: {
type: Number,
default: 0
},
selectOnFocus: {
type: Boolean,
default: false
}
},
data() {
return {
id: UniqueComponentId(),
d_activeIndex: this.activeIndex
}
},
@ -59,98 +70,186 @@ export default {
this.d_activeIndex = newValue;
}
},
mounted() {
this.id = this.$attrs.id || this.id;
},
methods: {
onTabClick(event, tab, i) {
if (!this.isTabDisabled(tab)) {
const active = this.isTabActive(i);
isAccordionTab(child) {
return child.type.name === 'AccordionTab';
},
isTabActive(index) {
return this.multiple ? (this.d_activeIndex && this.d_activeIndex.includes(index)) : this.d_activeIndex === index;
},
getTabProp(tab, name) {
return tab.props ? tab.props[name] : undefined;
},
getKey(tab, index) {
return this.getTabProp(tab, 'header') || index;
},
getTabHeaderActionId(index) {
return `${this.id}_${index}_header_action`;
},
getTabContentId(index) {
return `${this.id}_${index}_content`;
},
onTabClick(event, tab, index) {
this.changeActiveIndex(event, tab, index);
this.$emit('tab-click', { originalEvent: event, index });
},
onTabKeyDown(event, tab, index) {
switch (event.code) {
case 'ArrowDown':
this.onTabArrowDownKey(event);
break;
case 'ArrowUp':
this.onTabArrowUpKey(event);
break;
case 'Home':
this.onTabHomeKey(event);
break;
case 'End':
this.onTabEndKey(event);
break;
case 'Enter':
case 'Space':
this.onTabEnterKey(event, tab, index);
break;
default:
break;
}
},
onTabArrowDownKey(event) {
const nextHeaderAction = this.findNextHeaderAction(event.target.parentElement.parentElement);
nextHeaderAction ? this.changeFocusedTab(event, nextHeaderAction) : this.onTabHomeKey(event);
event.preventDefault();
},
onTabArrowUpKey(event) {
const prevHeaderAction = this.findPrevHeaderAction(event.target.parentElement.parentElement);
prevHeaderAction ? this.changeFocusedTab(event, prevHeaderAction) : this.onTabEndKey(event);
event.preventDefault();
},
onTabHomeKey(event) {
const firstHeaderAction = this.findFirstHeaderAction();
this.changeFocusedTab(event, firstHeaderAction);
event.preventDefault();
},
onTabEndKey(event) {
const lastHeaderAction = this.findLastHeaderAction();
this.changeFocusedTab(event, lastHeaderAction);
event.preventDefault();
},
onTabEnterKey(event, tab, index) {
this.changeActiveIndex(event, tab, index);
event.preventDefault();
},
findNextHeaderAction(tabElement, selfCheck = false) {
const nextTabElement = selfCheck ? tabElement : tabElement.nextElementSibling;
const headerElement = DomHandler.findSingle(nextTabElement, '.p-accordion-header');
return headerElement ? (DomHandler.hasClass(headerElement, 'p-disabled') ? this.findNextHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '.p-accordion-header-action')) : null;
},
findPrevHeaderAction(tabElement, selfCheck = false) {
const prevTabElement = selfCheck ? tabElement : tabElement.previousElementSibling;
const headerElement = DomHandler.findSingle(prevTabElement, '.p-accordion-header');
return headerElement ? (DomHandler.hasClass(headerElement, 'p-disabled') ? this.findPrevHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '.p-accordion-header-action')) : null;
},
findFirstHeaderAction() {
return this.findNextHeaderAction(this.$el.firstElementChild, true);
},
findLastHeaderAction() {
return this.findPrevHeaderAction(this.$el.lastElementChild, true);
},
changeActiveIndex(event, tab, index) {
if (!this.getTabProp(tab, 'disabled')) {
const active = this.isTabActive(index);
const eventName = active ? 'tab-close' : 'tab-open';
if (this.multiple) {
if (active) {
this.d_activeIndex = this.d_activeIndex.filter(index => index !== i);
this.d_activeIndex = this.d_activeIndex.filter(i => i !== index);
}
else {
if (this.d_activeIndex)
this.d_activeIndex.push(i);
this.d_activeIndex.push(index);
else
this.d_activeIndex = [i];
this.d_activeIndex = [index];
}
}
else {
this.d_activeIndex = this.d_activeIndex === i ? null : i;
this.d_activeIndex = this.d_activeIndex === index ? null : index;
}
this.$emit('update:activeIndex', this.d_activeIndex);
this.$emit(eventName, { originalEvent: event, index });
}
},
changeFocusedTab(event, element) {
if (element) {
element.focus();
this.$emit(eventName, {
originalEvent: event,
index: i
});
if (this.selectOnFocus) {
const index = parseInt(element.parentElement.parentElement.dataset.index, 10);
const tab = this.tabs[index];
this.changeActiveIndex(event, tab, index);
}
}
},
onTabKeydown(event, tab, i) {
if (event.which === 13) {
this.onTabClick(event, tab, i);
}
},
isTabActive(index) {
if (this.multiple)
return this.d_activeIndex && this.d_activeIndex.includes(index);
else
return index === this.d_activeIndex;
},
getKey(tab, i) {
return (tab.props && tab.props.header) ? tab.props.header : i;
},
isTabDisabled(tab) {
return tab.props && tab.props.disabled;
},
getTabClass(i) {
return ['p-accordion-tab', {'p-accordion-tab-active': this.isTabActive(i)}];
return ['p-accordion-tab', {
'p-accordion-tab-active': this.isTabActive(i)
}];
},
getTabHeaderClass(tab, i) {
return ['p-accordion-header', {'p-highlight': this.isTabActive(i), 'p-disabled': this.isTabDisabled(tab)}];
return ['p-accordion-header', this.getTabProp(tab, 'headerClass'), {
'p-highlight': this.isTabActive(i),
'p-disabled': this.getTabProp(tab, 'disabled')
}];
},
getTabAriaId(i) {
return this.ariaId + '_' + i;
getTabHeaderIconClass(i) {
return ['p-accordion-toggle-icon pi', this.isTabActive(i) ? this.collapseIcon : this.expandIcon];
},
getHeaderCollapseIcon() {
return ['p-accordion-toggle-icon pi', this.collapseIcon];
},
getHeaderExpandIcon() {
return ['p-accordion-toggle-icon pi', this.expandIcon];
},
isAccordionTab(child) {
return child.type.name === 'AccordionTab';
getTabContentClass(tab) {
return ['p-toggleable-content', this.getTabProp(tab, 'contentClass')];
}
},
computed: {
tabs() {
const tabs = []
this.$slots.default().forEach(child => {
if (this.isAccordionTab(child)) {
tabs.push(child);
}
else if (child.children && child.children instanceof Array) {
child.children.forEach(nestedChild => {
if (this.isAccordionTab(nestedChild)) {
tabs.push(nestedChild)
}
});
}
return this.$slots.default().reduce((tabs, child) => {
if (this.isAccordionTab(child)) {
tabs.push(child);
}
)
return tabs;
},
ariaId() {
return UniqueComponentId();
else if (child.children && child.children instanceof Array) {
child.children.forEach(nestedChild => {
if (this.isAccordionTab(nestedChild)) {
tabs.push(nestedChild);
}
});
}
return tabs;
}, []);
}
},
directives: {
'ripple': Ripple
}
}
</script>
<style>
.p-accordion-header-link {
.p-accordion-header-action {
cursor: pointer;
display: flex;
align-items: center;
@ -159,7 +258,7 @@ export default {
text-decoration: none;
}
.p-accordion-header-link:focus {
.p-accordion-header-action:focus {
z-index: 1;
}

View File

@ -1,4 +1,4 @@
import { VNode } from 'vue';
import { AnchorHTMLAttributes, HTMLAttributes, VNode } from 'vue';
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
export interface AccordionTabProps {
@ -6,6 +6,34 @@ export interface AccordionTabProps {
* Orientation of tab headers.
*/
header?: string | undefined;
/**
* Inline style of the tab header.
*/
headerStyle?: any;
/**
* Style class of the tab header.
*/
headerClass?: any;
/**
* Uses to pass all properties of the HTMLDivElement to the tab header.
*/
headerProps?: HTMLAttributes | undefined;
/**
* Uses to pass all properties of the HTMLAnchorElement to the focusable anchor element inside the tab header.
*/
headerActionProps?: AnchorHTMLAttributes | undefined;
/**
* Inline style of the tab content.
*/
contentStyle?: any;
/**
* Style class of the tab content.
*/
contentClass?: any;
/**
* Uses to pass all properties of the HTMLDivElement to the tab content.
*/
contentProps?: HTMLAttributes | undefined;
/**
* Whether the tab is disabled.
*/

View File

@ -7,6 +7,13 @@ export default {
name: 'AccordionTab',
props: {
header: null,
headerStyle: null,
headerClass: null,
headerProps: null,
headerActionProps: null,
contentStyle: null,
contentClass: null,
contentProps: null,
disabled: Boolean
}
}

View File

@ -218,18 +218,60 @@ export default {
</tr>
</thead>
<tbody>
<tr>
<td>header</td>
<td>string</td>
<td>null</td>
<td>Orientation of tab headers.</td>
</tr>
<tr>
<td>disabled</td>
<td>boolean</td>
<td>false</td>
<td>Whether the tab is disabled.</td>
</tr>
<tr>
<td>header</td>
<td>string</td>
<td>null</td>
<td>Orientation of tab headers.</td>
</tr>
<tr>
<td>headerStyle</td>
<td>string</td>
<td>null</td>
<td>Inline style of the tab header.</td>
</tr>
<tr>
<td>headerClass</td>
<td>string</td>
<td>null</td>
<td>Style class of the tab header.</td>
</tr>
<tr>
<td>headerProps</td>
<td>object</td>
<td>null</td>
<td>Uses to pass all properties of the HTMLDivElement to the tab header.</td>
</tr>
<tr>
<td>headerActionProps</td>
<td>object</td>
<td>null</td>
<td>Uses to pass all properties of the HTMLAnchorElement to the focusable anchor element inside the tab header.</td>
</tr>
<tr>
<td>contentStyle</td>
<td>string</td>
<td>null</td>
<td>Inline style of the tab content.</td>
</tr>
<tr>
<td>contentClass</td>
<td>string</td>
<td>null</td>
<td>Style class of the tab content.</td>
</tr>
<tr>
<td>contentProps</td>
<td>object</td>
<td>null</td>
<td>Uses to pass all properties of the HTMLDivElement to the tab content.</td>
</tr>
<tr>
<td>disabled</td>
<td>boolean</td>
<td>false</td>
<td>Whether the tab is disabled.</td>
</tr>
</tbody>
</table>
</div>
@ -276,6 +318,18 @@ export default {
<td>string</td>
<td>pi-chevron-down</td>
<td>Icon of an expanded tab.</td>
</tr>
<tr>
<td>tabindex</td>
<td>number</td>
<td>0</td>
<td>Index of the element in tabbing order.</td>
</tr>
<tr>
<td>selectOnFocus</td>
<td>boolean</td>
<td>false</td>
<td>When enabled, the focused tab is activated.</td>
</tr>
</tbody>
</table>
@ -305,6 +359,13 @@ export default {
event.index: Closed tab index
</td>
<td>Callback to invoke when an active tab is collapsed by clicking on the header.</td>
</tr>
<tr>
<td>tab-click</td>
<td>event.originalEvent: Browser event <br/>
event.index: Index of the clicked tab
</td>
<td>Callback to invoke when an active tab is clicked.</td>
</tr>
</tbody>
</table>
@ -337,6 +398,78 @@ export default {
</table>
</div>
<h5>Accessibility</h5>
<h6>Screen Reader</h6>
<p>
Accordion header elements have a <i>button</i> role and use <i>aria-controls</i> to define the id of the content section along with <i>aria-expanded</i> for the visibility state. The value to read a header element defaults
to the value of the <i>header</i> property and can be customized by defining an <i>aria-label</i> or <i>aria-labelledby</i> via the <i>headerActionProps</i> property.
</p>
<p>
The content uses <i>region</i> role, defines an id that matches the <i>aria-controls</i> of the header and <i>aria-labelledby</i> referring to the id of the header.
</p>
<h6>Header Keyboard Support</h6>
<div className="doc-tablewrapper">
<table className="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves focus to the next the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>shift</i> + <i>tab</i>
</td>
<td>Moves focus to the previous the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>enter</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>space</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>down arrow</i>
</td>
<td>Moves focus to the next header. If focus is on the last header, moves focus to the first header.</td>
</tr>
<tr>
<td>
<i>up arrow</i>
</td>
<td>Moves focus to the previous header. If focus is on the first header, moves focus to the last header.</td>
</tr>
<tr>
<td>
<i>home</i>
</td>
<td>Moves focus to the first header.</td>
</tr>
<tr>
<td>
<i>end</i>
</td>
<td>Moves focus to the last header.</td>
</tr>
</tbody>
</table>
</div>
<h5>Dependencies</h5>
<p>None.</p>
</AppDoc>
@ -442,15 +575,15 @@ export default {
active: 0,
tabs: [
{
title: "Header I",
title: "Header I",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
title: "Header II",
title: "Header II",
content: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi."
},
{
title: "Header III",
title: "Header III",
content: "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus."
}
]
@ -572,15 +705,15 @@ export default {
const active = ref(0);
const tabs = ref([
{
title: "Header I",
title: "Header I",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
title: "Header II",
title: "Header II",
content: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi."
},
{
title: "Header III",
title: "Header III",
content: "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus."
}
]);
@ -702,15 +835,15 @@ export default {
const active = ref(0);
const tabs = ref([
{
title: "Header I",
title: "Header I",
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
},
{
title: "Header II",
title: "Header II",
content: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi."
},
{
title: "Header III",
title: "Header III",
content: "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus."
}
]);
@ -750,4 +883,4 @@ export default {
}
}
}
</script>
</script>