feat(ILN): add in keepOpen configuration and tests
ADD
src/components/interior-left-nav/interior-left-nav-keep-open.html
+<nav role="navigation" aria-label="Interior Left Navigation" data-interior-left-nav class="bx--interior-left-nav bx--interior-left-nav--collapseable" data-keep-open="true">
+ <ul role="menubar" class="left-nav-list" data-interior-left-nav-list aria-hidden="false">
+ <li role="menuitem" tabindex="0" class="left-nav-list__item" data-interior-left-nav-item>
+ <a class="left-nav-list__item-link">
+ Example Item 1
+ </a>
+ </li>
+ <li role="menuitem" tabindex="0" class="left-nav-list__item" data-interior-left-nav-item>
+ <a class="left-nav-list__item-link">
+ Example Item 2
+ </a>
+ </li>
+ <li role="menuitem" tabindex="0" class="left-nav-list__item left-nav-list__item--has-children" data-interior-left-nav-item
+ data-interior-left-nav-with-children>
+ <a class="left-nav-list__item-link">
+ Example Item 3
+ <div class="left-nav-list__item-icon">
+ <svg class="bx--interior-left-nav__icon" width="10" height="5" viewBox="0 0 10 5" fill-rule="evenodd">
+ <path d="M10 0L5 5 0 0z"></path>
+ </svg>
+ </div>
+ </a>
+ <ul role="menu" aria-hidden="true" class="left-nav-list left-nav-list--nested" data-interior-left-nav-nested-list>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-1A" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 1A</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-1B" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 1B</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-1C" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 1C</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-1D" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 1D</a>
+ </li>
+ </ul>
+ </li>
+ <li role="menuitem" tabindex="0" class="left-nav-list__item left-nav-list__item--has-children" data-interior-left-nav-item
+ data-interior-left-nav-with-children>
+ <a class="left-nav-list__item-link">
+ Example Item 4
+ <div class="left-nav-list__item-icon">
+ <svg class="bx--interior-left-nav__icon" width="10" height="5" viewBox="0 0 10 5" fill-rule="evenodd">
+ <path d="M10 0L5 5 0 0z"></path>
+ </svg>
+ </div>
+ </a>
+ <ul role="menu" aria-hidden="true" class="left-nav-list left-nav-list--nested" data-interior-left-nav-nested-list>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-2A" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 2A</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-2B" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 2B</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-2C" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 2C</a>
+ </li>
+ <li class="left-nav-list__item" data-interior-left-nav-nested-item role="menuitem" tabindex="-1">
+ <a href="#example-item-2D" class="left-nav-list__item-link" data-interior-left-nav-item-link tabindex="-1">Example Item 2D</a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+
+ <div class="bx--interior-left-nav-collapse" data-interior-left-nav-collapse>
+ <a class="bx--interior-left-nav-collapse__link" href="#">
+ <svg class="bx--interior-left-nav-collapse__arrow" width="8" height="12" viewBox="0 0 8 12" fill-rule="evenodd">
+ <path d="M7.5 10.6L2.8 6l4.7-4.6L6.1 0 0 6l6.1 6z"></path>
+ </svg>
+ </a>
+ </div>
+ </nav>

