All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
163 lines
4.7 KiB
JavaScript
163 lines
4.7 KiB
JavaScript
'use strict';
|
|
|
|
var createFocusGroup = require('focus-group');
|
|
var externalStateControl = require('./externalStateControl');
|
|
|
|
var focusGroupOptions = {
|
|
wrap: true,
|
|
stringSearch: true
|
|
};
|
|
|
|
var protoManager = {
|
|
init: function init(options) {
|
|
this.updateOptions(options);
|
|
|
|
this.handleBlur = handleBlur.bind(this);
|
|
this.handleSelection = handleSelection.bind(this);
|
|
this.handleMenuKey = handleMenuKey.bind(this);
|
|
|
|
// "With focus on the drop-down menu, the Up and Down Arrow
|
|
// keys move focus within the menu items, "wrapping" at the top and bottom."
|
|
// "Typing a letter (printable character) key moves focus to the next
|
|
// instance of a visible node whose title begins with that printable letter."
|
|
//
|
|
// All of the above is handled by focus-group.
|
|
this.focusGroup = createFocusGroup(focusGroupOptions);
|
|
|
|
// These component references are added when the relevant components mount
|
|
this.button = null;
|
|
this.menu = null;
|
|
|
|
// State trackers
|
|
this.isOpen = false;
|
|
},
|
|
updateOptions: function updateOptions(options) {
|
|
var oldOptions = this.options;
|
|
|
|
this.options = options || this.options || {};
|
|
|
|
if (typeof this.options.closeOnSelection === 'undefined') {
|
|
this.options.closeOnSelection = true;
|
|
}
|
|
|
|
if (typeof this.options.closeOnBlur === 'undefined') {
|
|
this.options.closeOnBlur = true;
|
|
}
|
|
|
|
if (this.options.id) {
|
|
externalStateControl.registerManager(this.options.id, this);
|
|
}
|
|
|
|
if (oldOptions && oldOptions.id && oldOptions.id !== this.options.id) {
|
|
externalStateControl.unregisterManager(this.options.id, this);
|
|
}
|
|
},
|
|
focusItem: function focusItem(index) {
|
|
this.focusGroup.focusNodeAtIndex(index);
|
|
},
|
|
addItem: function addItem(item) {
|
|
this.focusGroup.addMember(item);
|
|
},
|
|
clearItems: function clearItems() {
|
|
this.focusGroup.clearMembers();
|
|
},
|
|
handleButtonNonArrowKey: function handleButtonNonArrowKey(event) {
|
|
this.focusGroup._handleUnboundKey(event);
|
|
},
|
|
destroy: function destroy() {
|
|
this.button = null;
|
|
this.menu = null;
|
|
this.focusGroup.deactivate();
|
|
clearTimeout(this.blurTimer);
|
|
clearTimeout(this.moveFocusTimer);
|
|
},
|
|
update: function update() {
|
|
this.menu.setState({ isOpen: this.isOpen });
|
|
this.button.setState({ menuOpen: this.isOpen });
|
|
this.options.onMenuToggle && this.options.onMenuToggle({ isOpen: this.isOpen });
|
|
},
|
|
openMenu: function openMenu(openOptions) {
|
|
if (this.isOpen) return;
|
|
openOptions = openOptions || {};
|
|
if (openOptions.focusMenu === undefined) {
|
|
openOptions.focusMenu = true;
|
|
}
|
|
this.isOpen = true;
|
|
this.update();
|
|
this.focusGroup.activate();
|
|
if (openOptions.focusMenu) {
|
|
var self = this;
|
|
this.moveFocusTimer = setTimeout(function () {
|
|
self.focusItem(0);
|
|
}, 0);
|
|
}
|
|
},
|
|
closeMenu: function closeMenu(closeOptions) {
|
|
if (!this.isOpen) return;
|
|
closeOptions = closeOptions || {};
|
|
this.isOpen = false;
|
|
this.update();
|
|
if (closeOptions.focusButton) {
|
|
this.button.ref.current.focus();
|
|
}
|
|
},
|
|
toggleMenu: function toggleMenu(closeOptions, openOptions) {
|
|
closeOptions = closeOptions || {};
|
|
openOptions = openOptions || {};
|
|
if (this.isOpen) {
|
|
this.closeMenu(closeOptions);
|
|
} else {
|
|
this.openMenu(openOptions);
|
|
}
|
|
}
|
|
};
|
|
|
|
function handleBlur() {
|
|
var self = this;
|
|
self.blurTimer = setTimeout(function () {
|
|
if (!self.button) return;
|
|
var buttonNode = self.button.ref.current;
|
|
if (!buttonNode) return;
|
|
var activeEl = buttonNode.ownerDocument.activeElement;
|
|
if (buttonNode && activeEl === buttonNode) return;
|
|
var menuNode = self.menu.ref.current;
|
|
if (menuNode === activeEl) {
|
|
self.focusItem(0);
|
|
return;
|
|
}
|
|
if (menuNode && menuNode.contains(activeEl)) return;
|
|
if (self.isOpen) self.closeMenu({ focusButton: false });
|
|
}, 0);
|
|
}
|
|
|
|
function handleSelection(value, event) {
|
|
if (this.options.closeOnSelection) this.closeMenu({ focusButton: true });
|
|
if (this.options.onSelection) this.options.onSelection(value, event);
|
|
}
|
|
|
|
function handleMenuKey(event) {
|
|
if (this.isOpen) {
|
|
switch (event.key) {
|
|
// With focus on the drop-down menu, pressing Escape closes
|
|
// the menu and returns focus to the button.
|
|
case 'Escape':
|
|
event.preventDefault();
|
|
this.closeMenu({ focusButton: true });
|
|
break;
|
|
case 'Home':
|
|
event.preventDefault();
|
|
this.focusGroup.moveFocusToFirst();
|
|
break;
|
|
case 'End':
|
|
event.preventDefault();
|
|
this.focusGroup.moveFocusToLast();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = function (options) {
|
|
var newManager = Object.create(protoManager);
|
|
newManager.init(options);
|
|
return newManager;
|
|
}; |