-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: extract accordion and accordion-panel mixins
- Loading branch information
1 parent
f349a02
commit 7d4cabc
Showing
10 changed files
with
332 additions
and
267 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* @license | ||
* Copyright (c) 2019 - 2023 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import type { Constructor } from '@open-wc/dedupe-mixin'; | ||
import type { KeyboardDirectionMixinClass } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; | ||
|
||
/** | ||
* A mixin providing common accordion functionality. | ||
*/ | ||
export declare function AccordionMixin<T extends Constructor<HTMLElement>>( | ||
base: T, | ||
): Constructor<AccordionMixinClass> & Constructor<KeyboardDirectionMixinClass> & T; | ||
|
||
export declare class AccordionMixinClass { | ||
/** | ||
* The index of currently opened panel. First panel is opened by | ||
* default. Only one panel can be opened at the same time. | ||
* Setting null or undefined closes all the accordion panels. | ||
*/ | ||
opened: number | null; | ||
|
||
/** | ||
* The list of `<vaadin-accordion-panel>` child elements. | ||
* It is populated from the elements passed to the light DOM, | ||
* and updated dynamically when adding or removing panels. | ||
*/ | ||
readonly items: HTMLElement[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/** | ||
* @license | ||
* Copyright (c) 2019 - 2023 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { isElementFocused } from '@vaadin/a11y-base/src/focus-utils.js'; | ||
import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js'; | ||
import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js'; | ||
|
||
/** | ||
* A mixin providing common accordion functionality. | ||
* | ||
* @polymerMixin | ||
* @mixes KeyboardDirectionMixin | ||
*/ | ||
export const AccordionMixin = (superClass) => | ||
class AccordionMixinClass extends KeyboardDirectionMixin(superClass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* The index of currently opened panel. First panel is opened by | ||
* default. Only one panel can be opened at the same time. | ||
* Setting null or undefined closes all the accordion panels. | ||
* @type {number} | ||
*/ | ||
opened: { | ||
type: Number, | ||
value: 0, | ||
notify: true, | ||
reflectToAttribute: true, | ||
}, | ||
|
||
/** | ||
* The list of `<vaadin-accordion-panel>` child elements. | ||
* It is populated from the elements passed to the light DOM, | ||
* and updated dynamically when adding or removing panels. | ||
* @type {!Array<!AccordionPanel>} | ||
*/ | ||
items: { | ||
type: Array, | ||
readOnly: true, | ||
notify: true, | ||
}, | ||
}; | ||
} | ||
|
||
static get observers() { | ||
return ['_updateItems(items, opened)']; | ||
} | ||
|
||
constructor() { | ||
super(); | ||
this._boundUpdateOpened = this._updateOpened.bind(this); | ||
} | ||
|
||
/** | ||
* Override getter from `KeyboardDirectionMixin` | ||
* to check if the heading element has focus. | ||
* | ||
* @return {Element | null} | ||
* @protected | ||
* @override | ||
*/ | ||
get focused() { | ||
return (this._getItems() || []).find((item) => isElementFocused(item.focusElement)); | ||
} | ||
|
||
/** | ||
* @protected | ||
* @override | ||
*/ | ||
focus() { | ||
if (this._observer) { | ||
this._observer.flush(); | ||
} | ||
super.focus(); | ||
} | ||
|
||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
|
||
const slot = this.shadowRoot.querySelector('slot'); | ||
this._observer = new SlotObserver(slot, (info) => { | ||
this._setItems(this._filterItems(Array.from(this.children))); | ||
|
||
this._filterItems(info.addedNodes).forEach((el) => { | ||
el.addEventListener('opened-changed', this._boundUpdateOpened); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Override method inherited from `KeyboardDirectionMixin` | ||
* to use the stored list of accordion panels as items. | ||
* | ||
* @return {Element[]} | ||
* @protected | ||
* @override | ||
*/ | ||
_getItems() { | ||
return this.items; | ||
} | ||
|
||
/** | ||
* @param {!Array<!Element>} array | ||
* @return {!Array<!AccordionPanel>} | ||
* @protected | ||
*/ | ||
_filterItems(array) { | ||
return array.filter((el) => el instanceof customElements.get('vaadin-accordion-panel')); | ||
} | ||
|
||
/** @private */ | ||
_updateItems(items, opened) { | ||
if (items) { | ||
const itemToOpen = items[opened]; | ||
items.forEach((item) => { | ||
item.opened = item === itemToOpen; | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Override an event listener from `KeyboardMixin` | ||
* to only handle details toggle buttons events. | ||
* | ||
* @param {!KeyboardEvent} event | ||
* @protected | ||
* @override | ||
*/ | ||
_onKeyDown(event) { | ||
// Only check keyboard events on details toggle buttons | ||
if (!this.items.some((item) => item.focusElement === event.target)) { | ||
return; | ||
} | ||
|
||
super._onKeyDown(event); | ||
} | ||
|
||
/** @private */ | ||
_updateOpened(e) { | ||
const target = this._filterItems(e.composedPath())[0]; | ||
const idx = this.items.indexOf(target); | ||
if (e.detail.value) { | ||
if (target.disabled || idx === -1) { | ||
return; | ||
} | ||
|
||
this.opened = idx; | ||
} else if (!this.items.some((item) => item.opened)) { | ||
this.opened = null; | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* @license | ||
* Copyright (c) 2019 - 2023 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import type { Constructor } from '@open-wc/dedupe-mixin'; | ||
import type { DelegateFocusMixinClass } from '@vaadin/a11y-base/src/delegate-focus-mixin.js'; | ||
import type { DelegateStateMixinClass } from '@vaadin/component-base/src/delegate-state-mixin.js'; | ||
import type { CollapsibleMixinClass } from '@vaadin/details/src/collapsible-mixin.js'; | ||
|
||
/** | ||
* A mixin providing common accordion panel functionality. | ||
*/ | ||
export declare function AccordionPanelMixin<T extends Constructor<HTMLElement>>( | ||
base: T, | ||
): Constructor<AccordionPanelMixinClass> & | ||
Constructor<CollapsibleMixinClass> & | ||
Constructor<DelegateFocusMixinClass> & | ||
Constructor<DelegateStateMixinClass> & | ||
T; | ||
|
||
export declare class AccordionPanelMixinClass { | ||
/** | ||
* A text that is displayed in the heading, if no | ||
* element is assigned to the `summary` slot. | ||
*/ | ||
summary: string | null | undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** | ||
* @license | ||
* Copyright (c) 2019 - 2023 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { DelegateFocusMixin } from '@vaadin/a11y-base/src/delegate-focus-mixin.js'; | ||
import { DelegateStateMixin } from '@vaadin/component-base/src/delegate-state-mixin.js'; | ||
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; | ||
import { CollapsibleMixin } from '@vaadin/details/src/collapsible-mixin.js'; | ||
import { SummaryController } from '@vaadin/details/src/summary-controller.js'; | ||
|
||
/** | ||
* A mixin providing common accordion panel functionality. | ||
* | ||
* @polymerMixin | ||
* @mixes CollapsibleMixin | ||
* @mixes DelegateFocusMixin | ||
* @mixes DelegateStateMixin | ||
*/ | ||
export const AccordionPanelMixin = (superClass) => | ||
class AccordionPanelMixinClass extends CollapsibleMixin(DelegateFocusMixin(DelegateStateMixin(superClass))) { | ||
static get properties() { | ||
return { | ||
/** | ||
* A text that is displayed in the heading, if no | ||
* element is assigned to the `summary` slot. | ||
*/ | ||
summary: { | ||
type: String, | ||
observer: '_summaryChanged', | ||
}, | ||
}; | ||
} | ||
|
||
static get observers() { | ||
return ['__updateAriaAttributes(focusElement, _contentElements)']; | ||
} | ||
|
||
static get delegateAttrs() { | ||
return ['theme']; | ||
} | ||
|
||
static get delegateProps() { | ||
return ['disabled', 'opened']; | ||
} | ||
|
||
constructor() { | ||
super(); | ||
|
||
this._summaryController = new SummaryController(this, 'vaadin-accordion-heading'); | ||
this._summaryController.addEventListener('slot-content-changed', (event) => { | ||
const { node } = event.target; | ||
|
||
this._setFocusElement(node); | ||
this.stateTarget = node; | ||
|
||
this._tooltipController.setTarget(node); | ||
}); | ||
|
||
this._tooltipController = new TooltipController(this); | ||
this._tooltipController.setPosition('bottom-start'); | ||
} | ||
|
||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
|
||
this.addController(this._summaryController); | ||
this.addController(this._tooltipController); | ||
} | ||
|
||
/** | ||
* Override method inherited from `DisabledMixin` | ||
* to not set `aria-disabled` on the host element. | ||
* | ||
* @protected | ||
* @override | ||
*/ | ||
_setAriaDisabled() { | ||
// The `aria-disabled` is set on the details summary. | ||
} | ||
|
||
/** @private */ | ||
_summaryChanged(summary) { | ||
this._summaryController.setSummary(summary); | ||
} | ||
|
||
/** @private */ | ||
__updateAriaAttributes(focusElement, contentElements) { | ||
if (focusElement && contentElements) { | ||
const node = contentElements[0]; | ||
|
||
if (node) { | ||
node.setAttribute('role', 'region'); | ||
node.setAttribute('aria-labelledby', focusElement.id); | ||
} | ||
|
||
if (node && node.id) { | ||
focusElement.setAttribute('aria-controls', node.id); | ||
} else { | ||
focusElement.removeAttribute('aria-controls'); | ||
} | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.