Fixed #5643 - Reimplement: `Accordion`

pull/5677/head
mertsincan 2024-04-24 23:42:28 +01:00
parent 6cbb5e0993
commit 10f530f7b1
30 changed files with 1049 additions and 243 deletions

View File

@ -90,6 +90,7 @@ export interface AccordionPassThroughOptions {
tab?: AccordionTabPassThroughOptionType;
/**
* Used to pass attributes to AccordionTab helper components.
* @deprecated since v4. Use new structure instead.
*/
accordiontab?: AccordionTabPassThroughOptionType;
/**
@ -115,15 +116,20 @@ export interface AccordionState {
*/
id: string;
/**
* Current active index state.
* Current active value state.
*/
d_activeIndex: number | number[];
d_value: string | string[];
}
/**
* Defines valid properties in Accordion component.
*/
export interface AccordionProps {
/**
* Value of the active panel or an array of values in multiple mode.
* @defaultValue null
*/
value?: string | string[] | null | undefined;
/**
* When enabled, multiple tabs can be activated at the same time.
* @defaultValue false
@ -132,6 +138,7 @@ export interface AccordionProps {
/**
* Index of the active tab or an array of indexes in multiple mode.
* @defaultValue null
* @deprecated since v4. Use value property instead.
*/
activeIndex?: number | number[] | null | undefined;
/**
@ -200,24 +207,33 @@ export interface AccordionSlots {
* Defines valid emits in Accordion component.
*/
export interface AccordionEmits {
/**
* Emitted when the active panel changes.
* @param {string | string[] | null | undefined} value - Value of new active panel.
*/
'update:value'(value: string | string[] | null | undefined): void;
/**
* Emitted when the active tab changes.
* @param {number | undefined} value - Index of new active tab.
* @deprecated since v4. Use update:value emit instead.
*/
'update:activeIndex'(value: number | undefined): void;
/**
* Callback to invoke when a tab gets expanded.
* @param {AccordionTabOpenEvent} event - Custom tab open event.
* @deprecated since v4.
*/
'tab-open'(event: AccordionTabOpenEvent): void;
/**
* Callback to invoke when an active tab is collapsed by clicking on the header.
* @param {AccordionTabCloseEvent} event - Custom tab close event.
* @deprecated since v4.
*/
'tab-close'(event: AccordionTabCloseEvent): void;
/**
* Callback to invoke when an active tab is clicked.
* @param {AccordionClickEvent} event - Custom tab click event.
* @deprecated since v4.
*/
'tab-click'(event: AccordionClickEvent): void;
}

View File

@ -1,63 +1,39 @@
<template>
<div :class="cx('root')" v-bind="ptmi('root')">
<div v-for="(tab, i) of tabs" :key="getKey(tab, i)" :class="cx('tab.root', { tab, index: i })" v-bind="getTabPT(tab, 'root', i)" data-pc-name="accordiontab" :data-pc-index="i" :data-p-active="isTabActive(i)">
<div
:style="getTabProp(tab, 'headerStyle')"
:class="[cx('tab.header', { tab, index: i }), getTabProp(tab, 'headerClass')]"
v-bind="{ ...getTabProp(tab, 'headerProps'), ...getTabPT(tab, 'header', i) }"
:data-p-highlight="isTabActive(i)"
:data-p-disabled="getTabProp(tab, 'disabled')"
>
<a
:id="getTabHeaderActionId(i)"
:class="cx('tab.headerAction')"
: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'), ...getTabPT(tab, 'headeraction', i) }"
>
<component v-if="tab.children && tab.children.headericon" :is="tab.children.headericon" :isTabActive="isTabActive(i)" :active="isTabActive(i)" :index="i"></component>
<template v-if="hasAccordionTab">
<AccordionPanel v-for="(tab, i) of tabs" :key="getKey(tab, i)" :value="`${i}`" :pt="{ root: getTabPT(tab, 'root', i) }" :disabled="getTabProp(tab, 'disabled')">
<AccordionHeader :class="getTabProp(tab, 'headerClass')" :pt="getHeaderPT(tab, i)">
<component v-if="tab.children && tab.children.headericon" :is="tab.children.headericon" :isTabActive="isItemActive(`${i}`)" :active="isItemActive(`${i}`)" :index="i"></component>
<span v-if="tab.props && tab.props.header" v-bind="getTabPT(tab, 'headertitle', i)">{{ tab.props.header }}</span>
<template #toggleicon="slotProps">
<component
v-else-if="isTabActive(i)"
v-if="slotProps.active"
:is="$slots.collapseicon ? $slots.collapseicon : collapseIcon ? 'span' : 'ChevronDownIcon'"
:class="[cx('tab.headerIcon'), collapseIcon]"
:class="[collapseIcon, slotProps.class]"
aria-hidden="true"
v-bind="getTabPT(tab, 'headericon', i)"
/>
<component v-else :is="$slots.expandicon ? $slots.expandicon : expandIcon ? 'span' : 'ChevronRightIcon'" :class="[cx('tab.headerIcon'), expandIcon]" aria-hidden="true" v-bind="getTabPT(tab, 'headericon', i)" />
<span v-if="tab.props && tab.props.header" :class="cx('tab.headerTitle')" v-bind="getTabPT(tab, 'headertitle', i)">{{ tab.props.header }}</span>
<component v-else :is="$slots.expandicon ? $slots.expandicon : expandIcon ? 'span' : 'ChevronUpIcon'" :class="[expandIcon, slotProps.class]" aria-hidden="true" v-bind="getTabPT(tab, 'headericon', i)" />
</template>
<component v-if="tab.children && tab.children.header" :is="tab.children.header"></component>
</a>
</div>
<transition name="p-toggleable-content" v-bind="getTabPT(tab, 'transition', i)">
<div
v-if="lazy ? isTabActive(i) : true"
v-show="lazy ? true : isTabActive(i)"
:id="getTabContentId(i)"
:style="getTabProp(tab, 'contentStyle')"
:class="[cx('tab.toggleableContent'), getTabProp(tab, 'contentClass')]"
role="region"
:aria-labelledby="getTabHeaderActionId(i)"
v-bind="{ ...getTabProp(tab, 'contentProps'), ...getTabPT(tab, 'toggleablecontent', i) }"
>
<div :class="cx('tab.content')" v-bind="getTabPT(tab, 'content', i)">
</AccordionHeader>
<AccordionContent :pt="getContentPT(tab, i)">
<component :is="tab"></component>
</div>
</div>
</transition>
</div>
</AccordionContent>
</AccordionPanel>
</template>
<slot v-else></slot>
</div>
</template>
<script>
import ChevronDownIcon from 'primevue/icons/chevrondown';
import AccordionContent from 'primevue/accordioncontent';
import AccordionHeader from 'primevue/accordionheader';
import AccordionPanel from 'primevue/accordionpanel';
import ChevronRightIcon from 'primevue/icons/chevronright';
import Ripple from 'primevue/ripple';
import { DomHandler, UniqueComponentId } from 'primevue/utils';
import ChevronUpIcon from 'primevue/icons/chevronup';
import { UniqueComponentId } from 'primevue/utils';
import { mergeProps } from 'vue';
import BaseAccordion from './BaseAccordion.vue';
@ -65,42 +41,78 @@ export default {
name: 'Accordion',
extends: BaseAccordion,
inheritAttrs: false,
emits: ['update:activeIndex', 'tab-open', 'tab-close', 'tab-click'],
emits: ['update:value', 'update:activeIndex', 'tab-open', 'tab-close', 'tab-click'],
data() {
return {
id: this.$attrs.id,
d_activeIndex: this.activeIndex
d_value: this.value
};
},
watch: {
'$attrs.id': function (newValue) {
this.id = newValue || UniqueComponentId();
},
activeIndex(newValue) {
this.d_activeIndex = newValue;
value(newValue) {
this.d_value = newValue;
},
activeIndex: {
immediate: true,
handler(newValue) {
if (this.hasAccordionTab) {
this.d_value = this.multiple ? newValue?.map(String) : newValue?.toString();
}
}
}
},
mounted() {
this.id = this.id || UniqueComponentId();
},
methods: {
isItemActive(value) {
return this.multiple ? this.d_value?.includes(value) : this.d_value === value;
},
updateValue(newValue) {
const active = this.isItemActive(newValue);
if (this.multiple) {
if (active) {
this.d_value = this.d_value.filter((v) => v !== newValue);
} else {
if (this.d_value) this.d_value.push(newValue);
else this.d_value = [newValue];
}
} else {
this.d_value = active ? null : newValue;
}
this.$emit('update:value', this.d_value);
// @deprecated since v4.
this.$emit('update:activeIndex', this.multiple ? this.d_value?.map(Number) : Number(this.d_value));
this.$emit(active ? 'tab-close' : 'tab-open', { originalEvent: undefined, index: Number(newValue) });
},
// @deprecated since v4. Use new structure instead.
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`;
getHeaderPT(tab, index) {
return {
root: mergeProps({ onClick: (event) => this.onTabClick(event, index) }, this.getTabProp(tab, 'headerProps'), this.getTabPT(tab, 'header', index)),
toggleicon: mergeProps(this.getTabProp(tab, 'headeractionprops'), this.getTabPT(tab, 'headeraction', index))
};
},
getTabContentId(index) {
return `${this.id}_${index}_content`;
getContentPT(tab, index) {
return {
root: mergeProps(this.getTabProp(tab, 'contentProps'), this.getTabPT(tab, 'toggleablecontent', index)),
transition: this.getTabPT(tab, 'transition', index),
content: this.getTabPT(tab, 'content', index)
};
},
getTabPT(tab, key, index) {
const count = this.tabs.length;
@ -116,125 +128,18 @@ export default {
count,
first: index === 0,
last: index === count - 1,
active: this.isTabActive(index)
active: this.isItemActive(`${index}`)
}
};
return mergeProps(this.ptm(`tab.${key}`, { tab: tabMetaData }), this.ptm(`accordiontab.${key}`, { accordiontab: tabMetaData }), this.ptm(`accordiontab.${key}`, tabMetaData), this.ptmo(this.getTabProp(tab, 'pt'), key, tabMetaData));
},
onTabClick(event, tab, index) {
this.changeActiveIndex(event, tab, index);
onTabClick(event, 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 'NumpadEnter':
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, '[data-pc-section="header"]');
return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? this.findNextHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '[data-pc-section="headeraction"]')) : null;
},
findPrevHeaderAction(tabElement, selfCheck = false) {
const prevTabElement = selfCheck ? tabElement : tabElement.previousElementSibling;
const headerElement = DomHandler.findSingle(prevTabElement, '[data-pc-section="header"]');
return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? this.findPrevHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '[data-pc-section="headeraction"]')) : 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((i) => i !== index);
} else {
if (this.d_activeIndex) this.d_activeIndex.push(index);
else this.d_activeIndex = [index];
}
} else {
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) {
DomHandler.focus(element);
if (this.selectOnFocus) {
const index = parseInt(element.parentElement.parentElement.dataset.pcIndex, 10);
const tab = this.tabs[index];
this.changeActiveIndex(event, tab, index);
}
}
}
},
computed: {
// @deprecated since v4.
tabs() {
return this.$slots.default().reduce((tabs, child) => {
if (this.isAccordionTab(child)) {
@ -249,14 +154,17 @@ export default {
return tabs;
}, []);
},
hasAccordionTab() {
return this.tabs.length;
}
},
components: {
ChevronDownIcon,
AccordionPanel,
AccordionHeader,
AccordionContent,
ChevronUpIcon,
ChevronRightIcon
},
directives: {
ripple: Ripple
}
};
</script>

View File

@ -6,18 +6,26 @@ export default {
name: 'BaseAccordion',
extends: BaseComponent,
props: {
value: {
type: [String, Array],
default: undefined
},
multiple: {
type: Boolean,
default: false
},
activeIndex: {
type: [Number, Array],
default: null
},
lazy: {
type: Boolean,
default: false
},
tabindex: {
type: Number,
default: 0
},
selectOnFocus: {
type: Boolean,
default: false
},
expandIcon: {
type: String,
default: undefined
@ -26,18 +34,16 @@ export default {
type: String,
default: undefined
},
tabindex: {
type: Number,
default: 0
},
selectOnFocus: {
type: Boolean,
default: false
// @deprecated since v4.
activeIndex: {
type: [Number, Array],
default: null
}
},
style: AccordionStyle,
provide() {
return {
$pcAccordion: this,
$parentInstance: this
};
}

View File

@ -1,27 +1,7 @@
import BaseStyle from 'primevue/base/style';
const classes = {
root: 'p-accordion p-component',
tab: {
root: ({ instance, index }) => [
'p-accordion-panel',
{
'p-accordion-panel-active': instance.isTabActive(index)
}
],
header: ({ instance, tab, index }) => [
'p-accordion-panel-header',
{
'p-accordion-panel-header-active': instance.isTabActive(index),
'p-disabled': instance.getTabProp(tab, 'disabled')
}
],
headerAction: 'p-accordion-panel-header-content',
headerIcon: 'p-accordion-panel-toggle-icon',
headerTitle: 'p-accordion-panel-title',
toggleableContent: 'p-accordion-panel-content-container',
content: 'p-accordion-panel-content'
}
root: 'p-accordion p-component'
};
export default BaseStyle.extend({

View File

@ -0,0 +1,147 @@
/**
*
* AccordionContent is a helper component for Accordion component.
*
* [Live Demo](https://www.primevue.org/accordion/)
*
* @module accordioncontent
*
*/
import { VNode } from 'vue';
import { ComponentHooks } from '../basecomponent';
import { PassThroughOptions } from '../passthrough';
import { ClassComponent, DesignToken, GlobalComponentConstructor, PassThrough } from '../ts-helpers';
export declare type AccordionContentPassThroughOptionType = AccordionContentPassThroughAttributes | ((options: AccordionContentPassThroughMethodOptions) => AccordionContentPassThroughAttributes | string) | string | null | undefined;
/**
* Custom passthrough(pt) option method.
*/
export interface AccordionContentPassThroughMethodOptions {
/**
* Defines instance.
*/
instance: any;
/**
* Defines valid properties.
*/
props: AccordionContentProps;
/**
* Defines current options.
*/
context: AccordionContentContext;
/**
* Defines valid attributes.
*/
attrs: any;
/**
* Defines parent options.
*/
parent: any;
/**
* Defines passthrough(pt) options in global config.
*/
global: object | undefined;
}
/**
* Custom passthrough(pt) options.
* @see {@link AccordionContentProps.pt}
*/
export interface AccordionContentPassThroughOptions {
/**
* Used to pass attributes to the root's DOM element.
*/
root?: AccordionContentPassThroughOptionType;
/**
* Used to pass attributes to the transition's DOM element.
*/
transition?: AccordionContentPassThroughOptionType;
/**
* Used to pass attributes to the content's DOM element.
*/
content?: AccordionContentPassThroughOptionType;
/**
* Used to manage all lifecycle hooks.
* @see {@link BaseComponent.ComponentHooks}
*/
hooks?: ComponentHooks;
}
export interface AccordionContentPassThroughAttributes {
[key: string]: any;
}
/**
* Defines valid properties in AccordionContent component.
*/
export interface AccordionContentProps {
/**
* Use to change the HTML tag of root element.
* @defaultValue DIV
*/
as?: string | undefined;
/**
* When enabled, it changes the default rendered element for the one passed as a child element.
* @defaultValue false
*/
asChild?: boolean | undefined;
/**
* It generates scoped CSS variables using design tokens for the component.
*/
dt?: DesignToken<any>;
/**
* Used to pass attributes to DOM elements inside the component.
* @type {AccordionContentPassThroughOptions}
*/
pt?: PassThrough<AccordionContentPassThroughOptions>;
/**
* Used to configure passthrough(pt) options of the component.
* @type {PassThroughOptions}
*/
ptOptions?: PassThroughOptions;
}
/**
* Defines current options in AccordionContent component.
*/
export interface AccordionContentContext {
/**
* Whether the item is active.
*/
active: boolean;
}
/**
* Defines valid slots in AccordionContent slots.
*/
export interface AccordionContentSlots {
/**
* Custom content template.
*/
default(): VNode[];
}
export interface AccordionContentEmits {}
/**
* **PrimeVue - AccordionContent**
*
* _AccordionContent is a helper component for Accordion component._
*
* [Live Demo](https://www.primevue.org/accordion/)
* --- ---
* ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
*
* @group Component
*
*/
declare class AccordionContent extends ClassComponent<AccordionContentProps, AccordionContentSlots, AccordionContentEmits> {}
declare module 'vue' {
export interface GlobalComponents {
AccordionContent: GlobalComponentConstructor<AccordionContent>;
}
}
export default AccordionContent;

View File

@ -0,0 +1,52 @@
<template>
<template v-if="!asChild">
<transition name="p-toggleable-content" v-bind="ptm('transition', ptParams)">
<component v-if="$pcAccordion.lazy ? $pcAccordionPanel.active : true" v-show="$pcAccordion.lazy ? true : $pcAccordionPanel.active" :is="as" :class="cx('root')" v-bind="attrs">
<div :class="cx('content')" v-bind="ptm('content', ptParams)">
<slot></slot>
</div>
</component>
</transition>
</template>
<slot v-else :class="cx('root')" :active="active" :a11yAttrs="a11yAttrs"></slot>
</template>
<script>
import { mergeProps } from 'vue';
import BaseAccordionContent from './BaseAccordionContent.vue';
export default {
name: 'AccordionContent',
extends: BaseAccordionContent,
inheritAttrs: false,
inject: ['$pcAccordion', '$pcAccordionPanel'],
computed: {
id() {
return `${this.$pcAccordion.id}_accordioncontent_${this.$pcAccordionPanel.value}`;
},
ariaLabelledby() {
return `${this.$pcAccordion.id}_accordionheader_${this.$pcAccordionPanel.value}`;
},
attrs() {
return mergeProps(this.a11yAttrs, this.ptmi('root', this.ptParams));
},
a11yAttrs() {
return {
id: this.id,
role: 'region',
'aria-labelledby': this.ariaLabelledby,
'data-pc-name': 'accordioncontent',
'data-p-active': this.$pcAccordionPanel.active
};
},
ptParams() {
return {
context: {
active: this.$pcAccordionPanel.active
}
};
}
}
};
</script>

View File

@ -0,0 +1,20 @@
<script>
import AccordionContentStyle from 'primevue/accordioncontent/style';
import BaseComponent from 'primevue/basecomponent';
export default {
name: 'BaseAccordionContent',
extends: BaseComponent,
props: {
as: {
type: String,
default: 'DIV'
},
asChild: {
type: Boolean,
default: false
}
},
style: AccordionContentStyle
};
</script>

View File

@ -0,0 +1,9 @@
{
"main": "./accordioncontent.cjs.js",
"module": "./accordioncontent.esm.js",
"unpkg": "./accordioncontent.min.js",
"types": "./AccordionContent.d.ts",
"browser": {
"./sfc": "./AccordionContent.vue"
}
}

View File

@ -0,0 +1,3 @@
import { BaseStyle } from '../../base/style/BaseStyle';
export interface AccordionContentStyle extends BaseStyle {}

View File

@ -0,0 +1,11 @@
import BaseStyle from 'primevue/base/style';
const classes = {
root: 'p-accordioncontent',
content: 'p-accordioncontent-content'
};
export default BaseStyle.extend({
name: 'accordioncontent',
classes
});

View File

@ -0,0 +1,6 @@
{
"main": "./accordioncontentstyle.cjs.js",
"module": "./accordioncontentstyle.esm.js",
"unpkg": "./accordioncontentstyle.min.js",
"types": "./AccordionContentStyle.d.ts"
}

View File

@ -0,0 +1,147 @@
/**
*
* AccordionHeader is a helper component for Accordion component.
*
* [Live Demo](https://www.primevue.org/accordion/)
*
* @module accordionheader
*
*/
import { VNode } from 'vue';
import { ComponentHooks } from '../basecomponent';
import { PassThroughOptions } from '../passthrough';
import { ClassComponent, DesignToken, GlobalComponentConstructor, PassThrough } from '../ts-helpers';
export declare type AccordionHeaderPassThroughOptionType = AccordionHeaderPassThroughAttributes | ((options: AccordionHeaderPassThroughMethodOptions) => AccordionHeaderPassThroughAttributes | string) | string | null | undefined;
/**
* Custom passthrough(pt) option method.
*/
export interface AccordionHeaderPassThroughMethodOptions {
/**
* Defines instance.
*/
instance: any;
/**
* Defines valid properties.
*/
props: AccordionHeaderProps;
/**
* Defines current options.
*/
context: AccordionHeaderContext;
/**
* Defines valid attributes.
*/
attrs: any;
/**
* Defines parent options.
*/
parent: any;
/**
* Defines passthrough(pt) options in global config.
*/
global: object | undefined;
}
/**
* Custom passthrough(pt) options.
* @see {@link AccordionHeaderProps.pt}
*/
export interface AccordionHeaderPassThroughOptions {
/**
* Used to pass attributes to the root's DOM element.
*/
root?: AccordionHeaderPassThroughOptionType;
/**
* Used to pass attributes to the root's DOM element.
*/
toggleicon?: AccordionHeaderPassThroughOptionType;
/**
* Used to manage all lifecycle hooks.
* @see {@link BaseComponent.ComponentHooks}
*/
hooks?: ComponentHooks;
}
export interface AccordionHeaderPassThroughAttributes {
[key: string]: any;
}
/**
* Defines valid properties in AccordionHeader component.
*/
export interface AccordionHeaderProps {
/**
* Use to change the HTML tag of root element.
* @defaultValue BUTTON
*/
as?: string | undefined;
/**
* When enabled, it changes the default rendered element for the one passed as a child element.
* @defaultValue false
*/
asChild?: boolean | undefined;
/**
* It generates scoped CSS variables using design tokens for the component.
*/
dt?: DesignToken<any>;
/**
* Used to pass attributes to DOM elements inside the component.
* @type {AccordionHeaderPassThroughOptions}
*/
pt?: PassThrough<AccordionHeaderPassThroughOptions>;
/**
* Used to configure passthrough(pt) options of the component.
* @type {PassThroughOptions}
*/
ptOptions?: PassThroughOptions;
}
/**
* Defines current options in AccordionHeader component.
*/
export interface AccordionHeaderContext {
/**
* Whether the item is active.
*/
active: boolean;
}
/**
* Defines valid slots in AccordionHeader slots.
*/
export interface AccordionHeaderSlots {
/**
* Custom content template.
*/
default(): VNode[];
/**
* Custom toggleicon template.
*/
toggleicon(): VNode[];
}
export interface AccordionHeaderEmits {}
/**
* **PrimeVue - AccordionHeader**
*
* _AccordionHeader is a helper component for Accordion component._
*
* [Live Demo](https://www.primevue.org/accordion/)
* --- ---
* ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
*
* @group Component
*
*/
declare class AccordionHeader extends ClassComponent<AccordionHeaderProps, AccordionHeaderSlots, AccordionHeaderEmits> {}
declare module 'vue' {
export interface GlobalComponents {
AccordionHeader: GlobalComponentConstructor<AccordionHeader>;
}
}
export default AccordionHeader;

View File

@ -0,0 +1,171 @@
<template>
<component v-if="!asChild" :is="as" v-ripple :class="cx('root')" @click="onClick" v-bind="attrs">
<slot :active="$pcAccordionPanel.active"></slot>
<slot name="toggleicon" :active="$pcAccordionPanel.active" :class="cx('toggleicon')">
<component
v-if="$pcAccordionPanel.active"
:is="$pcAccordion.$slots.collapseicon ? $pcAccordion.$slots.collapseicon : $pcAccordion.collapseIcon ? 'span' : 'ChevronDownIcon'"
:class="[$pcAccordion.collapseIcon, cx('toggleicon')]"
aria-hidden="true"
v-bind="ptm('toggleicon', ptParams)"
></component>
<component
v-else
:is="$pcAccordion.$slots.expandicon ? $pcAccordion.$slots.expandicon : $pcAccordion.expandIcon ? 'span' : 'ChevronUpIcon'"
:class="[$pcAccordion.expandIcon, cx('toggleicon')]"
aria-hidden="true"
v-bind="ptm('toggleicon', ptParams)"
></component>
</slot>
</component>
<slot v-else :class="cx('root')" :active="$pcAccordionPanel.active" :a11yAttrs="a11yAttrs" :onClick="onClick"></slot>
</template>
<script>
import ChevronDownIcon from 'primevue/icons/chevrondown';
import ChevronUpIcon from 'primevue/icons/chevronup';
import Ripple from 'primevue/ripple';
import { DomHandler } from 'primevue/utils';
import { mergeProps } from 'vue';
import BaseAccordionHeader from './BaseAccordionHeader.vue';
export default {
name: 'AccordionHeader',
extends: BaseAccordionHeader,
inheritAttrs: false,
inject: ['$pcAccordion', '$pcAccordionPanel'],
methods: {
onFocus() {
this.$pcAccordion.selectOnFocus && this.changeActiveValue();
},
onClick() {
this.changeActiveValue();
},
onKeydown(event) {
switch (event.code) {
case 'ArrowDown':
this.onArrowDownKey(event);
break;
case 'ArrowUp':
this.onArrowUpKey(event);
break;
case 'Home':
this.onHomeKey(event);
break;
case 'End':
this.onEndKey(event);
break;
case 'Enter':
case 'NumpadEnter':
case 'Space':
this.onEnterKey(event);
break;
default:
break;
}
},
onArrowDownKey(event) {
const nextPanel = this.findNextPanel(this.findPanel(event.currentTarget));
nextPanel ? this.changeFocusedPanel(event, nextPanel) : this.onHomeKey(event);
event.preventDefault();
},
onArrowUpKey(event) {
const prevPanel = this.findPrevPanel(this.findPanel(event.currentTarget));
prevPanel ? this.changeFocusedPanel(event, prevPanel) : this.onEndKey(event);
event.preventDefault();
},
onHomeKey(event) {
const firstPanel = this.findFirstPanel();
this.changeFocusedPanel(event, firstPanel);
event.preventDefault();
},
onEndKey(event) {
const lastPanel = this.findLastPanel();
this.changeFocusedPanel(event, lastPanel);
event.preventDefault();
},
onEnterKey(event) {
this.changeActiveValue();
event.preventDefault();
},
findPanel(headerElement) {
return headerElement?.closest('[data-pc-name="accordionpanel"]');
},
findHeader(panelElement) {
return DomHandler.findSingle(panelElement, '[data-pc-name="accordionheader"]');
},
findNextPanel(panelElement, selfCheck = false) {
const element = selfCheck ? panelElement : panelElement.nextElementSibling;
return element ? (DomHandler.getAttribute(element, 'data-p-disabled') ? this.findNextPanel(element) : this.findHeader(element)) : null;
},
findPrevPanel(panelElement, selfCheck = false) {
const element = selfCheck ? panelElement : panelElement.previousElementSibling;
return element ? (DomHandler.getAttribute(element, 'data-p-disabled') ? this.findPrevPanel(element) : this.findHeader(element)) : null;
},
findFirstPanel() {
return this.findNextPanel(this.$pcAccordion.$el.firstElementChild, true);
},
findLastPanel() {
return this.findPrevPanel(this.$pcAccordion.$el.lastElementChild, true);
},
changeActiveValue() {
this.$pcAccordion.updateValue(this.$pcAccordionPanel.value);
},
changeFocusedPanel(event, element) {
DomHandler.focus(this.findHeader(element));
}
},
computed: {
id() {
return `${this.$pcAccordion.id}_accordionheader_${this.$pcAccordionPanel.value}`;
},
ariaControls() {
return `${this.$pcAccordion.id}_accordioncontent_${this.$pcAccordionPanel.value}`;
},
attrs() {
return mergeProps(this.asAttrs, this.a11yAttrs, this.ptmi('root', this.ptParams));
},
asAttrs() {
return this.as === 'BUTTON' ? { type: 'button', disabled: this.$pcAccordionPanel.disabled } : undefined;
},
a11yAttrs() {
return {
id: this.id,
tabindex: this.$pcAccordion.tabindex,
'aria-expanded': this.$pcAccordionPanel.active,
'aria-controls': this.ariaControls,
'data-pc-name': 'accordionheader',
'data-p-disabled': this.$pcAccordionPanel.disabled,
'data-p-active': this.$pcAccordionPanel.active,
onFocus: this.onFocus,
onKeydown: this.onKeydown
};
},
ptParams() {
return {
context: {
active: this.$pcAccordionPanel.active
}
};
}
},
components: {
ChevronUpIcon,
ChevronDownIcon
},
directives: {
ripple: Ripple
}
};
</script>

View File

@ -0,0 +1,25 @@
<script>
import AccordionHeaderStyle from 'primevue/accordionheader/style';
import BaseComponent from 'primevue/basecomponent';
export default {
name: 'BaseAccordionHeader',
extends: BaseComponent,
props: {
as: {
type: String,
default: 'BUTTON'
},
asChild: {
type: Boolean,
default: false
}
},
style: AccordionHeaderStyle,
provide() {
return {
$parentInstance: this
};
}
};
</script>

View File

@ -0,0 +1,9 @@
{
"main": "./accordionheader.cjs.js",
"module": "./accordionheader.esm.js",
"unpkg": "./accordionheader.min.js",
"types": "./AccordionHeader.d.ts",
"browser": {
"./sfc": "./AccordionHeader.vue"
}
}

View File

@ -0,0 +1,3 @@
import { BaseStyle } from '../../base/style/BaseStyle';
export interface AccordionHeaderStyle extends BaseStyle {}

View File

@ -0,0 +1,11 @@
import BaseStyle from 'primevue/base/style';
const classes = {
root: 'p-accordionheader',
toggleicon: 'p-accordionheader-toggle-icon'
};
export default BaseStyle.extend({
name: 'accordionheader',
classes
});

View File

@ -0,0 +1,6 @@
{
"main": "./accordionheaderstyle.cjs.js",
"module": "./accordionheaderstyle.esm.js",
"unpkg": "./accordionheaderstyle.min.js",
"types": "./AccordionHeaderStyle.d.ts"
}

View File

@ -0,0 +1,145 @@
/**
*
* AccordionPanel is a helper component for Accordion component.
*
* [Live Demo](https://www.primevue.org/accordion/)
*
* @module accordionpanel
*
*/
import { VNode } from 'vue';
import { ComponentHooks } from '../basecomponent';
import { PassThroughOptions } from '../passthrough';
import { ClassComponent, DesignToken, GlobalComponentConstructor, PassThrough } from '../ts-helpers';
export declare type AccordionPanelPassThroughOptionType = AccordionPanelPassThroughAttributes | ((options: AccordionPanelPassThroughMethodOptions) => AccordionPanelPassThroughAttributes | string) | string | null | undefined;
/**
* Custom passthrough(pt) option method.
*/
export interface AccordionPanelPassThroughMethodOptions {
/**
* Defines instance.
*/
instance: any;
/**
* Defines valid properties.
*/
props: AccordionPanelProps;
/**
* Defines current options.
*/
context: AccordionPanelContext;
/**
* Defines valid attributes.
*/
attrs: any;
/**
* Defines parent options.
*/
parent: any;
/**
* Defines passthrough(pt) options in global config.
*/
global: object | undefined;
}
/**
* Custom passthrough(pt) options.
* @see {@link AccordionPanelProps.pt}
*/
export interface AccordionPanelPassThroughOptions {
/**
* Used to pass attributes to the root's DOM element.
*/
root?: AccordionPanelPassThroughOptionType;
/**
* Used to manage all lifecycle hooks.
* @see {@link BaseComponent.ComponentHooks}
*/
hooks?: ComponentHooks;
}
export interface AccordionPanelPassThroughAttributes {
[key: string]: any;
}
/**
* Defines valid properties in AccordionPanel component.
*/
export interface AccordionPanelProps {
/**
* Unique value of item.
*/
value: string;
/**
* Whether the item is disabled.
* @defaultValue false
*/
disabled?: boolean | undefined;
/**
* Use to change the HTML tag of root element.
* @defaultValue DIV
*/
as?: string | undefined;
/**
* When enabled, it changes the default rendered element for the one passed as a child element.
* @defaultValue false
*/
asChild?: boolean | undefined;
/**
* It generates scoped CSS variables using design tokens for the component.
*/
dt?: DesignToken<any>;
/**
* Used to pass attributes to DOM elements inside the component.
* @type {AccordionPanelPassThroughOptions}
*/
pt?: PassThrough<AccordionPanelPassThroughOptions>;
/**
* Used to configure passthrough(pt) options of the component.
* @type {PassThroughOptions}
*/
ptOptions?: PassThroughOptions;
}
/**
* Defines current options in AccordionPanel component.
*/
export interface AccordionPanelContext {
[key: string]: any;
}
/**
* Defines valid slots in AccordionPanel slots.
*/
export interface AccordionPanelSlots {
/**
* Custom content template.
*/
default(): VNode[];
}
export interface AccordionPanelEmits {}
/**
* **PrimeVue - AccordionPanel**
*
* _AccordionPanel is a helper component for Accordion component._
*
* [Live Demo](https://www.primevue.org/accordion/)
* --- ---
* ![PrimeVue](https://primefaces.org/cdn/primevue/images/logo-100.png)
*
* @group Component
*
*/
declare class AccordionPanel extends ClassComponent<AccordionPanelProps, AccordionPanelSlots, AccordionPanelEmits> {}
declare module 'vue' {
export interface GlobalComponents {
AccordionPanel: GlobalComponentConstructor<AccordionPanel>;
}
}
export default AccordionPanel;

View File

@ -0,0 +1,40 @@
<template>
<component v-if="!asChild" :is="as" v-ripple :class="cx('root')" v-bind="attrs">
<slot></slot>
</component>
<slot v-else :class="cx('root')" :active="active" :a11yAttrs="a11yAttrs"></slot>
</template>
<script>
import { mergeProps } from 'vue';
import BaseAccordionPanel from './BaseAccordionPanel.vue';
export default {
name: 'AccordionPanel',
extends: BaseAccordionPanel,
inheritAttrs: false,
inject: ['$pcAccordion'],
computed: {
active() {
return this.$pcAccordion.isItemActive(this.value);
},
attrs() {
return mergeProps(this.a11yAttrs, this.ptmi('root', this.ptParams));
},
a11yAttrs() {
return {
'data-pc-name': 'accordionpanel',
'data-p-disabled': this.disabled,
'data-p-active': this.active
};
},
ptParams() {
return {
context: {
active: this.active
}
};
}
}
};
</script>

View File

@ -0,0 +1,34 @@
<script>
import AccordionPanelStyle from 'primevue/accordionpanel/style';
import BaseComponent from 'primevue/basecomponent';
export default {
name: 'BaseAccordionPanel',
extends: BaseComponent,
props: {
value: {
type: String,
default: undefined
},
disabled: {
type: Boolean,
default: false
},
as: {
type: String,
default: 'DIV'
},
asChild: {
type: Boolean,
default: false
}
},
style: AccordionPanelStyle,
provide() {
return {
$pcAccordionPanel: this,
$parentInstance: this
};
}
};
</script>

View File

@ -0,0 +1,9 @@
{
"main": "./accordionpanel.cjs.js",
"module": "./accordionpanel.esm.js",
"unpkg": "./accordionpanel.min.js",
"types": "./AccordionPanel.d.ts",
"browser": {
"./sfc": "./AccordionPanel.vue"
}
}

View File

@ -0,0 +1,3 @@
import { BaseStyle } from '../../base/style/BaseStyle';
export interface AccordionPanelStyle extends BaseStyle {}

View File

@ -0,0 +1,16 @@
import BaseStyle from 'primevue/base/style';
const classes = {
root: ({ instance, props }) => [
'p-accordionpanel',
{
'p-active': instance.active,
'p-disabled': props.disabled
}
]
};
export default BaseStyle.extend({
name: 'accordionpanel',
classes
});

View File

@ -0,0 +1,6 @@
{
"main": "./accordionpanelstyle.cjs.js",
"module": "./accordionpanelstyle.esm.js",
"unpkg": "./accordionpanelstyle.min.js",
"types": "./AccordionPanelStyle.d.ts"
}

View File

@ -1,4 +1,5 @@
/**
* @deprecated since v4. Use the new structure of Accordion instead.
*
* AccordionTab is a helper component for Accordion.
*
@ -218,7 +219,9 @@ export interface AccordionTabSlots {
export interface AccordionTabEmits {}
/**
* **PrimeVue - Accordion**
* @deprecated since v4. Use the new structure of Accordion instead.
*
* **PrimeVue - AccordionTab**
*
* _AccordionTab is a helper component for Accordion.._
*

View File

@ -8,6 +8,9 @@ import BaseAccordionTab from './BaseAccordionTab.vue';
export default {
name: 'AccordionTab',
extends: BaseAccordionTab,
inheritAttrs: false
inheritAttrs: false,
mounted() {
console.warn('Deprecated since v4. Use the new structure of Accordion instead.');
}
};
</script>

View File

@ -1,13 +1,21 @@
export default {
css: ({ dt }) => `
.p-accordion-panel-header-content {
.p-accordionpanel {
display: flex;
flex-direction: column;
border-bottom: 1px solid ${dt('accordion.content.border.color')};
}
.p-accordionpanel:last-child {
border-bottom: 0 none;
}
.p-accordionheader {
all: unset;
cursor: pointer;
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
user-select: none;
text-decoration: none;
justify-content: space-between;
padding: 1.125rem 1.125rem 1.125rem 1.125rem;
color: ${dt('accordion.header.color')};
background: ${dt('accordion.header.background')};
@ -17,43 +25,23 @@ export default {
outline-color: transparent;
}
.p-accordion-panel-header-text {
line-height: 1;
}
.p-accordion-panel-header:not(.p-disabled) .p-accordion-panel-header-content:focus-visible {
.p-accordionpanel:not(.p-disabled) .p-accordionheader:focus-visible {
outline: ${dt('focus.ring.width')} ${dt('focus.ring.style')} ${dt('focus.ring.color')};
outline-offset: -2px;
}
.p-accordion-panel-header:not(.p-accordion-panel-header-active):not(.p-disabled):hover .p-accordion-panel-header-content {
.p-accordionpanel:not(.p-active):not(.p-disabled):hover .p-accordionheader {
color: ${dt('accordion.header.hover.color')}
}
.p-accordion-panel-header:not(.p-disabled).p-accordion-panel-header-active .p-accordion-panel-header-content {
.p-accordionpanel:not(.p-disabled).p-active .p-accordionheader {
color: ${dt('accordion.header.active.color')}
}
.p-accordion-panel-toggle-icon {
transform: rotate(90deg);
}
.p-accordion-panel-header-active .p-accordion-panel-toggle-icon {
transform: rotate(-180deg);
}
.p-accordion-panel-content {
.p-accordioncontent-content {
padding: 0 1.125rem 1.125rem 1.125rem;
background: color: ${dt('accordion.content.background')}
color: ${dt('accordion.content.color')}
}
.p-accordion-panel {
border-bottom: 1px solid ${dt('accordion.content.border.color')};
}
.p-accordion-panel:last-child {
border-bottom: 0 none
}
`
};

View File

@ -38,7 +38,30 @@ const button = ['Button', 'ButtonGroup', 'SpeedDial', 'SplitButton'];
const data = ['Column', 'Row', 'ColumnGroup', 'DataTable', 'DataView', 'OrderList', 'OrganizationChart', 'Paginator', 'PickList', 'Tree', 'TreeTable', 'Timeline', 'VirtualScroller'];
const panel = ['Accordion', 'AccordionTab', 'Card', 'DeferredContent', 'Divider', 'Fieldset', 'Panel', 'ScrollPanel', 'Splitter', 'SplitterPanel', 'Stepper', 'StepperPanel', 'TabView', 'Tabs', 'TabList', 'Tab', 'TabPanels', 'TabPanel', 'Toolbar'];
const panel = [
'Accordion',
'AccordionPanel',
'AccordionHeader',
'AccordionContent',
'AccordionTab',
'Card',
'DeferredContent',
'Divider',
'Fieldset',
'Panel',
'ScrollPanel',
'Splitter',
'SplitterPanel',
'Stepper',
'StepperPanel',
'TabView',
'Tabs',
'TabList',
'Tab',
'TabPanels',
'TabPanel',
'Toolbar'
];
const overlay = [
{ name: 'ConfirmDialog', use: { as: 'ConfirmationService' } },

View File

@ -70,6 +70,9 @@ const CORE_STYLE_DEPENDENCIES = {
'primevue/base/style': 'primevue.base.style',
'primevue/basecomponent/style': 'primevue.basecomponent.style',
'primevue/accordion/style': 'primevue.accordion.style',
'primevue/accordionpanel/style': 'primevue.accordionpanel.style',
'primevue/accordionheader/style': 'primevue.accordionheader.style',
'primevue/accordioncontent/style': 'primevue.accordioncontent.style',
'primevue/accordiontab/style': 'primevue.accordiontab.style',
'primevue/animateonscroll/style': 'primevue.animateonscroll.style',
'primevue/autocomplete/style': 'primevue.autocomplete.style',
@ -282,6 +285,9 @@ const CORE_DEPENDENCIES = {
'primevue/drawer': 'primevue.drawer',
'primevue/datepicker': 'primevue.datepicker',
'primevue/select': 'primevue.select',
'primevue/accordionpanel': 'primevue.accordionpanel',
'primevue/accordionheader': 'primevue.accordionheader',
'primevue/accordioncontent': 'primevue.accordioncontent',
...CORE_PASSTHROUGH_DEPENDENCIES,
...CORE_THEME_DEPENDENCIES
};