UNPKG

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
"use strict"; 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);