baseframe-js
Version:
A suite of useful Javascript plugins and functions to help with Front-end Development on websites
643 lines (642 loc) • 25.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _cashDom = _interopRequireDefault(require("cash-dom"));
var _Store = _interopRequireDefault(require("./core/Store"));
var _constants = require("./core/constants");
var _helpers = require("./util/helpers");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var VERSION = "3.0.0";
var EVENT_NAME = 'selectEnhance';
var DATA_NAME = 'SelectEnhance';
var DEFAULTS = {
cssPrefix: 'select-enhance',
cssListModifer: null,
mobileNative: false,
emptyValAsPlaceholder: true,
focusIn: _helpers.noop,
focusOut: _helpers.noop,
beforeChange: _helpers.noop,
afterChange: _helpers.noop,
blurDuration: 250,
typeAheadDuration: 500,
observeSelectbox: true
};
// TOOD:
// - Search with Keyboard
// wrap the select first
var mobileOS = (0, _helpers.isMobileOS)();
// helper
var getSelectedOptNode = function getSelectedOptNode($el) {
return $el.find('option').filter(function () {
return this.selected;
})[0];
};
var currSelectEnhance = null;
var SelectEnhance = exports["default"] = /*#__PURE__*/function () {
function SelectEnhance(element, options, index) {
_classCallCheck(this, SelectEnhance);
_defineProperty(this, "$select", void 0);
_defineProperty(this, "select", void 0);
_defineProperty(this, "params", void 0);
_defineProperty(this, "index", void 0);
_defineProperty(this, "id", void 0);
_defineProperty(this, "selectId", void 0);
_defineProperty(this, "isReadOnly", void 0);
_defineProperty(this, "$label", void 0);
_defineProperty(this, "$selectEnhance", void 0);
_defineProperty(this, "selectEnhance", void 0);
_defineProperty(this, "$textInput", void 0);
_defineProperty(this, "textInput", void 0);
_defineProperty(this, "$selectList", void 0);
_defineProperty(this, "selectList", void 0);
_defineProperty(this, "optionSet", void 0);
_defineProperty(this, "listPosTop", void 0);
_defineProperty(this, "optionsShown", void 0);
_defineProperty(this, "selectboxObserver", void 0);
_defineProperty(this, "selectListBoxInFullView", void 0);
_defineProperty(this, "keyedInput", void 0);
_defineProperty(this, "posTimeout", void 0);
_defineProperty(this, "bodyCloseEvt", void 0);
var s = this;
s.index = index;
s.$select = (0, _cashDom["default"])(element);
s.select = element;
s.id = s.$select.attr('id') || s.$select.attr('name') || 'select_enhance_' + index;
s.selectId = s.id + '_enhance';
s.$label = (0, _cashDom["default"])("label[for=\"".concat(s.id, "\"]"));
s.optionSet = new WeakMap();
s.optionsShown = false;
s.listPosTop = false;
s.selectboxObserver;
s.selectListBoxInFullView = true;
s.keyedInput = "";
s.posTimeout = null;
s.bodyCloseEvt = 'click.close_' + s.selectId + EVENT_NAME;
s.isReadOnly = typeof s.$select.attr('readonly') === "string";
if (s.select.multiple) {
console.warn("The SelectEnhance plugin doesn't support multiple selections.");
}
if (s.$label.length) {
s.$label.attr({
id: s.selectId + '_lbl'
});
}
var dataOptions = (0, _helpers.getDataOptions)(element, EVENT_NAME);
s.params = _cashDom["default"].extend({}, SelectEnhance.defaults, options, dataOptions);
s.setUpSelectHtml();
if (mobileOS && s.params.mobileNative || s.isReadOnly) {
s.mobileOnlyIfNavite();
} else {
//
// attach events
//
if (s.params.observeSelectbox) {
s.selectInputMutationObserver();
}
s.eventLabelClick();
s.eventKeyboardSearch();
s.eventShowOptions();
s.eventOptionClick();
s.eventArrowKeys();
s.eventResizeClose();
s.observeSelectListBoxInFullView();
}
return s;
}
return _createClass(SelectEnhance, [{
key: "mobileOnlyIfNavite",
value: function mobileOnlyIfNavite() {
var s = this;
var prevElSelectedVal = getSelectedOptNode(s.$select);
s.$select.on('mouseup.' + EVENT_NAME, 'option', function (e) {
if (!this.isSameNode(prevElSelectedVal)) {
s.params.beforeChange(s.$select);
}
}).on('change.' + EVENT_NAME, function (e) {
s.params.afterChange(s.$select);
prevElSelectedVal = getSelectedOptNode(s.$select);
});
}
}, {
key: "showOptions",
value: function showOptions() {
var s = this;
if (s.select.disabled || s.optionsShown) {
return;
}
s.optionsShown = true;
currSelectEnhance = s.selectEnhance;
var cssPrefix = s.params.cssPrefix;
var $selectedBtn = s.$selectList.find('[aria-selected="true"]');
document.body.append(s.selectList);
s.$selectEnhance.toggleClass(cssPrefix + '--focused');
s.$textInput.attr({
'aria-expanded': 'true'
});
s.getListPosition();
s.eventScrollGetListPosition(true);
s.eventOutsideClickClose();
if ($selectedBtn.length) {
$selectedBtn[0].focus();
}
s.params.focusIn(s.$select);
}
}, {
key: "closeOptions",
value: function closeOptions() {
var focusBack = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
var s = this;
var cssPrefix = s.params.cssPrefix;
s.optionsShown = false;
s.$selectEnhance.addClass(cssPrefix + '--blurring').removeClass(cssPrefix + '--focused');
s.$textInput.attr({
'aria-expanded': 'false'
});
s.$selectList.addClass(cssPrefix + '__list--blurring').removeClass(cssPrefix + '__list--focused');
// remove the close event on the body
(0, _cashDom["default"])(document.body).off(s.bodyCloseEvt);
setTimeout(function () {
s.$selectEnhance.removeClass(cssPrefix + '--blurring ' + cssPrefix + '--pos-above');
s.$selectList.detach().removeClass(cssPrefix + '__list--blurring ' + cssPrefix + '__list--focused' + cssPrefix + '__list--pos-above');
s.params.focusOut(s.$select);
if (focusBack && s.selectEnhance.isSameNode(currSelectEnhance)) {
s.textInput.focus();
}
s.eventScrollGetListPosition(true);
}, s.params.blurDuration);
}
}, {
key: "setSelectionState",
value: function setSelectionState(optionBtn) {
var doBlur = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var s = this;
var cssPrefix = s.params.cssPrefix;
var selectedOpt = s.optionSet.get(optionBtn);
if (!selectedOpt) return;
var newSelectedState = !selectedOpt.selected;
s.params.beforeChange(s.$select);
selectedOpt.selected = true;
s.params.afterChange(s.$select);
s.$selectList.find('.' + cssPrefix + '__list-btn[aria-selected]').attr({
'aria-selected': 'false'
});
(0, _cashDom["default"])(optionBtn).attr({
'aria-selected': newSelectedState + ''
});
s.$textInput.attr({
'aria-activedescendant': optionBtn.id
});
// add a class whether there is an input value or not
s.$selectEnhance.toggleClass(cssPrefix + '--empty-val', !selectedOpt.value.trim());
s.select.dispatchEvent(new Event('change'));
if (s.params.emptyValAsPlaceholder && selectedOpt.value.trim() === '') {
s.$textInput.val('').attr({
placeholder: selectedOpt.text
});
} else {
s.$textInput.val(selectedOpt.text);
}
if (doBlur) {
s.closeOptions();
} else {
optionBtn.focus();
}
}
// Events
}, {
key: "eventLabelClick",
value: function eventLabelClick() {
var s = this;
s.$label.on('click.' + EVENT_NAME, function (e) {
e.preventDefault();
if (!s.select.disabled) {
s.textInput.focus();
}
});
}
}, {
key: "eventResizeClose",
value: function eventResizeClose() {
var s = this;
(0, _cashDom["default"])(window).on('resize.' + EVENT_NAME, function () {
if (s.optionsShown) s.closeOptions();
});
}
}, {
key: "eventShowOptions",
value: function eventShowOptions() {
var s = this;
var cssPrefix = s.params.cssPrefix;
s.$selectEnhance.on('click.' + EVENT_NAME, '.' + cssPrefix + '__enable-text', function (e) {
if (s.select.disabled) {
return;
}
s.showOptions();
});
// Only works on keydown event
s.$textInput.on('keydown.' + EVENT_NAME, function (e) {
if (e.key === _constants.KEYS.down || e.key === _constants.KEYS.up) {
s.showOptions();
e.preventDefault();
}
});
s.$textInput.on('keypress.' + EVENT_NAME, function (e) {
if (e.key !== _constants.KEYS.tab || e.shiftKey && e.key !== _constants.KEYS.tab) {
e.preventDefault();
}
if (e.key === _constants.KEYS.enter ||
//enter key
e.code === _constants.KEYS.space && s.keyedInput.trim() === '' ||
// space key
e.ctrlKey && e.altKey && e.shiftKey && _constants.KEYS.space === e.code) {
s.showOptions();
console.log('whha');
}
});
}
}, {
key: "eventOptionClick",
value: function eventOptionClick() {
var s = this;
var cssPrefix = s.params.cssPrefix;
var newSelectedOption = function newSelectedOption(elem) {
s.setSelectionState(document.activeElement);
s.$selectEnhance.removeClass(cssPrefix + '--focused');
setTimeout(function () {
return s.closeOptions();
}, 200);
};
// mouse click
s.$selectList.on('click.' + EVENT_NAME, '.' + cssPrefix + '__list-btn', function (e) {
newSelectedOption(this);
e.preventDefault();
});
// Kyeboard click
s.$selectList.on('keypress.' + EVENT_NAME, '.' + cssPrefix + '__list-btn', function (e) {
if (e.key === _constants.KEYS.enter || e.code === _constants.KEYS.space && s.keyedInput.trim() === '') {
newSelectedOption(document.activeElement);
e.preventDefault();
}
});
}
}, {
key: "eventOutsideClickClose",
value: function eventOutsideClickClose() {
var s = this;
setTimeout(function () {
(0, _cashDom["default"])(document.body).off(s.bodyCloseEvt).on(s.bodyCloseEvt, function (e) {
var ae = document.activeElement;
if (ae && !s.$selectList.has(ae).length) {
s.closeOptions();
}
});
}, 100);
}
}, {
key: "eventKeyboardSearch",
value: function eventKeyboardSearch() {
var s = this;
var keyInputTo = null;
var changedTo = null;
var keyedFound;
function keyboardSearch(e) {
if (s.select.disabled) {
return;
}
var keyCurr = e.key.length === 1 ? e.key : '';
s.keyedInput += keyCurr;
clearTimeout(keyInputTo);
keyInputTo = setTimeout(function () {
s.keyedInput = "";
}, s.params.typeAheadDuration);
if (s.keyedInput.trim()) {
var rgx = RegExp(s.keyedInput.trim(), 'i');
keyedFound = Array.from(s.select.options).find(function (el) {
return rgx.test(el.text);
});
if (keyedFound) {
clearTimeout(changedTo);
changedTo = setTimeout(function () {
s.setSelectionState(s.optionSet.get(keyedFound), false);
}, 100);
}
}
}
s.$selectEnhance.on('keypress.' + EVENT_NAME, keyboardSearch);
s.$selectList.on('keypress.' + EVENT_NAME, keyboardSearch);
}
}, {
key: "eventArrowKeys",
value: function eventArrowKeys() {
var s = this;
s.$selectEnhance.on('keydown.navigate_' + EVENT_NAME, function (e) {
if (s.select.disabled || !s.optionsShown) {
return;
}
if (e.key === _constants.KEYS.down) {
if (!s.textInput.isSameNode(document.activeElement)) {
e.preventDefault();
}
s.nextOptionButton('next');
}
if (e.key === _constants.KEYS.esc) {
s.closeOptions();
}
if (e.key === _constants.KEYS.tab) {
s.closeOptions(false);
}
});
s.$selectList.on('keydown.navigate_' + EVENT_NAME, function (e) {
if (s.select.disabled || !s.optionsShown) {
return;
}
if (e.key === _constants.KEYS.down) {
if (!s.textInput.isSameNode(document.activeElement)) {
e.preventDefault();
}
s.nextOptionButton('next');
}
if (e.key === _constants.KEYS.up) {
e.preventDefault();
s.nextOptionButton('prev');
}
if (e.key === _constants.KEYS.esc) {
s.closeOptions();
}
});
}
// build the HTML for it
}, {
key: "setUpSelectHtml",
value: function setUpSelectHtml() {
var s = this;
var $selectEnhance = (0, _cashDom["default"])('<div />').attr({
"class": s.params.cssPrefix,
id: s.selectId + '_wrap'
});
var $textInput = (0, _cashDom["default"])('<input>').attr({
type: 'text',
"class": s.params.cssPrefix + '__enable-text',
role: "combobox",
'aria-controls': s.selectId + '_listbox',
'aria-labelledby': s.selectId + '_lbl',
'aria-autocomplete': 'list',
'aria-expanded': 'false',
id: s.selectId + '_input'
});
s.$select.wrap($selectEnhance);
// jQuery, elements need to be bound to the DOM before they
// can have events attached to them. So this is the solution
s.$selectEnhance = s.$select.parent();
s.selectEnhance = s.$selectEnhance[0];
if (mobileOS && s.params.mobileNative || s.isReadOnly) {
// exit if its a mobile device after wrapping for styling
return;
}
$textInput.insertAfter(s.$select);
s.$select.attr({
tabindex: '-1',
'aria-hidden': 'true'
});
// jQuery, elements need to be bound to the DOM before they
// can have events attached to them. So this is the solution
s.$textInput = s.$select.parent().find('#' + s.selectId + '_input');
s.textInput = s.$textInput[0];
SelectEnhance.buildOptionsList(s);
}
}, {
key: "nextOptionButton",
value: function nextOptionButton(dir) {
var s = this;
var ae = document.activeElement;
var $btnList = s.$selectList.find('.' + s.params.cssPrefix + '__list-btn');
var btnLength = $btnList.length;
if (btnLength && s.textInput.isSameNode(ae)) {
$btnList.eq(dir === 'next' ? 0 : btnLength - 1)[0].focus();
return;
}
for (var i = 0; i < btnLength; i++) {
var el = $btnList[i];
var prevNextIndex = 0;
if (ae.isSameNode(el)) {
if (dir === 'next') {
var isLast = i === btnLength - 1;
prevNextIndex = isLast ? i : i + 1;
} else {
var isFirst = i === 0;
prevNextIndex = isFirst ? i : i - 1;
}
$btnList.eq(prevNextIndex)[0].focus();
break;
}
}
}
}, {
key: "selectInputMutationObserver",
value: function selectInputMutationObserver() {
var s = this;
var selectNode = s.select;
var config = {
attributes: true,
childList: true,
subtree: true
};
// Callback function to execute when mutations are observed
var callback = function callback(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for (var i = 0, l = mutationsList.length; i < l; i++) {
var mutation = mutationsList[i];
if (mutation.type === 'childList') {
SelectEnhance.refreshOptions(s.select);
} else if (mutation.type === 'attributes') {
s.$selectEnhance.toggleClass(s.params.cssPrefix + '--disabled', s.select.disabled);
}
}
};
// Create an observer instance linked to the callback function
s.selectboxObserver = new MutationObserver(callback);
// Start observing the target node for configured mutations
s.selectboxObserver.observe(selectNode, config);
}
}, {
key: "eventScrollGetListPosition",
value: function eventScrollGetListPosition() {
var init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
var s = this;
var evt = 'scroll.' + EVENT_NAME;
if (init) {
(0, _cashDom["default"])(window).off(evt).on(evt, function () {
s.posTimeout && clearTimeout(s.posTimeout);
s.posTimeout = setTimeout(function () {
return s.getListPosition();
}, 100);
});
} else {
(0, _cashDom["default"])(window).off(evt);
}
}
}, {
key: "observeSelectListBoxInFullView",
value: function observeSelectListBoxInFullView() {
var s = this;
if (window.IntersectionObserver) {
var selectIntersectionObserver = new IntersectionObserver(function (selects) {
s.selectListBoxInFullView = selects[0].intersectionRatio === 1;
}, {
threshold: [0, 1]
});
s.$selectList[0] && selectIntersectionObserver.observe(s.$selectList[0]);
}
}
}, {
key: "getListPosition",
value: function getListPosition() {
var s = this;
if (s.optionsShown) {
var cssPrefix = s.params.cssPrefix,
selWrapOffset = s.$selectEnhance.offset(),
selWrapHeight = s.selectEnhance.scrollHeight,
selWrapPosTop = selWrapOffset.top,
selWrapPosBot = selWrapPosTop + selWrapHeight,
selWrapWidth = s.selectEnhance.offsetWidth,
selListHeight = s.selectList.offsetHeight,
_window = window,
innerHeight = _window.innerHeight,
scrollY = _window.scrollY,
scrollYBot = scrollY + innerHeight,
isSpaceAbove = selWrapPosTop - scrollY > selListHeight,
isSpaceBelow = scrollYBot - selWrapPosBot > selListHeight,
canPlaceBelow = isSpaceBelow ? true : isSpaceAbove ? false : true;
s.$selectEnhance.toggleClass(cssPrefix + '--pos-above', !canPlaceBelow);
s.$selectList.css({
left: selWrapOffset.left,
top: canPlaceBelow ? selWrapPosBot : selWrapPosTop - selListHeight,
width: selWrapWidth
}).toggleClass(cssPrefix + '__list--focused', s.optionsShown).toggleClass(cssPrefix + '__list--pos-above', !canPlaceBelow);
}
}
}], [{
key: "buildOptionsList",
value: function buildOptionsList(s, $selectList) {
var _s$params = s.params,
cssListModifer = _s$params.cssListModifer,
cssPrefix = _s$params.cssPrefix;
var optGroup = s.select.getElementsByTagName('optgroup');
var hasOptGroup = !!optGroup.length;
var $optGroupWm = new WeakMap();
var modifierCss = " " + (cssListModifer ? cssPrefix + '__list--' + cssListModifer : '');
if (hasOptGroup) {
for (var i = 0, l = optGroup.length; i < l; i++) {
var group = optGroup[i];
$optGroupWm.set(group, (0, _cashDom["default"])('<div/>').attr({
"class": cssPrefix + '__optgroup',
role: 'group',
label: group.label || ""
}).append((0, _cashDom["default"])('<span>').attr({
"class": cssPrefix + '__optgroup-label',
'aria-hidden': 'true'
}).text(group.label || "")));
}
}
var options = s.select.options;
s.$selectList = $selectList ? $selectList : (0, _cashDom["default"])('<div>').attr({
"class": cssPrefix + '__list' + modifierCss,
role: 'listbox',
id: s.selectId + '_listbox',
'aria-label': s.$label.text() || ''
});
s.selectList = s.$selectList[0];
var optId = s.selectId || 'select_' + s.index;
for (var _i = 0, _l = options.length; _i < _l; _i++) {
var option = options[_i];
var id = optId + _i;
if (option.hidden) continue;
var valCssStatus = option.value === '' ? ' ' + cssPrefix + '__list-btn--empty' : '';
var attrs = {
// type: 'button',
tabIndex: '0',
role: 'option',
id: id,
'data-value': option.value,
'aria-selected': option.selected + '',
disabled: option.disabled ? 'disabled' : null,
"class": cssPrefix + '__list-btn' + valCssStatus
};
var optionBtn = (0, _cashDom["default"])('<div/>').attr(attrs).text(option.textContent)[0];
s.optionSet.set(optionBtn, option);
s.optionSet.set(option, optionBtn);
// append to list or optgroup
hasOptGroup && option.parentElement.nodeName.toUpperCase() === 'OPTGROUP' ? $optGroupWm.get(option.parentElement).append(optionBtn) : s.$selectList.append(optionBtn);
if (option.selected) {
s.$textInput.attr({
'aria-activedescendant': id
});
s.$selectEnhance.toggleClass(cssPrefix + '--empty-val', !option.value.trim());
if (s.params.emptyValAsPlaceholder && option.value.trim() === '') {
s.$textInput.val('');
s.$textInput.attr({
placeholder: option.text
});
} else {
s.$textInput.val(option.text);
}
}
}
if (hasOptGroup) {
for (var _i2 = 0, _l2 = optGroup.length; _i2 < _l2; _i2++) {
var _group = optGroup[_i2];
s.$selectList.append($optGroupWm.get(_group));
}
}
}
}, {
key: "refreshOptions",
value: function refreshOptions(element) {
(0, _cashDom["default"])(element).each(function () {
var s = (0, _Store["default"])(this, DATA_NAME);
if (s) {
s.$selectList.empty();
s.optionSet = new WeakMap();
SelectEnhance.buildOptionsList(s, s.$selectList);
} else {
console.warn("No instance of a selectbox", element);
}
});
}
}, {
key: "remove",
value: function remove(element, plugin) {
(0, _cashDom["default"])(element).each(function () {
var s = plugin || (0, _Store["default"])(this, DATA_NAME);
s.$selectEnhance.off('keydown.' + EVENT_NAME).off('keydown.navigate_' + EVENT_NAME).off('click.' + EVENT_NAME).off('focusout.' + EVENT_NAME).off('blur.' + EVENT_NAME);
s.$label.off('click.' + EVENT_NAME);
(0, _cashDom["default"])(document.body).off(s.bodyCloseEvt);
(0, _cashDom["default"])(window).off('resize.' + EVENT_NAME);
// the window event will just stay
s.$select.insertAfter(s.$selectEnhance);
s.$select.attr({
tabindex: null,
'aria-hidden': null
}).off('mouseup.' + EVENT_NAME).off('change.' + EVENT_NAME);
if (s.selectboxObserver) {
s.selectboxObserver.disconnect();
}
s.$selectEnhance.remove();
(0, _Store["default"])(this, DATA_NAME, null);
});
}
}]);
}();
_defineProperty(SelectEnhance, "defaults", DEFAULTS);
_defineProperty(SelectEnhance, "version", VERSION);
_defineProperty(SelectEnhance, "pluginName", DATA_NAME);