feat(accordion): add types for accordion

This commit is contained in:
Joren Broekema 2020-09-24 18:34:39 +02:00 committed by Thomas Allmer
parent 56cc174c0b
commit 175e6bea6f
4 changed files with 206 additions and 105 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/accordion': minor
---
Added types for the accordion package.

View file

@ -1,53 +1,118 @@
import { LitElement, css, html } from '@lion/core';
/**
* @typedef {Object} StoreEntry
* @property {string} uid Unique ID for the entry
* @property {number} index index of the node
* @property {HTMLElement} invoker invoker node
* @property {HTMLElement} content content node
* @property {EventHandlerNonNull} clickHandler executed on click event
* @property {EventHandlerNonNull} keydownHandler executed on keydown event
*/
const uuid = () => Math.random().toString(36).substr(2, 10);
/**
* @param {Object} opts
* @param {HTMLElement} opts.element
* @param {string} opts.uid
* @param {number} opts.index
*/
const setupContent = ({ element, uid, index }) => {
element.style.setProperty('order', index + 1);
element.style.setProperty('order', `${index + 1}`);
element.setAttribute('id', `content-${uid}`);
element.setAttribute('aria-labelledby', `invoker-${uid}`);
};
/**
* @param {Object} opts
* @param {HTMLElement} opts.element
* @param {string} opts.uid
* @param {number} opts.index
* @param {EventHandlerNonNull} opts.clickHandler
* @param {EventHandlerNonNull} opts.keydownHandler
*/
const setupInvoker = ({ element, uid, index, clickHandler, keydownHandler }) => {
element.style.setProperty('order', index + 1);
element.firstElementChild.setAttribute('id', `invoker-${uid}`);
element.firstElementChild.setAttribute('aria-controls', `content-${uid}`);
element.firstElementChild.addEventListener('click', clickHandler);
element.firstElementChild.addEventListener('keydown', keydownHandler);
element.style.setProperty('order', `${index + 1}`);
const firstChild = element.firstElementChild;
if (firstChild) {
firstChild.setAttribute('id', `invoker-${uid}`);
firstChild.setAttribute('aria-controls', `content-${uid}`);
firstChild.addEventListener('click', clickHandler);
firstChild.addEventListener('keydown', keydownHandler);
}
};
/**
* @param {HTMLElement} element
* @param {EventHandlerNonNull} clickHandler
* @param {EventHandlerNonNull} keydownHandler
*/
const cleanInvoker = (element, clickHandler, keydownHandler) => {
element.firstElementChild.removeAttribute('id');
element.firstElementChild.removeAttribute('aria-controls');
element.firstElementChild.removeEventListener('click', clickHandler);
element.firstElementChild.removeEventListener('keydown', keydownHandler);
const firstChild = element.firstElementChild;
if (firstChild) {
firstChild.removeAttribute('id');
firstChild.removeAttribute('aria-controls');
firstChild.removeEventListener('click', clickHandler);
firstChild.removeEventListener('keydown', keydownHandler);
}
};
/**
* @param {HTMLElement} element
*/
const focusInvoker = element => {
element.firstElementChild.focus();
element.firstElementChild.setAttribute('focused', true);
const firstChild = /** @type {HTMLElement|null} */ (element.firstElementChild);
if (firstChild) {
firstChild.focus();
firstChild.setAttribute('focused', `${true}`);
}
};
/**
* @param {HTMLElement} element
*/
const unfocusInvoker = element => {
element.firstElementChild.removeAttribute('focused');
const firstChild = element.firstElementChild;
if (firstChild) {
firstChild.removeAttribute('focused');
}
};
/**
* @param {HTMLElement} element
*/
const expandInvoker = element => {
element.setAttribute('expanded', true);
element.firstElementChild.setAttribute('expanded', true);
element.firstElementChild.setAttribute('aria-expanded', true);
element.setAttribute('expanded', `${true}`);
const firstChild = element.firstElementChild;
if (firstChild) {
firstChild.setAttribute('expanded', `${true}`);
firstChild.setAttribute('aria-expanded', `${true}`);
}
};
/**
* @param {HTMLElement} element
*/
const collapseInvoker = element => {
element.removeAttribute('expanded');
element.firstElementChild.removeAttribute('expanded');
element.firstElementChild.setAttribute('aria-expanded', false);
const firstChild = element.firstElementChild;
if (firstChild) {
firstChild.removeAttribute('expanded');
firstChild.setAttribute('aria-expanded', `${false}`);
}
};
/**
* @param {HTMLElement} element
*/
const expandContent = element => {
element.setAttribute('expanded', true);
element.setAttribute('expanded', `${true}`);
};
/**
* @param {HTMLElement} element
*/
const collapseContent = element => {
element.removeAttribute('expanded');
};
@ -117,31 +182,44 @@ export class LionAccordion extends LitElement {
constructor() {
super();
this.focusedIndex = null;
this.expanded = [];
this.styles = {};
/** @type {StoreEntry[]} */
this.__store = [];
/** @type {number} */
this.__focusedIndex = -1;
/** @type {number[]} */
this.__expanded = [];
}
firstUpdated() {
super.firstUpdated();
/** @param {import('lit-element').PropertyValues } changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.__setupSlots();
}
__setupSlots() {
const invokerSlot = this.shadowRoot.querySelector('slot[name=invoker]');
const invokerSlot = this.shadowRoot?.querySelector('slot[name=invoker]');
const handleSlotChange = () => {
this.__cleanStore();
this.__setupStore();
this.__updateFocused();
this.__updateExpanded();
};
invokerSlot.addEventListener('slotchange', handleSlotChange);
if (invokerSlot) {
invokerSlot.addEventListener('slotchange', handleSlotChange);
}
}
__setupStore() {
this.__store = [];
const invokers = this.querySelectorAll('[slot="invoker"]');
const contents = this.querySelectorAll('[slot="content"]');
const invokers = /** @type {HTMLElement[]} */ (Array.from(
this.querySelectorAll('[slot="invoker"]'),
));
const contents = /** @type {HTMLElement[]} */ (Array.from(
this.querySelectorAll('[slot="content"]'),
));
if (invokers.length !== contents.length) {
// eslint-disable-next-line no-console
console.warn(
@ -152,6 +230,7 @@ export class LionAccordion extends LitElement {
invokers.forEach((invoker, index) => {
const uid = uuid();
const content = contents[index];
/** @type {StoreEntry} */
const entry = {
uid,
index,
@ -176,8 +255,13 @@ export class LionAccordion extends LitElement {
this.__store.forEach(entry => {
cleanInvoker(entry.invoker, entry.clickHandler, entry.keydownHandler);
});
this.__store = [];
}
/**
*
* @param {number} index
*/
__createInvokerClickHandler(index) {
return () => {
this.focusedIndex = index;
@ -185,28 +269,32 @@ export class LionAccordion extends LitElement {
};
}
/**
* @param {Event} e
*/
__handleInvokerKeydown(e) {
switch (e.key) {
const _e = /** @type {KeyboardEvent} */ (e);
switch (_e.key) {
case 'ArrowDown':
case 'ArrowRight':
e.preventDefault();
_e.preventDefault();
if (this.focusedIndex + 2 <= this._pairCount) {
this.focusedIndex += 1;
}
break;
case 'ArrowUp':
case 'ArrowLeft':
e.preventDefault();
_e.preventDefault();
if (this.focusedIndex >= 1) {
this.focusedIndex -= 1;
}
break;
case 'Home':
e.preventDefault();
_e.preventDefault();
this.focusedIndex = 0;
break;
case 'End':
e.preventDefault();
_e.preventDefault();
this.focusedIndex = this._pairCount - 1;
break;
/* no default */
@ -245,9 +333,9 @@ export class LionAccordion extends LitElement {
if (!(this.__store && this.__store[this.focusedIndex])) {
return;
}
const previousInvoker = Array.from(this.children).find(
child => child.slot === 'invoker' && child.firstElementChild.hasAttribute('focused'),
);
const previousInvoker = /** @type {HTMLElement | null} */ (Array.from(this.children).find(
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
));
if (previousInvoker) {
unfocusInvoker(previousInvoker);
}
@ -274,6 +362,9 @@ export class LionAccordion extends LitElement {
});
}
/**
* @param {number} value
*/
__toggleExpanded(value) {
const { expanded } = this;
const index = expanded.indexOf(value);

View file

@ -2,6 +2,9 @@ import { expect, fixture, html } from '@open-wc/testing';
import sinon from 'sinon';
import '../lion-accordion.js';
/**
* @typedef {import('../src/LionAccordion.js').LionAccordion} LionAccordion
*/
const basicAccordion = html`
<lion-accordion>
@ -17,36 +20,36 @@ const basicAccordion = html`
describe('<lion-accordion>', () => {
describe('Accordion', () => {
it('sets expanded to [] by default', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
expect(el.expanded).to.deep.equal([]);
});
it('can programmatically set expanded', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion .expanded=${[1]}>
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
<h2 slot="invoker"><button>invoker 2</button></h2>
<div slot="content">content 2</div>
</lion-accordion>
`);
`));
expect(el.expanded).to.deep.equal([1]);
expect(
Array.from(el.children).find(
child => child.slot === 'invoker' && child.hasAttribute('expanded'),
).textContent,
)?.textContent,
).to.equal('invoker 2');
el.expanded = [0];
expect(
Array.from(el.children).find(
child => child.slot === 'invoker' && child.hasAttribute('expanded'),
).textContent,
)?.textContent,
).to.equal('invoker 1');
});
it('has [expanded] on current expanded invoker which serves as styling hook', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
el.expanded = [0];
expect(invokers[0]).to.have.attribute('expanded');
@ -58,7 +61,7 @@ describe('<lion-accordion>', () => {
});
it('has [expanded] on current expanded invoker first child which serves as styling hook', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
el.expanded = [0];
expect(invokers[0].firstElementChild).to.have.attribute('expanded');
@ -70,7 +73,7 @@ describe('<lion-accordion>', () => {
});
it('sends event "expanded-changed" for every expanded state change', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const spy = sinon.spy();
el.addEventListener('expanded-changed', spy);
el.expanded = [1];
@ -95,44 +98,44 @@ describe('<lion-accordion>', () => {
describe('Accordion navigation', () => {
it('sets focusedIndex to null by default', async () => {
const el = await fixture(basicAccordion);
expect(el.focusedIndex).to.be.null;
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
expect(el.focusedIndex).to.equal(-1);
});
it('can programmatically set focusedIndex', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion .focusedIndex=${1}>
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
<h2 slot="invoker"><button>invoker 2</button></h2>
<div slot="content">content 2</div>
</lion-accordion>
`);
`));
expect(el.focusedIndex).to.equal(1);
expect(
Array.from(el.children).find(
child => child.slot === 'invoker' && child.firstElementChild.hasAttribute('focused'),
).textContent,
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
)?.textContent,
).to.equal('invoker 2');
el.focusedIndex = 0;
expect(
Array.from(el.children).find(
child => child.slot === 'invoker' && child.firstElementChild.hasAttribute('focused'),
).textContent,
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
)?.textContent,
).to.equal('invoker 1');
});
it('has [focused] on current focused invoker first child which serves as styling hook', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
el.focusedIndex = [0];
el.focusedIndex = 0;
expect(invokers[0]).to.not.have.attribute('focused');
expect(invokers[1]).to.not.have.attribute('focused');
expect(invokers[0].firstElementChild).to.have.attribute('focused');
expect(invokers[1].firstElementChild).to.not.have.attribute('focused');
el.focusedIndex = [1];
el.focusedIndex = 1;
expect(invokers[0]).to.not.have.attribute('focused');
expect(invokers[1]).to.not.have.attribute('focused');
expect(invokers[0].firstElementChild).to.not.have.attribute('focused');
@ -140,7 +143,7 @@ describe('<lion-accordion>', () => {
});
it('sends event "focused-changed" for every focused state change', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const spy = sinon.spy();
el.addEventListener('focused-changed', spy);
el.focusedIndex = 1;
@ -150,7 +153,7 @@ describe('<lion-accordion>', () => {
describe('Accordion Contents (slot=content)', () => {
it('are visible when corresponding invoker is expanded', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const contents = el.querySelectorAll('[slot=content]');
el.expanded = [0];
expect(contents[0]).to.be.visible;
@ -174,44 +177,44 @@ describe('<lion-accordion>', () => {
*/
describe('User interaction', () => {
it('opens a invoker on click', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[1].firstElementChild.dispatchEvent(new Event('click'));
invokers[1].firstElementChild?.dispatchEvent(new Event('click'));
expect(el.expanded).to.deep.equal([1]);
});
it('selects a invoker on click', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[1].firstElementChild.dispatchEvent(new Event('click'));
invokers[1].firstElementChild?.dispatchEvent(new Event('click'));
expect(el.focusedIndex).to.equal(1);
});
it.skip('opens/close invoker on [enter] and [space]', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[0].firstElementChild.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
invokers[0].firstElementChild?.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
expect(el.expanded).to.deep.equal([0]);
invokers[0].firstElementChild.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
invokers[0].firstElementChild?.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
expect(el.expanded).to.deep.equal([]);
});
it('selects next invoker on [arrow-right] and [arrow-down]', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
el.focusedIndex = 0;
invokers[0].firstElementChild.dispatchEvent(
invokers[0].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
);
expect(el.focusedIndex).to.equal(1);
invokers[0].firstElementChild.dispatchEvent(
invokers[0].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowDown' }),
);
expect(el.focusedIndex).to.equal(2);
});
it('selects previous invoker on [arrow-left] and [arrow-up]', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion .focusedIndex=${1}>
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
@ -220,40 +223,42 @@ describe('<lion-accordion>', () => {
<h2 slot="invoker"><button>invoker 3</button></h2>
<div slot="content">content 3</div>
</lion-accordion>
`);
`));
const invokers = el.querySelectorAll('[slot=invoker]');
el.focusedIndex = 2;
invokers[2].firstElementChild.dispatchEvent(
invokers[2].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
);
expect(el.focusedIndex).to.equal(1);
invokers[1].firstElementChild.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
invokers[1].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp' }),
);
expect(el.focusedIndex).to.equal(0);
});
it('selects first invoker on [home]', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion .focusedIndex=${1}>
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
<h2 slot="invoker"><button>invoker 2</button></h2>
<div slot="content">content 2</div>
</lion-accordion>
`);
`));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[1].firstElementChild.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
invokers[1].firstElementChild?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
expect(el.focusedIndex).to.equal(0);
});
it('selects last invoker on [end]', async () => {
const el = await fixture(basicAccordion);
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[0].firstElementChild.dispatchEvent(new KeyboardEvent('keydown', { key: 'End' }));
invokers[0].firstElementChild?.dispatchEvent(new KeyboardEvent('keydown', { key: 'End' }));
expect(el.focusedIndex).to.equal(2);
});
it('stays on last invoker on [arrow-right]', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion focusedIndex="2">
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
@ -262,16 +267,16 @@ describe('<lion-accordion>', () => {
<h2 slot="invoker"><button>invoker 3</button></h2>
<div slot="content">content 3</div>
</lion-accordion>
`);
`));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[2].firstElementChild.dispatchEvent(
invokers[2].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
);
expect(el.focusedIndex).to.equal(2);
});
it('stays on first invoker on [arrow-left]', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion>
<h2 slot="invoker"><button>invoker 1</button></h2>
<div slot="content">content 1</div>
@ -280,21 +285,19 @@ describe('<lion-accordion>', () => {
<h2 slot="invoker"><button>invoker 3</button></h2>
<div slot="content">content 3</div>
</lion-accordion>
`);
`));
const invokers = el.querySelectorAll('[slot=invoker]');
invokers[0].firstElementChild.dispatchEvent(
invokers[0].firstElementChild?.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
);
expect(el.focusedIndex).to.equal(null);
expect(el.focusedIndex).to.equal(-1);
});
});
describe('Content distribution', () => {
it('should work with append children', async () => {
const el = await fixture(basicAccordion);
const c = 2;
const n = el.children.length / 2;
for (let i = n + 1; i < n + c + 1; i += 1) {
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
for (let i = 4; i < 6; i += 1) {
const invoker = document.createElement('h2');
const button = document.createElement('button');
invoker.setAttribute('slot', 'invoker');
@ -306,25 +309,23 @@ describe('<lion-accordion>', () => {
el.append(invoker);
el.append(content);
}
el.expanded = [el.children.length / 2 - 1];
el.expanded = [4];
await el.updateComplete;
expect(
Array.from(el.children).find(
child => child.slot === 'invoker' && child.hasAttribute('expanded'),
).textContent,
)?.textContent,
).to.equal('invoker 5');
expect(
Array.from(el.children).find(
child => child.slot === 'content' && child.hasAttribute('expanded'),
).textContent,
)?.textContent,
).to.equal('content 5');
});
it('should add order style property to each invoker and content', async () => {
const el = await fixture(basicAccordion);
const c = 2;
const n = el.children.length / 2;
for (let i = n + 1; i < n + c + 1; i += 1) {
const el = /** @type {LionAccordion} */ (await fixture(basicAccordion));
for (let i = 4; i < 6; i += 1) {
const invoker = document.createElement('h2');
const button = document.createElement('button');
invoker.setAttribute('slot', 'invoker');
@ -336,10 +337,13 @@ describe('<lion-accordion>', () => {
el.append(invoker);
el.append(content);
}
el.expanded = [el.children.length / 2 - 1];
await el.updateComplete;
const invokers = el.querySelectorAll('[slot=invoker]');
const contents = el.querySelectorAll('[slot=content]');
const invokers = /** @type {HTMLElement[]} */ (Array.from(
el.querySelectorAll('[slot=invoker]'),
));
const contents = /** @type {HTMLElement[]} */ (Array.from(
el.querySelectorAll('[slot=content]'),
));
invokers.forEach((invoker, index) => {
const content = contents[index];
expect(invoker.style.getPropertyValue('order')).to.equal(`${index + 1}`);
@ -378,10 +382,10 @@ describe('<lion-accordion>', () => {
`);
const invokers = el.querySelectorAll('[slot=invoker]');
const contents = el.querySelectorAll('[slot=content]');
expect(invokers[0].firstElementChild.getAttribute('aria-controls')).to.equal(
expect(invokers[0].firstElementChild?.getAttribute('aria-controls')).to.equal(
contents[0].id,
);
expect(invokers[1].firstElementChild.getAttribute('aria-controls')).to.equal(
expect(invokers[1].firstElementChild?.getAttribute('aria-controls')).to.equal(
contents[1].id,
);
});
@ -394,20 +398,20 @@ describe('<lion-accordion>', () => {
</lion-accordion>
`);
expect(
Array.from(el.children).find(child => child.slot === 'invoker').firstElementChild,
Array.from(el.children).find(child => child.slot === 'invoker')?.firstElementChild,
).to.have.attribute('aria-expanded', 'false');
});
it('adds aria-expanded="true" to invoker when its content is expanded', async () => {
const el = await fixture(html`
const el = /** @type {LionAccordion} */ (await fixture(html`
<lion-accordion>
<h2 slot="invoker"><button>invoker</button></h2>
<div slot="content">content</div>
</lion-accordion>
`);
`));
el.expanded = [0];
expect(
Array.from(el.children).find(child => child.slot === 'invoker').firstElementChild,
Array.from(el.children).find(child => child.slot === 'invoker')?.firstElementChild,
).to.have.attribute('aria-expanded', 'true');
});
});
@ -424,8 +428,8 @@ describe('<lion-accordion>', () => {
`);
const contents = el.querySelectorAll('[slot=content]');
const invokers = el.querySelectorAll('[slot=invoker]');
expect(contents[0]).to.have.attribute('aria-labelledby', invokers[0].firstElementChild.id);
expect(contents[1]).to.have.attribute('aria-labelledby', invokers[1].firstElementChild.id);
expect(contents[0]).to.have.attribute('aria-labelledby', invokers[0].firstElementChild?.id);
expect(contents[1]).to.have.attribute('aria-labelledby', invokers[1].firstElementChild?.id);
});
});
});

View file

@ -15,6 +15,7 @@
"suppressImplicitAnyIndexErrors": true
},
"include": [
"packages/accordion/**/*.js",
"packages/core/**/*.js",
"packages/tabs/**/*.js",
"packages/singleton-manager/**/*.js",