Adding height animation to multi-panel

This commit is contained in:
Jake Archibald
2018-10-27 13:32:35 +01:00
parent 52f61dfccc
commit ac4f845d8e
6 changed files with 106 additions and 67 deletions

View File

@@ -1,4 +1,5 @@
import * as style from './styles.css';
import { transitionHeight } from '../../../../lib/util';
interface CloseAllOptions {
exceptFirst?: boolean;
@@ -6,15 +7,44 @@ interface CloseAllOptions {
const openOneOnlyAttr = 'open-one-only';
function getClosestHeading(el: Element) {
function getClosestHeading(el: Element): HTMLElement | undefined {
// Look for the child of multi-panel, but stop at interactive elements like links & buttons
const closestEl = el.closest('multi-panel > *, a, button');
if (closestEl && closestEl.classList.contains(style.panelHeading)) {
return closestEl;
return closestEl as HTMLElement;
}
return undefined;
}
async function close(content: HTMLElement) {
const from = content.getBoundingClientRect().height;
content.removeAttribute('expanded');
content.setAttribute('aria-expanded', 'false');
await transitionHeight(content, {
from,
to: 0,
duration: 300,
});
content.style.height = '';
}
async function open(content: HTMLElement) {
const from = content.getBoundingClientRect().height;
content.setAttribute('expanded', '');
content.setAttribute('aria-expanded', 'true');
await transitionHeight(content, {
from,
duration: 300,
});
content.style.height = '';
}
/**
* A multi-panel view that the user can add any number of 'panels'.
* 'a panel' consists of two elements. Even index element becomes heading,
@@ -47,7 +77,7 @@ export default class MultiPanel extends HTMLElement {
// Click event handler
private _onClick(event: MouseEvent) {
const el = event.target as Element;
const el = event.target as HTMLElement;
const heading = getClosestHeading(el);
if (!heading) return;
this._toggle(heading);
@@ -108,41 +138,36 @@ export default class MultiPanel extends HTMLElement {
}
}
private _toggle(heading: Element) {
private _toggle(heading: HTMLElement) {
if (!heading) return;
const content = heading.nextElementSibling;
const content = heading.nextElementSibling as HTMLElement;
// if there is no content, nothing to expand
if (!content) return;
// toggle expanded and aria-expanded attributes
if (content.hasAttribute('expanded')) {
content.removeAttribute('expanded');
content.setAttribute('aria-expanded', 'false');
close(content);
} else {
if (this.openOneOnly) this._closeAll();
content.setAttribute('expanded', '');
content.setAttribute('aria-expanded', 'true');
open(content);
}
}
private _closeAll(options: CloseAllOptions = {}): void {
const { exceptFirst = false } = options;
let els = [...this.children].filter(el => el.matches('[expanded]'));
let els = [...this.children].filter(el => el.matches('[expanded]')) as HTMLElement[];
if (exceptFirst) {
els = els.slice(1);
}
for (const el of els) {
el.removeAttribute('expanded');
el.setAttribute('aria-expanded', 'false');
}
for (const el of els) close(el);
}
// children of multi-panel should always be even number (heading/content pair)
private _childrenChange() {
let preserveTabIndex : boolean = false;
let preserveTabIndex = false;
let heading = this.firstElementChild;
while (heading) {