1/** 2 * wp-nav-menus.js 3 * https://github.com/cedaro/wp-nav-menus 4 * 5 * @copyright Copyright (c) 2016 Cedaro, LLC 6 * @license MIT 7 * @version 2.0.0 8 */ 9this["cedaroNavMenu"] = 10/******/ (function(modules) { // webpackBootstrap 11/******/ // The module cache 12/******/ var installedModules = {}; 13/******/ 14/******/ // The require function 15/******/ function __webpack_require__(moduleId) { 16/******/ 17/******/ // Check if module is in cache 18/******/ if(installedModules[moduleId]) { 19/******/ return installedModules[moduleId].exports; 20/******/ } 21/******/ // Create a new module (and put it into the cache) 22/******/ var module = installedModules[moduleId] = { 23/******/ i: moduleId, 24/******/ l: false, 25/******/ exports: {} 26/******/ }; 27/******/ 28/******/ // Execute the module function 29/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 30/******/ 31/******/ // Flag the module as loaded 32/******/ module.l = true; 33/******/ 34/******/ // Return the exports of the module 35/******/ return module.exports; 36/******/ } 37/******/ 38/******/ 39/******/ // expose the modules object (__webpack_modules__) 40/******/ __webpack_require__.m = modules; 41/******/ 42/******/ // expose the module cache 43/******/ __webpack_require__.c = installedModules; 44/******/ 45/******/ // define getter function for harmony exports 46/******/ __webpack_require__.d = function(exports, name, getter) { 47/******/ if(!__webpack_require__.o(exports, name)) { 48/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 49/******/ } 50/******/ }; 51/******/ 52/******/ // define __esModule on exports 53/******/ __webpack_require__.r = function(exports) { 54/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 55/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 56/******/ } 57/******/ Object.defineProperty(exports, '__esModule', { value: true }); 58/******/ }; 59/******/ 60/******/ // create a fake namespace object 61/******/ // mode & 1: value is a module id, require it 62/******/ // mode & 2: merge all properties of value into the ns 63/******/ // mode & 4: return value when already ns object 64/******/ // mode & 8|1: behave like require 65/******/ __webpack_require__.t = function(value, mode) { 66/******/ if(mode & 1) value = __webpack_require__(value); 67/******/ if(mode & 8) return value; 68/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 69/******/ var ns = Object.create(null); 70/******/ __webpack_require__.r(ns); 71/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 72/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 73/******/ return ns; 74/******/ }; 75/******/ 76/******/ // getDefaultExport function for compatibility with non-harmony modules 77/******/ __webpack_require__.n = function(module) { 78/******/ var getter = module && module.__esModule ? 79/******/ function getDefault() { return module['default']; } : 80/******/ function getModuleExports() { return module; }; 81/******/ __webpack_require__.d(getter, 'a', getter); 82/******/ return getter; 83/******/ }; 84/******/ 85/******/ // Object.prototype.hasOwnProperty.call 86/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 87/******/ 88/******/ // __webpack_public_path__ 89/******/ __webpack_require__.p = ""; 90/******/ 91/******/ 92/******/ // Load entry module and return exports 93/******/ return __webpack_require__(__webpack_require__.s = 1); 94/******/ }) 95/************************************************************************/ 96/******/ ([ 97/* 0 */ 98/***/ (function(module, exports, __webpack_require__) { 99 100"use strict"; 101 102 103Object.defineProperty(exports, "__esModule", { 104 value: true 105}); 106 107var _utils = __webpack_require__(5); 108 109var _utils2 = _interopRequireDefault(_utils); 110 111function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 112 113function Query(selector) { 114 var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; 115 116 if (selector instanceof Query) { 117 return selector; 118 } 119 120 if (!selector) { 121 return this; 122 } 123 124 if (context.length) { 125 context = context[0]; 126 } 127 128 if ('string' === typeof selector) { 129 selector = context.querySelectorAll(selector); 130 } 131 132 if (selector && selector.nodeType) { 133 this.length = 1; 134 this[0] = selector; 135 } else { 136 selector = Array.prototype.slice.call(selector); 137 var length = this.length = selector.length; 138 139 for (var i = 0; i < length; i++) { 140 this[i] = selector[i]; 141 } 142 } 143} 144 145_utils2.default.extend(Query.prototype, { 146 length: 0, 147 148 closest: function closest(selector) { 149 var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document.body; 150 151 var el = this[0]; 152 153 while (el && el !== context) { 154 if (_utils2.default.matches(el, selector)) { 155 return el; 156 } 157 158 el = el.parentElement; 159 } 160 161 return null; 162 }, 163 164 each: function each(callback) { 165 _utils2.default.each(this, callback); 166 return this; 167 }, 168 169 filter: function filter(callback) { 170 return query(Array.prototype.filter.call(this, callback)); 171 }, 172 173 /** 174 * @todo Improve performance: http://ryanmorr.com/abstract-away-the-performance-faults-of-queryselectorall/ 175 */ 176 find: function find(selector) { 177 return query(this[0].querySelectorAll(selector)); 178 }, 179 180 parents: function parents(selector) { 181 var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document.body; 182 183 var el = this[0]; 184 var parents = []; 185 186 while (el && el.parentElement && el.parentElement !== context) { 187 if (_utils2.default.matches(el.parentElement, selector)) { 188 parents.push(el.parentElement); 189 } 190 191 el = el.parentElement; 192 } 193 194 return query(parents); 195 }, 196 197 up: function up(selector, context) { 198 var items = Array.prototype.slice.call(this.parents(selector, context)); 199 200 if (_utils2.default.matches(this[0], selector)) { 201 items.unshift(this[0]); 202 } 203 204 return query(items); 205 } 206}); 207 208function query(selector, context) { 209 return new Query(selector, context); 210} 211 212exports.default = _utils2.default.extend(query, _utils2.default, { 213 isClickable: function isClickable(el) { 214 if (!el) { 215 return false;
216 } 217 218 var styles = window.getComputedStyle(el); 219 return 'none' !== styles.display && 'none' !== styles.pointerEvents; 220 }, 221 222 isElementOrDescendant: function isElementOrDescendant(el, tagName, container) { 223 return tagName === el.nodeName || null !== query(el).closest(tagName, container); 224 }, 225 226 isVisible: function isVisible(el) { 227 return el && 'none' !== window.getComputedStyle(el).display; 228 } 229}); 230 231/***/ }), 232/* 1 */ 233/***/ (function(module, exports, __webpack_require__) { 234 235"use strict"; 236 237 238var _jquery = __webpack_require__(2); 239 240var _jquery2 = _interopRequireDefault(_jquery); 241 242var _entry = __webpack_require__(3); 243 244var _entry2 = _interopRequireDefault(_entry); 245 246function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 247 248if (_jquery2.default) { 249 /** 250 * jQuery method to initialize menus. 251 * 252 * @memberOf jQuery.fn 253 * 254 * @param {Object} options Menu options. 255 * @return {jQuery} Chainable jQuery collection. 256 */ 257 _jquery2.default.fn.cedaroNavMenu = function (options) { 258 return this.each(function () { 259 var menu = (0, _entry2.default)(this, options); 260 menu.initialize(); 261 }); 262 }; 263} 264 265/** 266 * Export the method for instantiating a new menu. 267 */ 268module.exports = _entry2.default; 269 270/***/ }), 271/* 2 */ 272/***/ (function(module, exports) { 273 274(function() { module.exports = this["jQuery"]; }()); 275 276/***/ }), 277/* 3 */ 278/***/ (function(module, exports, __webpack_require__) { 279 280"use strict"; 281/* WEBPACK VAR INJECTION */(function(global) { 282 283Object.defineProperty(exports, "__esModule", { 284 value: true 285}); 286 287exports.default = function (el) { 288 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 289 290 // Fail gracefully in unsupported browsers. 291 if (!('addEventListener' in global)) { 292 return; 293 } 294 295 options.l10n = options.l10n || {}; 296 297 // Merge global localized strings. 298 _query2.default.extend(options.l10n, l10n); 299 300 var menu = new _navMenu2.default(el, options); 301 302 // Attempt to detect the partial id in the customizer and store a 303 // reference so it can be cleaned up during a selective refresh. 304 var partialId = menu.el.getAttribute('data-customize-partial-id'); 305 if (partialId) { 306 customizerPartials[partialId] = menu; 307 } 308 309 return menu; 310}; 311 312var _query = __webpack_require__(0); 313 314var _query2 = _interopRequireDefault(_query); 315 316var _navMenu = __webpack_require__(6); 317 318var _navMenu2 = _interopRequireDefault(_navMenu); 319 320var _wp = __webpack_require__(7); 321 322var _wp2 = _interopRequireDefault(_wp); 323 324function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 325 326var customizerPartials = {}; 327 328// Merge global localized strings. 329var l10n = _query2.default.extend({ 330 collapseSubmenu: 'Collapse submenu', 331 expandSubmenu: 'Expand submenu' 332}, global._cedaroNavMenuL10n); 333 334/** 335 * Initialize a partial placement in the customizer. 336 * 337 * Managing menus in the customizer causes the placement to be replaced for 338 * each change, so it needs to be reinitialized each time to ensure submenus 339 * are accessible. 340 * 341 * @param {wp.customize.selectiveRefresh.Placement} placement Partial placement. 342 */ 343function initializeCustomizePlacement(placement) { 344 if (!(placement.partial.id in customizerPartials)) { 345 return; 346 } 347 348 var removedMenu = customizerPartials[placement.partial.id]; 349 removedMenu.destroy(); 350 351 // Initialize the new placement. 352 var addedMenu = new _navMenu2.default(placement.container[0], removedMenu.options); 353 addedMenu.initialize(); 354 355 // Synchronize expanded states from the removed menu. 356 placement.removedNodes.find('.' + removedMenu.options.expandedMenuItemClass).each(function () { 357 placement.container.find('#' + this.id).find('> .{class}, > a .{class}'.replace(/{class}/g, removedMenu.options.submenuToggleClass)).trigger('click'); 358 }); 359} 360 361/** 362 * Selective refresh support in the customizer. 363 */ 364if (_wp2.default && 'customize' in _wp2.default) { 365 _wp2.default.customize.bind('preview-ready', function () { 366 _wp2.default.customize.selectiveRefresh.bind('partial-content-rendered', initializeCustomizePlacement); 367 }); 368} 369 370/** 371 * Create a new menu object. 372 * 373 * @param {string|Element} el Selector or HTML element. 374 * @param {object} options Menu options. 375 * @return {NavMenu} Nav menu instance. 376 */ 377/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(4))) 378 379/***/ }), 380/* 4 */
381/***/ (function(module, exports) { 382 383var g; 384 385// This works in non-strict mode 386g = (function() { 387 return this; 388})(); 389 390try { 391 // This works if eval is allowed (see CSP) 392 g = g || Function("return this")() || (1, eval)("this"); 393} catch (e) { 394 // This works if the window reference is available 395 if (typeof window === "object") g = window; 396} 397 398// g can still be undefined, but nothing to do about it... 399// We return undefined, instead of nothing here, so it's 400// easier to handle this case. if(!global) { ...} 401 402module.exports = g; 403 404 405/***/ }), 406/* 5 */ 407/***/ (function(module, exports, __webpack_require__) { 408 409"use strict"; 410 411 412Object.defineProperty(exports, "__esModule", { 413 value: true 414}); 415function each(items, callback) { 416 var length = items.length; 417 418 for (var i = 0; i < length; i++) { 419 if (false === callback.call(items[i], items[i], i)) { 420 break; 421 } 422 } 423} 424 425function extend(target) { 426 var length = arguments.length; 427 var i = 0; 428 429 target = target || {}; 430 431 while (++i < length) { 432 if (!arguments[i]) { 433 continue; 434 } 435 436 for (var key in arguments[i]) { 437 if (arguments[i].hasOwnProperty(key)) { 438 target[key] = arguments[i][key]; 439 } 440 } 441 } 442 443 return target; 444} 445 446function generateUniqueId() { 447 var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 448 449 if ('' !== prefix) { 450 prefix += '-'; 451 } 452 453 return prefix + Math.random().toString(36).slice(2, 15); 454} 455 456function getUniqueId(el, prefix) { 457 if (el.id) { 458 return el.id; 459 } 460 461 return generateUniqueId(prefix); 462} 463 464/** 465 * @link https://developer.mozilla.org/en-US/docs/Web/API/Element/matches 466 */ 467function matches(el, selector) { 468 var fn = el && (el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector || el.oMatchesSelector); 469 470 return !!fn && fn.call(el, selector); 471} 472 473exports.default = { 474 each: each, 475 extend: extend, 476 getUid: getUniqueId, 477 matches: matches 478}; 479 480/***/ }), 481/* 6 */ 482/***/ (function(module, exports, __webpack_require__) { 483 484"use strict"; 485 486 487Object.defineProperty(exports, "__esModule", { 488 value: true 489}); 490 491var _createClass = 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, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 492 493var _query = __webpack_require__(0); 494 495var _query2 = _interopRequireDefault(_query); 496 497function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 498 499function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 500 501var NavMenu = function () { 502 /** 503 * Create a menu instance. 504 * 505 * @class NavMenu 506 * 507 * @param {Element|string} el HTML element or selector. 508 * @param {object} options Menu options. 509 * @param {string} options.activeMenuItemClass HTML class for active menu items. 510 * @param {number} options.breakpoint Breakpoint to switch between mobile and full site mode. 511 * @param {boolean} options.expandCurrentItem Whether to expand the current menu item on initial load. 512 * @param {string} options.expandedMenuItemClass HTML class applied to the menu item that contains an expanded submenu. 513 * @param {string} options.expandedSubmenuClass HTML class applied to expanded submenus. 514 * @param {string} options.expandedSubmenuToggleClass HTML class applied to submenu toggle buttons when the submenu is expanded. 515 * @param {number} options.hoverTimeout Delay in milliseconds before hiding submenus. 516 * @param {object} options.l10n Localized strings specific to this instance. 517 * @param {string} options.submenuToggleClass HTML class for submenu toggle buttons. 518 * @param {string} options.submenuToggleInsert Where to insert submenu toggle buttons. Defaults to before the submenu. 519 * @param {string} options.submenuToggleMode Mode for displaying submenus. 520 */ 521 function NavMenu(el, options) { 522 var _this = this; 523 524 _classCallCheck(this, NavMenu); 525 526 this._initialized = false;
527 this._hasExpandedSubmenu = false; 528 this._hoverTimeouts = []; 529 this._touchStarted = false; 530 this._viewportWidth = null; 531 532 this.el = (0, _query2.default)(el)[0]; 533 this.options = _query2.default.extend({}, NavMenu.defaults, options); 534 this.el.setAttribute('data-nav-menu-options', JSON.stringify(this.options)); 535 536 // Automatically bind all event handlers. 537 Object.getOwnPropertyNames(NavMenu.prototype).filter(function (key) { 538 return 'function' === typeof _this[key] && 0 === key.indexOf('on'); 539 }).forEach(function (key) { 540 _this[key] = _this[key].bind(_this); 541 }); 542 } 543 544 /** 545 * Initialize the menu. 546 * 547 * @return {this} 548 */ 549 550 551 _createClass(NavMenu, [{ 552 key: 'initialize', 553 value: function initialize() { 554 var _this2 = this; 555 556 if (this._initialized) { 557 return; 558 } 559 560 this._initialized = true; 561 562 // Ensure every menu item has an id to manage the hover state timeouts. 563 this.$items = (0, _query2.default)('li', this.el).each(function (el) { 564 return el.id = _query2.default.getUid(el, 'menu-item'); 565 }); 566 567 this.$submenus = (0, _query2.default)('li ul', this.el).each(function (el) { 568 return el.setAttribute('aria-expanded', 'false'); 569 }); 570 571 if (false !== this.options.submenuToggleInsert) { 572 this.insertSubmenuToggleButtons(); 573 } 574 575 document.addEventListener('click', this.onDocumentClick, true); 576 this.el.addEventListener('click', this.onSubmenuToggleClick, false); 577 578 if ('hover' === this.options.submenuToggleMode /*&& 'disable' !== this.options.breakpoint*/) { 579 this.el.addEventListener('touchstart', this.onMenuLinkTouchStart, false); 580 this.el.addEventListener('mouseover', this.onMenuItemMouseEnter, false); 581 this.el.addEventListener('mouseout', this.onMenuItemMouseLeave, false); 582 } 583 584 // Use capturing mode to support event delegation. 585 // @link https://developer.mozilla.org/en-US/docs/Web/Events/blur#Event_delegation 586 this.el.addEventListener('focus', this.onMenuLinkFocus, true); 587 this.el.addEventListener('blur', this.onMenuLinkBlur, true); 588 589 // Expand current submenus on initialization. 590 if (this.options.expandCurrentItem && ('click' === this.options.submenuToggleMode || this.isMobile())) { 591 this.$items.filter(function (el) { 592 return el.classList.contains('current-menu-item') || el.classList.contains('current-menu-ancestor'); 593 }).each(function (el) { 594 return _this2.expandSubmenu(el); 595 }); 596 } 597 598 return this; 599 } 600 601 /** 602 * Destroy the menu. 603 * 604 * Cleans up delegated event listeners. 605 * 606 * @todo Consider removing toggle buttons. 607 */ 608 609 }, { 610 key: 'destroy', 611 value: function destroy() { 612 document.removeEventListener('click', this.onDocumentClick, true); 613 this.el.removeEventListener('click', this.onSubmenuToggleClick, false); 614 this.el.removeEventListener('mouseover', this.onMenuItemMouseEnter, false); 615 this.el.removeEventListener('mouseout', this.onMenuItemMouseLeave, false); 616 this.el.removeEventListener('focus', this.onMenuLinkFocus, true); 617 this.el.removeEventListener('blur', this.onMenuLinkBlur, true); 618 } 619 620 /** 621 * Whether the menu is in mobile mode. 622 * 623 * @return {boolean} 624 */ 625 626 }, { 627 key: 'isMobile', 628 value: function isMobile() { 629 if ('disable' === this.options.breakpoint) { 630 return true; 631 } 632 633 return this.getViewportWidth() < this.options.breakpoint; 634 } 635 636 /** 637 * Collapse the submenu in a menu item. 638 * 639 * @param {Element} menuItem Menu item element. 640 */ 641 642 }, { 643 key: 'collapseSubmenu', 644 value: function collapseSubmenu(menuItem) { 645 var button = this.getSubmenuToggle(menuItem); 646 var $submenu = (0, _query2.default)('.sub-menu', menuItem); 647 648 menuItem.classList.remove(this.options.expandedMenuItemClass); 649 650 if (button) { 651 button.classList.remove(this.options.expandedSubmenuToggleClass); 652 button.setAttribute('aria-expanded', 'false'); 653 (0, _query2.default)('.screen-reader-text', button)[0].innerHTML = this.options.l10n.expandSubmenu || 'Expand submenu'; 654 } 655 656 if ($submenu.length) { 657 $submenu[0].classList.remove(this.options.expandedSubmenuClass); 658 $submenu[0].setAttribute('aria-expanded', 'false'); 659 } 660 } 661 662 /** 663 * Collapse all submenus. 664 */ 665 666 }, { 667 key: 'collapseAllSubmenus', 668 value: function collapseAllSubmenus() { 669 var _this3 = this; 670 671 this.$items.each(function (el) { 672 el.classList.remove(_this3.options.activeMenuItemClass); 673 _this3.collapseSubmenu(el); 674 }); 675 } 676 677 /** 678 * Collapse submenus in branches that don't contain the focused element. 679 * 680 * @param {Element} focusedEl Element that has focus. 681 */ 682 683 }, { 684 key: 'collapseUnrelatedSubmenus', 685 value: function collapseUnrelatedSubmenus(focusedEl) { 686 var _this4 = this; 687 688 this.$items.each(function (el) { 689 if (!el.contains(focusedEl)) { 690 el.classList.remove(_this4.options.activeMenuItemClass); 691 _this4.collapseSubmenu(el); 692 } 693 }); 694 } 695 696 /** 697 * Expand the submenu in a menu item. 698 * 699 * @param {Element} menuItem Menu item element. 700 */ 701 702 }, { 703 key: 'expandSubmenu', 704 value: function expandSubmenu(menuItem) {
705 var button = this.getSubmenuToggle(menuItem); 706 var $submenu = (0, _query2.default)('.sub-menu', menuItem); 707 708 menuItem.classList.add(this.options.activeMenuItemClass); 709 710 if (!this.hasSubmenu(menuItem)) { 711 return; 712 } 713 714 menuItem.classList.add(this.options.expandedMenuItemClass); 715 716 if (button) { 717 button.classList.add(this.options.expandedSubmenuToggleClass); 718 button.setAttribute('aria-expanded', 'true'); 719 (0, _query2.default)('.screen-reader-text', button)[0].innerHTML = this.options.l10n.collapseSubmenu || 'Collapse submenu'; 720 } 721 722 if ($submenu.length) { 723 $submenu[0].classList.add(this.options.expandedSubmenuClass); 724 $submenu[0].setAttribute('aria-expanded', 'true'); 725 } 726 } 727 728 /** 729 * Retrieve the viewport width. 730 * 731 * @return {number} 732 */ 733 734 }, { 735 key: 'getViewportWidth', 736 value: function getViewportWidth() { 737 return this._viewportWidth || Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 738 } 739 740 /** 741 * Set the viewport width. 742 * 743 * @param {number} width Width of the viewport. 744 * @return {this} 745 */ 746 747 }, { 748 key: 'setViewportWidth', 749 value: function setViewportWidth(width) { 750 this._viewportWidth = parseInt(width, 10); 751 return this; 752 } 753 754 /** 755 * Retrieve the submenu toggle button from a menu item. 756 * 757 * @param {Element} menuItem Menu item element. 758 * @return {Element} 759 */ 760 761 }, { 762 key: 'getSubmenuToggle', 763 value: function getSubmenuToggle(menuItem) { 764 return (0, _query2.default)('.' + this.options.submenuToggleClass, menuItem)[0]; 765 } 766 767 /** 768 * Whether a menu item has a submenu. 769 * 770 * WordPress adds the 'menu-item-has-children' class to menu items even when 771 * the children are hidden with a depth restriction, so we have to query for 772 * the submenu to see if it exists. 773 * 774 * @param {Element} menuItem Menu item element. 775 * @return {Boolean} 776 */ 777 778 }, { 779 key: 'hasSubmenu', 780 value: function hasSubmenu(menuItem) { 781 return (0, _query2.default)('.sub-menu', menuItem).length > 0; //classList.contains( 'menu-item-has-children' ); 782 } 783 784 /** 785 * Insert toggle buttons in menu items that contain a submenu. 786 */ 787 788 }, { 789 key: 'insertSubmenuToggleButtons', 790 value: function insertSubmenuToggleButtons() { 791 var _this5 = this; 792 793 var button = document.createElement('button'); 794 button.setAttribute('class', this.options.submenuToggleClass); 795 button.setAttribute('aria-expanded', 'false'); 796 797 var span = document.createElement('span'); 798 span.setAttribute('class', 'screen-reader-text'); 799 span.innerHTML = this.options.l10n.expandSubmenu || 'Expand submenu'; 800 801 button.appendChild(span); 802 803 this.$submenus.each(function (submenu) { 804 var buttonInstance = button.cloneNode(true); 805 806 // Assign an id to each submenu to use in ARIA attributes. 807 if (!submenu.id) { 808 submenu.id = _query2.default.getUid(submenu, 'sub-menu'); 809 } 810 811 buttonInstance.setAttribute('aria-controls', submenu.id); 812 813 if ('append' !== _this5.options.submenuToggleInsert) { 814 // Insert the toggle button before the submenu element. 815 submenu.parentElement.insertBefore(buttonInstance, submenu); 816 return; 817 } 818 819 // Append the toggle button to the parent item anchor element. 820 // This should primarily be used for backward compatibility. 821 (0, _query2.default)(submenu.parentElement.children).each(function (el) { 822 if ('A' === el.nodeName) { 823 el.appendChild(buttonInstance); 824 return false; 825 } 826 }); 827 }); 828 } 829 }, { 830 key: 'isSubmenuExpanded', 831 value: function isSubmenuExpanded(menuItem) { 832 return menuItem.classList.contains(this.options.expandedMenuItemClass); 833 } 834 835 /** 836 * Toggle the submenu in a menu item. 837 * 838 * @param {Element} menuItem Menu item element. 839 */ 840 841 }, { 842 key: 'toggleSubmenu', 843 value: function toggleSubmenu(menuItem) { 844 if (this.isSubmenuExpanded(menuItem)) { 845 this.collapseSubmenu(menuItem); 846 } else { 847 this.expandSubmenu(menuItem); 848 } 849 } 850 851 /** 852 * Collapse all submenus when clicking outside the menu. 853 *
854 * On touch devices without toggle buttons, tapping a link will expand 855 * the submenu. It's not possible to close it without clicking another link 856 * with a submenu, which leads back to the initial problem. 857 * 858 * Submenus also won't be collapsed when the keyboard is used to focus 859 * an element and a click occurs outside the menu. 860 */ 861 862 }, { 863 key: 'onDocumentClick', 864 value: function onDocumentClick(e) { 865 // Don't do this in click mode or on mobile. 866 if ('click' === this.options.submenuToggleMode || this.isMobile()) { 867 return; 868 } 869 870 if (this.el === e.target || !this.el.contains(e.target)) { 871 this.collapseAllSubmenus(); 872 } 873 } 874 875 /** 876 * Handle clicks on submenu toggle buttons. 877 * 878 * Delegated to the menu element, so need to ensure the click is on a 879 * toggle button before handling it. 880 * 881 * @param {MouseEvent} e Mouse event object. 882 */ 883 884 }, { 885 key: 'onSubmenuToggleClick', 886 value: function onSubmenuToggleClick(e) { 887 var button = (0, _query2.default)(e.target).closest('.' + this.options.submenuToggleClass, this.el); 888 889 // Bail if this isn't a click on a toggle button. 890 if (!button) { 891 return; 892 } 893 894 e.preventDefault(); 895 896 var menuItem = (0, _query2.default)(e.target).closest('li', this.el); 897 this.toggleSubmenu(menuItem); 898 } 899 900 /** 901 * Handle touchstart events on menu links. 902 * 903 * On touch devices, make the first click open a submenu if there isn't a 904 * clickable toggle button available. Otherwise, the submenus wouldn't be 905 * accessible on touch devices. 906 * 907 * @param {TouchEvent} e Touch event object. 908 */ 909 910 }, { 911 key: 'onMenuLinkTouchStart', 912 value: function onMenuLinkTouchStart(e) { 913 // Bail if this isn't a touchstart on the anchor element. 914 if (!_query2.default.isElementOrDescendant(e.target, 'A', this.el)) { 915 return; 916 } 917 918 var target = (0, _query2.default)(e.target).closest('li', this.el); 919 920 // Bail if a target couldn't be found. 921 if (!target) { 922 return; 923 } 924 925 // Bail if there isn't a submenu in this item or it's already expanded. 926 if (!this.hasSubmenu(target) || this.isSubmenuExpanded(target)) { 927 return; 928 } 929 930 var button = this.getSubmenuToggle(target); 931 932 // Expand the submenu and disable the click event if a clickable toggle 933 // button isn't available. 934 if (!_query2.default.isClickable(button)) { 935 e.stopImmediatePropagation(); 936 e.preventDefault(); 937 this.expandSubmenu(target); 938 this.collapseUnrelatedSubmenus(e.target); 939 } else { 940 this._touchStarted = true; 941 } 942 } 943 944 /** 945 * Handle mouseenter events on menu items. 946 * 947 * When the mouse enters a menu item: 948 * - Add an 'is-active' class to the menu item to aid in styling. Using 949 * the class should prevent needing to use :hover, which can cause 950 * double tap issues on touch devices. 951 * - Expand the submenu if not on mobile. The toggle button should be 952 * used to control submenus on mobile. 953 * 954 * Mimics mouseenter, but is delegated to the menu element instead of 955 * being bound to each item. The actual mouseenter event cannot be 956 * delegated. 957 * 958 * @link https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter 959 * @link http://stackoverflow.com/a/22444819 960 * @link https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW7 961 * @link https://patrickhlauke.github.io/touch/tests/results/ 962 * 963 * @param {MouseEvent} e Mouse event object. 964 */ 965 966 }, { 967 key: 'onMenuItemMouseEnter', 968 value: function onMenuItemMouseEnter(e) { 969 var _this6 = this; 970 971 var isTouchEvent = this._touchStarted; 972 973 // Reset the touch started flag. 974 this._isTouchStarted = false; 975 976 var target = (0, _query2.default)(e.target).closest('li', this.el); 977 var related = (0, _query2.default)(e.relatedTarget).closest('li', this.el); 978 979 /* 980 * Bail if a target couldn't be found, or if the cursor is already in 981 * the target element. Handling mouseover events like this mimics 982 * mousenter behavior. 983 */ 984 if (!target || related === target) { 985 return; 986 } 987 988 // Clear the timeout to hide a submenu when re-entering an item. 989 (0, _query2.default)(e.target).up('li', this.el).each(function (el) {
990 clearTimeout(_this6._hoverTimeouts[el.id]); 991 el.classList.add(_this6.options.activeMenuItemClass); 992 }); 993 994 // Bail if there isn't a submenu in this item or it's already expanded. 995 if (!this.hasSubmenu(target) || this.isSubmenuExpanded(target)) { 996 return; 997 } 998 999 /* 1000 * If a submenu has been expanded by a focus event, hovering over 1001 * unrelated menu items won't collapse the submenu. This should only 1002 * happen when the keyboard is used to focus a link, then the mouse is 1003 * used to interact with the menu. It's an edge case an may not be worth 1004 * the performance impact of looping through every item on mouseenter to 1005 * collapse unrelated submenus. 1006 */ 1007 // if ( ! this.isMobile() ) { 1008 // this.collapseUnrelatedSubmenus( target ); 1009 // } 1010 1011 /* 1012 * If this event was triggered by a touchstart event and the target is 1013 * an anchor element, don't open the submenu to prevent it from flashing 1014 * before the link is followed. Also prevents a double tap from being 1015 * required on iOS. 1016 */ 1017 if (!isTouchEvent || !_query2.default.isElementOrDescendant(e.target, 'A', this.el)) { 1018 // Don't expand on mobile to prevent the menu from jumping. 1019 if (!this.isMobile()) { 1020 this.expandSubmenu(target); 1021 } 1022 } 1023 } 1024 1025 /** 1026 * Handle mouseleave events on menu items. 1027 * 1028 * When the mouse leaves a menu item, collapse submenus and remove the 1029 * 'is-active' class for any menu items that no longer have the cursor 1030 * hovering over them. 1031 * 1032 * Mimics mouseleave, but is delegated to the menu element instead of 1033 * being bound to each item. 1034 * 1035 * @link https://developer.mozilla.org/en-US/docs/Web/Events/mouseleave 1036 * 1037 * @param {MouseEvent} e Moust event object. 1038 */ 1039 1040 }, { 1041 key: 'onMenuItemMouseLeave', 1042 value: function onMenuItemMouseLeave(e) { 1043 var _this7 = this; 1044 1045 var isMobile = this.isMobile(); 1046 1047 (0, _query2.default)(e.target).up('li', this.el).filter(function (el) { 1048 return el !== e.relatedTarget && !el.contains(e.relatedTarget); 1049 }).each(function (el) { 1050 el.classList.remove(_this7.options.activeMenuItemClass); 1051 1052 // Bail in mobile mode to prevent the menu from jumping. 1053 if (isMobile) { 1054 return; 1055 } 1056 1057 _this7._hoverTimeouts[el.id] = setTimeout(function () { 1058 _this7.collapseSubmenu(el); 1059 }, _this7.options.hoverTimeout); 1060 }); 1061 } 1062 1063 /** 1064 * Handle the focus event on elements within the menu. 1065 * 1066 * Aids keyboard navigation by: 1067 * - Collapsing submenus in unrelated items to prevent submenus from 1068 * overlapping in full screen mode. This is not done in mobile mode to 1069 * prevent the menu from jumping when tabbing through items. 1070 * - Adding an 'is-active' class to ancestor menu items when any 1071 * descendant is focused. 1072 * - Expanding a submenu when an element (link or toggle button) in the 1073 * parent menu item receives focus. 1074 * 1075 * @param {FocusEvent} e Focus event object. 1076 */ 1077 1078 }, { 1079 key: 'onMenuLinkFocus', 1080 value: function onMenuLinkFocus(e) { 1081 var _this8 = this; 1082 1083 // Collapse unrelated submenus except in mobile mode. 1084 if ('click' !== this.options.submenuToggleMode && !this.isMobile()) { 1085 this.collapseUnrelatedSubmenus(e.target); 1086 } 1087 1088 var $parents = (0, _query2.default)(e.target).parents('li', this.el).each(function (el) { 1089 el.classList.add(_this8.options.activeMenuItemClass); 1090 }); 1091 1092 // Bail if the target menu item doesn't have a submenu. 1093 if (!this.hasSubmenu($parents[0])) { 1094 return; 1095 } 1096 1097 var button = this.getSubmenuToggle($parents[0]); 1098 1099 /* 1100 * Expand the submenu if a toggle button isn't visible. 1101 * 1102 * Also expands parents since keyboards can access elements positioned 1103 * off-screen, so reverse tabbing through a menu needs to make sure 1104 * parents are visible. 1105 */ 1106 if (!button || !_query2.default.isVisible(button)) { 1107 $parents.each(function (el) { 1108 return _this8.expandSubmenu(el); 1109 }); 1110 } 1111 } 1112 1113 /** 1114 * Close submenus when focus leaves the containing item. 1115 * 1116 * Aids keyboard navigation in full screen mode by preventing multiple 1117 * expanded submenus from overlapping when tabbing through items. 1118 * 1119 * The relatedTarget property isn't available in some browsers for focus 1120 * events, so it's not possible to grab the focused element in a blur 1121 * event handler. For that reason, unrelated submenus are also collapsed in 1122 * onMenuLinkFocus. 1123 * 1124 * @link https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/relatedTarget 1125 * 1126 * @todo Blur events sent when clicking outside the element the event is 1127 * bound to also send null for the relatedTarget. This prevents 1128 * clicking anywhere else in the document from collapsing the submenu. 1129 * 1130 * @param {FocusEvent} e Focus event object. 1131 */ 1132 1133 }, { 1134 key: 'onMenuLinkBlur', 1135 value: function onMenuLinkBlur(e) { 1136 var _this9 = this; 1137 1138 // Remove the activeMenuItemClass from the parent menu items. 1139 (0, _query2.default)(e.target).parents('li', this.el).each(function (el) { 1140 el.classList.remove(_this9.options.activeMenuItemClass); 1141 }); 1142 1143 // Bail if the relatedTarget event is null. 1144 if (!e.relatedTarget) { 1145 return; 1146 } 1147 1148 // Disable on mobile to prevent the menu from jumping while tabbing 1149 // through menu items and toggle buttons. 1150 if ('click' === this.options.submenuToggleMode || this.isMobile()) { 1151 return; 1152 } 1153 1154 this.collapseUnrelatedSubmenus(e.relatedTarget); 1155 } 1156 }]); 1157
1158 return NavMenu; 1159}(); 1160 1161NavMenu.defaults = { 1162 breakpoint: 768, // disable 1163 expandCurrentItem: false, 1164 hoverTimeout: 50, 1165 l10n: {}, 1166 submenuToggleInsert: '', // default|append|false 1167 submenuToggleMode: 'hover', // hover|click; @todo hover-tap? 1168 1169 // Configurable HTML classes. 1170 activeMenuItemClass: 'is-active', 1171 expandedMenuItemClass: 'is-sub-menu-open', 1172 expandedSubmenuClass: 'is-open', 1173 expandedSubmenuToggleClass: 'is-open', 1174 submenuToggleClass: 'sub-menu-toggle' 1175}; 1176 1177exports.default = NavMenu; 1178 1179/***/ }), 1180/* 7 */ 1181/***/ (function(module, exports) { 1182 1183(function() { module.exports = this["wp"]; }()); 1184 1185/***/ }) 1186/******/ ]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.