Skip to content

Commit

Permalink
Refactor constructor options, replace mutation observers with callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
dotherightthing committed Jan 1, 2021
1 parent b3102e9 commit 73ce47d
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 67 deletions.
49 changes: 37 additions & 12 deletions js/_keyboard-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @param {boolean} options.infiniteNavigation - Whether to loop the focus to the first/last keyboardNavigableElement when the focus is out of range
* @param {object} options.keyboardActions - The key(s) which trigger actions
* @param {null|NodeList} options.keyboardNavigableElements - The DOM element(s) which will become keyboard navigable
* @param {null|Function} options.onSelect - Callback with an argument of selectedElement, called after an element is selected
* @param {Array} options.selectedAttr - Property and Value applied to the selected keyboardNavigableElement
* @param {boolean} options.selectionFollowsFocus - Automatically select the focussed option (<https://www.w3.org/TR/wai-aria-practices/#kbd_selection_follows_focus>)
* @param {object} options.toggleActions - The key(s) which toggle the parent state
Expand All @@ -23,19 +24,36 @@
* @todo Make this a module, as it doesn't need to manage state
*/
class KeyboardHelpers {
constructor(options = {}) {
constructor(options = {
instanceElement: null,
infiniteNavigation: false,
keyboardActions: {},
keyboardNavigableElements: null,
onSelect: () => { },
selectedAttr: [],
selectionFollowsFocus: false,
toggleActions: {},
toggleElement: null,
toggleAfterSelected: false,
unselectedAttr: [],
useRovingTabIndex: false
}) {
// public options
this.instanceElement = options.instanceElement || null;
this.infiniteNavigation = options.infiniteNavigation || false;
this.keyboardActions = options.keyboardActions || {};
this.keyboardNavigableElements = options.keyboardNavigableElements || null;
this.selectedAttr = options.selectedAttr || [];
this.selectionFollowsFocus = options.selectionFollowsFocus || false;
this.toggleActions = options.toggleActions || {};
this.toggleElement = options.toggleElement || null;
this.toggleAfterSelected = options.toggleAfterSelected || false;
this.unselectedAttr = options.unselectedAttr || [];
this.useRovingTabIndex = options.useRovingTabIndex || false;
this.instanceElement = options.instanceElement;
this.infiniteNavigation = options.infiniteNavigation;
this.keyboardActions = options.keyboardActions;
this.keyboardNavigableElements = options.keyboardNavigableElements;
this.selectedAttr = options.selectedAttr;
this.selectionFollowsFocus = options.selectionFollowsFocus;
this.toggleActions = options.toggleActions;
this.toggleElement = options.toggleElement;
this.toggleAfterSelected = options.toggleAfterSelected;
this.unselectedAttr = options.unselectedAttr;
this.useRovingTabIndex = options.useRovingTabIndex;

if (options.onSelect instanceof Function) {
this.onSelect = options.onSelect;
}

// private options

Expand Down Expand Up @@ -414,6 +432,7 @@ class KeyboardHelpers {
*/
selectFocussed(e) {
const focussed = document.activeElement;
const self = this;

if (this.isKeyboardNavigableElement(focussed)) {
const selectedAttrProp = this.selectedAttr[0];
Expand All @@ -438,6 +457,8 @@ class KeyboardHelpers {
this.toggleClosed();
}
}

self.onSelect.call(self, focussed);
}
}

Expand All @@ -461,6 +482,8 @@ class KeyboardHelpers {
* @param {Node} element - DOM Element
*/
selectNonFocussed(element) {
const self = this;

if (this.isKeyboardNavigableElement(element)) {
const selectedAttrProp = this.selectedAttr[0];
const selectedAttrVal = this.selectedAttr[1];
Expand All @@ -476,6 +499,8 @@ class KeyboardHelpers {

// this triggers mutation observer callback in host component
element.setAttribute(selectedAttrProp, selectedAttrVal);

self.onSelect.call(self, element);
}
}

Expand Down
84 changes: 29 additions & 55 deletions js/_tabbed-carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@
* @param {object} options - Module options
* @param {null|number} options.initialSelection - Tab to select on init
* @param {null|Node} options.instanceElement - The outermost DOM element
* @param {null|Function} options.onTabSelect - Callback with an argument of selectedTabPanel, called after a tab is selected
* @param {boolean} options.selectionFollowsFocus - Select the focussed tab, see <https://www.w3.org/TR/wai-aria-practices/#kbd_selection_follows_focus>
*/
class TabbedCarousel {
constructor(options = {}) {
constructor(options = {
initialSelection: null,
instanceElement: null,
selectionFollowsFocus: false,
onTabSelect: () => { }
}) {
// public options
this.initialSelection = options.initialSelection || null;
this.instanceElement = options.instanceElement || null;
this.selectionFollowsFocus = options.selectionFollowsFocus || false;
this.initialSelection = options.initialSelection;
this.instanceElement = options.instanceElement;
this.selectionFollowsFocus = options.selectionFollowsFocus;

if (options.onTabSelect instanceof Function) {
this.onTabSelect = options.onTabSelect;
}

// private options
// Note: when using setAttribute, any non-string value specified is automatically converted into a string.
Expand Down Expand Up @@ -82,55 +92,6 @@ class TabbedCarousel {
}
}

/**
* @function propagateSelection
* @summary When KeyboardHelpers makes a selection, update the UI to match
* @memberof TabbedCarousel
*
* @param {Node} target - Target to watch for changes
* @param {Node} tabPanels - Affected tab panels
*/
propagateSelection(target, tabPanels) {
// Options for the observer (which mutations to observe)
const observerConfig = {
attributes: true,
childList: false,
subtree: true
};

// const _self = this;
const selectedAttrProp = this.attributes.selected[0];
const selectedAttrVal = this.attributes.selected[1];

// Callback function to execute when mutations are observed
const callback = function (mutationsList) {
mutationsList.forEach(function (mutation) { // eslint-disable-line func-names
if (mutation.type === 'attributes') {
if (mutation.attributeName === selectedAttrProp) {
// if a tab was just selected
if (mutation.target.getAttribute(selectedAttrProp) === selectedAttrVal) {
const tab = mutation.target;
const tabPanelId = tab.getAttribute('aria-controls');
const selectedTabPanel = document.getElementById(tabPanelId);

tabPanels.forEach((tabPanel) => {
tabPanel.setAttribute('hidden', true);
});

selectedTabPanel.removeAttribute('hidden');
}
}
}
});
};

// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(target, observerConfig);
}

/**
* @function selectInitialSelection
* @summary Select the tab which should be active on init
Expand Down Expand Up @@ -162,6 +123,7 @@ class TabbedCarousel {
const tablist = document.querySelector(`#${this.instanceId} ${this.selectors.tablist}`);
const tabpanels = document.querySelectorAll(`#${this.instanceId} ${this.selectors.tabpanel}`);
const tabpanelExpandButtons = document.querySelectorAll(`#${this.instanceId} ${this.selectors.tabpanelExpandButton}`);
const self = this;

disabledButtons.forEach((disabledButton) => {
disabledButton.disabled = false;
Expand Down Expand Up @@ -211,6 +173,20 @@ class TabbedCarousel {
// an option is a value referenced by a name
keyboardNavigableElements: tabs,

onSelect: (element) => {
const tab = element;
const tabPanelId = tab.getAttribute('aria-controls');
const selectedTabPanel = document.getElementById(tabPanelId);

tabpanels.forEach((tabpanel) => {
tabpanel.setAttribute('hidden', true);
});

selectedTabPanel.removeAttribute('hidden');

self.onTabSelect.call(self, selectedTabPanel);
},

selectedAttr: this.attributes.selected,

// It is recommended that tabs activate automatically when they receive focus
Expand Down Expand Up @@ -238,8 +214,6 @@ class TabbedCarousel {
tabpanelExpandButton.addEventListener('click', this.onClickExpand.bind(this));
});

this.propagateSelection(tablist, tabpanels);

this.selectInitialSelection(tabs);
}
}
Expand Down

0 comments on commit 73ce47d

Please sign in to comment.