MODIFY
src/components/interior-left-nav/interior-left-nav.js
src/components/interior-left-nav/interior-left-nav.js
import mixin from '../../globals/js/misc/mixin';
import createComponent from '../../globals/js/mixins/create-component';
-import initComponentBySearch
- from '../../globals/js/mixins/init-component-by-search';
+import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
import eventMatches from '../../globals/js/misc/event-matches';
import toggleClass from '../../globals/js/misc/svg-toggle-class';
@@ -16,31 +15,37 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
constructor(element, options) {
super(element, options);
this.constructor.components.set(this.element, this);
+
+ this.keepOpen =
+ this.element.dataset.keepOpen === undefined
+ ? this.options.keepOpen
+ : Boolean(this.element.dataset.keepOpen);
+
this.hookListItemsEvents();
}
hookListItemsEvents = () => {
- this.element.addEventListener('click', (evt) => {
+ this.element.addEventListener('click', evt => {
const leftNavItem = eventMatches(
evt,
- this.options.selectorLeftNavListItem,
+ this.options.selectorLeftNavListItem
);
const collapseEl = eventMatches(
evt,
- this.options.selectorLeftNavCollapse,
+ this.options.selectorLeftNavCollapse
);
const collapsedBar = eventMatches(
evt,
- `.${this.options.classLeftNavCollapsed}`,
+ `.${this.options.classLeftNavCollapsed}`
);
if (leftNavItem) {
const childItem = eventMatches(
evt,
- this.options.selectorLeftNavNestedListItem,
+ this.options.selectorLeftNavNestedListItem
);
const hasChildren = leftNavItem.classList.contains(
- 'left-nav-list__item--has-children',
+ 'left-nav-list__item--has-children'
);
if (childItem) {
this.addActiveListItem(childItem);
@@ -57,14 +62,14 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
}
});
- this.element.addEventListener('keydown', (evt) => {
+ this.element.addEventListener('keydown', evt => {
const leftNavItemWithChildren = eventMatches(
evt,
- this.options.selectorLeftNavListItemHasChildren,
+ this.options.selectorLeftNavListItemHasChildren
);
const leftNavItem = eventMatches(
evt,
- this.options.selectorLeftNavListItem,
+ this.options.selectorLeftNavListItem
);
if (leftNavItemWithChildren && evt.which === 13) {
@@ -78,16 +83,16 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
addActiveListItem(item) {
[
...this.element.querySelectorAll(this.options.selectorLeftNavListItem),
- ].forEach((currentItem) => {
+ ].forEach(currentItem => {
if (!(item === currentItem)) {
currentItem.classList.remove(this.options.classActiveLeftNavListItem);
}
});
[
...this.element.querySelectorAll(
- this.options.selectorLeftNavNestedListItem,
+ this.options.selectorLeftNavNestedListItem
),
- ].forEach((currentItem) => {
+ ].forEach(currentItem => {
if (!(item === currentItem)) {
currentItem.classList.remove(this.options.classActiveLeftNavListItem);
}
@@ -103,39 +108,45 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
*/
handleNestedListClick(listItem, evt) {
const allNestedItems = [
- ...document.querySelectorAll(
- this.options.selectorLeftNavListItemHasChildren,
+ ...this.element.querySelectorAll(
+ this.options.selectorLeftNavListItemHasChildren
),
];
const isOpen = listItem.classList.contains(
- this.options.classExpandedLeftNavListItem,
+ this.options.classExpandedLeftNavListItem
);
- allNestedItems.forEach((currentItem) => {
+ const list = listItem.querySelector(this.options.selectorLeftNavNestedList);
+ const listItems = [
+ ...list.querySelectorAll(this.options.selectorLeftNavNestedListItem),
+ ];
+
+ if (!this.keepOpen) {
+ allNestedItems.forEach(currentItem => {
if (currentItem !== listItem) {
toggleClass(
currentItem,
this.options.classExpandedLeftNavListItem,
- false,
+ false
);
}
});
+ }
+
if (!('InteriorLeftNavItemLink' in evt.target.dataset)) {
toggleClass(listItem, this.options.classExpandedLeftNavListItem, !isOpen);
}
- const list = listItem.querySelector(this.options.selectorLeftNavNestedList);
- const listItems = [
- ...list.querySelectorAll(this.options.selectorLeftNavNestedListItem),
- ];
- listItems.forEach((item) => {
+
+ // a11y
+ listItems.forEach(item => {
if (isOpen) {
// eslint-disable-next-line no-param-reassign
item.querySelector(
- this.options.selectorLeftNavListItemLink,
+ this.options.selectorLeftNavListItemLink
).tabIndex = -1;
} else {
// eslint-disable-next-line no-param-reassign
item.querySelector(
- this.options.selectorLeftNavListItemLink,
+ this.options.selectorLeftNavListItemLink
).tabIndex = 0;
}
});
@@ -154,8 +165,7 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
this.element.dataset.collapsed = true;
this.element.classList.add(this.options.classLeftNavCollapsing);
- window.setTimeout(
- () => {
+ window.setTimeout(() => {
this.element.classList.remove(this.options.classLeftNavCollapsing);
this.element.classList.add(this.options.classLeftNavCollapsed);
this.element.dispatchEvent(
@@ -163,29 +173,24 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
bubbles: true,
cancelable: true,
detail: { collapsed: true },
- }),
- );
- },
- 250,
+ })
);
+ }, 250);
} else {
this.element.dataset.collapsed = false;
this.element.classList.remove(this.options.classLeftNavCollapsed);
this.element.classList.add(this.options.classLeftNavExpanding);
- window.setTimeout(
- () => {
+ window.setTimeout(() => {
this.element.classList.remove(this.options.classLeftNavExpanding);
this.element.dispatchEvent(
new CustomEvent(this.options.eventAfterLeftNavToggled, {
bubbles: true,
cancelable: true,
detail: { collapsed: false },
- }),
- );
- },
- 250,
+ })
);
+ }, 250);
}
}
};
@@ -214,7 +219,8 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
selectorLeftNavListItem: '[data-interior-left-nav-item]',
selectorLeftNavListItemLink: '[data-interior-left-nav-item-link]',
selectorLeftNavNestedListItem: '[data-interior-left-nav-nested-item]',
- selectorLeftNavListItemHasChildren: '[data-interior-left-nav-with-children]',
+ selectorLeftNavListItemHasChildren:
+ '[data-interior-left-nav-with-children]',
selectorLeftNavCollapse: '[data-interior-left-nav-collapse]',
// CSS Class Selectors
classActiveLeftNavListItem: 'left-nav-list__item--active',
@@ -225,6 +231,8 @@ class InteriorLeftNav extends mixin(createComponent, initComponentBySearch) {
// Event
eventBeforeLeftNavToggled: 'left-nav-beingtoggled',
eventAfterLeftNavToggled: 'left-nav-toggled',
+ // Option
+ keepOpen: false,
};
}

MODIFY
tests/spec/interior-left-nav_spec.js
tests/spec/interior-left-nav_spec.js
@@ -3,6 +3,7 @@ import InteriorLeftNav
from '../../src/components/interior-left-nav/interior-left-nav';
import InteriorLeftNavHtml
from '../../src/components/interior-left-nav/interior-left-nav.html';
+import KeepOpen from '../../src/components/interior-left-nav/interior-left-nav-keep-open.html';
describe('Test interior left nav', function () {
describe('Constructor', function () {
@@ -45,6 +46,67 @@ describe('Test interior left nav', function () {
});
});
+ describe('keepOpen', function () {
+ let container;
+ let element;
+ let instance1;
+ let instance2;
+ let instance3;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ container.innerHTML = InteriorLeftNavHtml;
+ document.body.appendChild(container);
+ element = document.querySelector('[data-interior-left-nav]');
+ });
+
+ it('Should have a default setting of false', function () {
+ instance1 = new InteriorLeftNav(element);
+ expect(instance1.options.keepOpen).to.be.false;
+ document.body.removeChild(container);
+ instance1.release();
+ });
+
+ it('Should accept an option in the constructor to override', function () {
+ instance2 = new InteriorLeftNav(element, {
+ keepOpen: true,
+ });
+ expect(instance2.options.keepOpen).to.be.true;
+ document.body.removeChild(container);
+ instance2.release();
+ });
+
+ it('Should read data-keep-open attribute in the markup to override', function () {
+ document.body.removeChild(container);
+ const container2 = document.createElement('div');
+ container2.innerHTML = KeepOpen;
+ document.body.appendChild(container2);
+ element = document.querySelector('[data-interior-left-nav]');
+ instance3 = new InteriorLeftNav(element);
+ expect(instance3.keepOpen).to.be.true;
+ document.body.removeChild(container2);
+ instance3.release();
+ });
+
+ it('If true, should not remove expanded class from other elements on second open', function () {
+ document.body.removeChild(container);
+ const container2 = document.createElement('div');
+ container2.innerHTML = KeepOpen;
+ document.body.appendChild(container2);
+ element = document.querySelector('[data-interior-left-nav]');
+ instance3 = new InteriorLeftNav(element);
+
+ const nested = document.querySelectorAll('.left-nav-list__item--has-children');
+
+ for (let i = 0; i < nested.length; i += 1) {
+ nested[i].dispatchEvent(new CustomEvent('click', { bubbles: true }));
+ }
+
+ const expanded = document.querySelectorAll('.left-nav-list__item--expanded');
+ expect(expanded.length).to.equal(nested.length);
+ });
+ });
+
describe('addActiveListItem', function () {
let container;
let element;

feat(ILN): add in keepOpen configuration and tests (#301)