diff --git a/Gruntfile.js b/Gruntfile.js index ffc660958956431481bdac1ba04ca7d6723dbcda..5c29acf039fbc68cafc8103d4eb16ab58ca4cdb3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -63,11 +63,12 @@ module.exports = function (grunt) { }, dist: { files: { - 'js/dist/util.js': 'js/src/util.js', - 'js/dist/alert.js': 'js/src/alert.js', - 'js/dist/button.js': 'js/src/button.js', - 'js/dist/carousel.js': 'js/src/carousel.js', - 'js/dist/collapse.js': 'js/src/collapse.js', + 'js/dist/util.js' : 'js/src/util.js', + 'js/dist/alert.js' : 'js/src/alert.js', + 'js/dist/button.js' : 'js/src/button.js', + 'js/dist/carousel.js' : 'js/src/carousel.js', + 'js/dist/collapse.js' : 'js/src/collapse.js', + 'js/dist/dropdown.js' : 'js/src/dropdown.js' } } }, diff --git a/js/dist/dropdown.js b/js/dist/dropdown.js new file mode 100644 index 0000000000000000000000000000000000000000..a22e3ce39d13657953ab4f2cb439d6ef95d5fe4d --- /dev/null +++ b/js/dist/dropdown.js @@ -0,0 +1,254 @@ +'use strict'; + +var _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; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +var Dropdown = (function ($) { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME = 'dropdown'; + var VERSION = '4.0.0'; + var DATA_KEY = 'bs.dropdown'; + var JQUERY_NO_CONFLICT = $.fn[NAME]; + + var Event = { + HIDE: 'hide.bs.dropdown', + HIDDEN: 'hidden.bs.dropdown', + SHOW: 'show.bs.dropdown', + SHOWN: 'shown.bs.dropdown', + CLICK: 'click.bs.dropdown', + KEYDOWN: 'keydown.bs.dropdown.data-api', + CLICK_DATA: 'click.bs.dropdown.data-api' + }; + + var ClassName = { + BACKDROP: 'dropdown-backdrop', + DISABLED: 'disabled', + OPEN: 'open' + }; + + var Selector = { + BACKDROP: '.dropdown-backdrop', + DATA_TOGGLE: '[data-toggle="dropdown"]', + FORM_CHILD: '.dropdown form', + ROLE_MENU: '[role="menu"]', + ROLE_LISTBOX: '[role="listbox"]', + NAVBAR_NAV: '.navbar-nav', + VISIBLE_ITEMS: '[role="menu"] li:not(.disabled) a, ' + '[role="listbox"] li:not(.disabled) a' + }; + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var Dropdown = (function () { + function Dropdown(element) { + _classCallCheck(this, Dropdown); + + $(element).on(Event.CLICK, this.toggle); + } + + _createClass(Dropdown, [{ + key: 'toggle', + + // public + + value: function toggle() { + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return; + } + + var parent = Dropdown._getParentFromElement(this); + var isActive = $(parent).hasClass(ClassName.OPEN); + + Dropdown._clearMenus(); + + if (isActive) { + return false; + } + + if ('ontouchstart' in document.documentElement && !$(parent).closest(Selector.NAVBAR_NAV).length) { + + // if mobile we use a backdrop because click events don't delegate + var dropdown = document.createElement('div'); + dropdown.className = ClassName.BACKDROP; + $(dropdown).insertBefore(this); + $(dropdown).on('click', Dropdown._clearMenus); + } + + var relatedTarget = { 'relatedTarget': this }; + var showEvent = $.Event(Event.SHOW, relatedTarget); + + $(parent).trigger(showEvent); + + if (showEvent.isDefaultPrevented()) { + return; + } + + this.focus(); + this.setAttribute('aria-expanded', 'true'); + + $(parent).toggleClass(ClassName.OPEN); + $(parent).trigger(Event.SHOWN, relatedTarget); + + return false; + } + }], [{ + key: '_jQueryInterface', + + // static + + value: function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY); + + if (!data) { + $(this).data(DATA_KEY, data = new Dropdown(this)); + } + + if (typeof config === 'string') { + data[config].call(this); + } + }); + } + }, { + key: '_clearMenus', + value: function _clearMenus(event) { + if (event && event.which === 3) { + return; + } + + var backdrop = $(Selector.BACKDROP)[0]; + if (backdrop) { + backdrop.parentNode.removeChild(backdrop); + } + + var toggles = $.makeArray($(Selector.DATA_TOGGLE)); + + for (var i = 0; i < toggles.length; i++) { + var _parent = Dropdown._getParentFromElement(toggles[i]); + var relatedTarget = { 'relatedTarget': toggles[i] }; + + if (!$(_parent).hasClass(ClassName.OPEN)) { + continue; + } + + if (event && event.type === 'click' && /input|textarea/i.test(event.target.tagName) && $.contains(_parent, event.target)) { + continue; + } + + var hideEvent = $.Event(Event.HIDE, relatedTarget); + $(_parent).trigger(hideEvent); + if (hideEvent.isDefaultPrevented()) { + continue; + } + + toggles[i].setAttribute('aria-expanded', 'false'); + + $(_parent).removeClass(ClassName.OPEN).trigger(Event.HIDDEN, relatedTarget); + } + } + }, { + key: '_getParentFromElement', + value: function _getParentFromElement(element) { + var parent = undefined; + var selector = Util.getSelectorFromElement(element); + + if (selector) { + parent = $(selector)[0]; + } + + return parent || element.parentNode; + } + }, { + key: '_dataApiKeydownHandler', + value: function _dataApiKeydownHandler(event) { + if (!/(38|40|27|32)/.test(event.which) || /input|textarea/i.test(event.target.tagName)) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return; + } + + var parent = Dropdown._getParentFromElement(this); + var isActive = $(parent).hasClass(ClassName.OPEN); + + if (!isActive && event.which !== 27 || isActive && event.which === 27) { + + if (event.which === 27) { + var toggle = $(parent).find(Selector.DATA_TOGGLE)[0]; + $(toggle).trigger('focus'); + } + + $(this).trigger('click'); + return; + } + + var items = $.makeArray($(Selector.VISIBLE_ITEMS)); + + items = items.filter(function (item) { + return item.offsetWidth || item.offsetHeight; + }); + + if (!items.length) { + return; + } + + var index = items.indexOf(event.target); + + if (event.which === 38 && index > 0) index--; // up + if (event.which === 40 && index < items.length - 1) index++; // down + if (! ~index) index = 0; + + items[index].focus(); + } + }]); + + return Dropdown; + })(); + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document).on(Event.KEYDOWN, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event.KEYDOWN, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler).on(Event.KEYDOWN, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler).on(Event.CLICK_DATA, Dropdown._clearMenus).on(Event.CLICK_DATA, Selector.DATA_TOGGLE, Dropdown.prototype.toggle).on(Event.CLICK_DATA, Selector.FORM_CHILD, function (e) { + e.stopPropagation(); + }); + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Dropdown._jQueryInterface; + $.fn[NAME].Constructor = Dropdown; + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return Dropdown._jQueryInterface; + }; + + return Dropdown; +})(jQuery); +//# sourceMappingURL=dropdown.js.map \ No newline at end of file diff --git a/js/dist/dropdown.js.map b/js/dist/dropdown.js.map new file mode 100644 index 0000000000000000000000000000000000000000..16e6332a8d5aad9cb991c77a58e1baa68da99a53 Binary files /dev/null and b/js/dist/dropdown.js.map differ diff --git a/js/src/dropdown.js b/js/src/dropdown.js new file mode 100644 index 0000000000000000000000000000000000000000..c5e29d8f1de306879af5e6d4adf48ef4d2c4fb10 --- /dev/null +++ b/js/src/dropdown.js @@ -0,0 +1,261 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Dropdown = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'dropdown' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.dropdown' + const JQUERY_NO_CONFLICT = $.fn[NAME] + + const Event = { + HIDE Â : 'hide.bs.dropdown', + HIDDEN Â : 'hidden.bs.dropdown', + SHOW Â : 'show.bs.dropdown', + SHOWN Â : 'shown.bs.dropdown', + CLICK Â : 'click.bs.dropdown', + KEYDOWN Â : 'keydown.bs.dropdown.data-api', + CLICK_DATA : 'click.bs.dropdown.data-api' + } + + const ClassName = { + BACKDROP : 'dropdown-backdrop', + DISABLED : 'disabled', + OPEN : 'open' + } + + const Selector = { + BACKDROP : '.dropdown-backdrop', + DATA_TOGGLE : '[data-toggle="dropdown"]', + FORM_CHILD : '.dropdown form', + ROLE_MENU : '[role="menu"]', + ROLE_LISTBOX : '[role="listbox"]', + NAVBAR_NAV : '.navbar-nav', + VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, ' + + '[role="listbox"] li:not(.disabled) a' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Dropdown { + + constructor(element) { + $(element).on(Event.CLICK, this.toggle) + } + + // public + + toggle() { + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return + } + + let parent = Dropdown._getParentFromElement(this) + let isActive = $(parent).hasClass(ClassName.OPEN) + + Dropdown._clearMenus() + + if (isActive) { + return false + } + + if ('ontouchstart' in document.documentElement && + (!$(parent).closest(Selector.NAVBAR_NAV).length)) { + + // if mobile we use a backdrop because click events don't delegate + let dropdown = document.createElement('div') + dropdown.className = ClassName.BACKDROP + $(dropdown).insertBefore(this) + $(dropdown).on('click', Dropdown._clearMenus) + } + + let relatedTarget = { relatedTarget : this } + let showEvent = $.Event(Event.SHOW, relatedTarget) + + $(parent).trigger(showEvent) + + if (showEvent.isDefaultPrevented()) { + return + } + + this.focus() + this.setAttribute('aria-expanded', 'true') + + $(parent).toggleClass(ClassName.OPEN) + $(parent).trigger(Event.SHOWN, relatedTarget) + + return false + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + + if (!data) { + $(this).data(DATA_KEY, (data = new Dropdown(this))) + } + + if (typeof config === 'string') { + data[config].call(this) + } + }) + } + + static _clearMenus(event) { + if (event && event.which === 3) { + return + } + + let backdrop = $(Selector.BACKDROP)[0] + if (backdrop) { + backdrop.parentNode.removeChild(backdrop) + } + + let toggles = $.makeArray($(Selector.DATA_TOGGLE)) + + for (let i = 0; i < toggles.length; i++) { + let parent = Dropdown._getParentFromElement(toggles[i]) + let relatedTarget = { relatedTarget : toggles[i] } + + if (!$(parent).hasClass(ClassName.OPEN)) { + continue + } + + if (event && event.type === 'click' && + (/input|textarea/i.test(event.target.tagName)) && + ($.contains(parent, event.target))) { + continue + } + + let hideEvent = $.Event(Event.HIDE, relatedTarget) + $(parent).trigger(hideEvent) + if (hideEvent.isDefaultPrevented()) { + continue + } + + toggles[i].setAttribute('aria-expanded', 'false') + + $(parent) + .removeClass(ClassName.OPEN) + .trigger(Event.HIDDEN, relatedTarget) + } + } + + static _getParentFromElement(element) { + let parent + let selector = Util.getSelectorFromElement(element) + + if (selector) { + parent = $(selector)[0] + } + + return parent || element.parentNode + } + + static _dataApiKeydownHandler(event) { + if (!/(38|40|27|32)/.test(event.which) || + /input|textarea/i.test(event.target.tagName)) { + return + } + + event.preventDefault() + event.stopPropagation() + + if (this.disabled || $(this).hasClass(ClassName.DISABLED)) { + return + } + + let parent = Dropdown._getParentFromElement(this) + let isActive = $(parent).hasClass(ClassName.OPEN) + + if ((!isActive && event.which !== 27) || + (isActive && event.which === 27)) { + + if (event.which === 27) { + let toggle = $(parent).find(Selector.DATA_TOGGLE)[0] + $(toggle).trigger('focus') + } + + $(this).trigger('click') + return + } + + let items = $.makeArray($(Selector.VISIBLE_ITEMS)) + + items = items.filter((item) => { + return item.offsetWidth || item.offsetHeight + }) + + if (!items.length) { + return + } + + let index = items.indexOf(event.target) + + if (event.which === 38 && index > 0) index-- // up + if (event.which === 40 && index < items.length - 1) index++ // down + if (!~index) index = 0 + + items[index].focus() + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document) + .on(Event.KEYDOWN, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) + .on(Event.KEYDOWN, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) + .on(Event.CLICK_DATA, Dropdown._clearMenus) + .on(Event.CLICK_DATA, Selector.DATA_TOGGLE, Dropdown.prototype.toggle) + .on(Event.CLICK_DATA, Selector.FORM_CHILD, function (e) { + e.stopPropagation() + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Dropdown._jQueryInterface + $.fn[NAME].Constructor = Dropdown + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Dropdown._jQueryInterface + } + + return Dropdown + +})(jQuery) + +export default Dropdown diff --git a/js/tests/index.html b/js/tests/index.html index cc008152fbf95ce02ff6fa120421e1393712c62b..fa25ed883f83f41063994445b82044b6f9e1efa7 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -135,9 +135,9 @@ <script src="../../js/dist/button.js"></script> <script src="../../js/dist/carousel.js"></script> <script src="../../js/dist/collapse.js"></script> + <script src="../../js/dist/dropdown.js"></script> <!-- Old Plugin sources --> - <script src="../../js/dropdown.js"></script> <script src="../../js/modal.js"></script> <script src="../../js/scrollspy.js"></script> <script src="../../js/tab.js"></script> diff --git a/js/tests/visual/dropdown.html b/js/tests/visual/dropdown.html index 02090b6cc9c58026a95254f7f41b05fdf184d42a..a51267de8444112bbadec2c3621391cb72e7afe4 100644 --- a/js/tests/visual/dropdown.html +++ b/js/tests/visual/dropdown.html @@ -22,21 +22,15 @@ <h1>Dropdown <small>Bootstrap Visual Test</small></h1> </div> - <nav id="navbar-example" class="navbar navbar-default navbar-static" role="navigation"> + <nav id="navbar-example" class="navbar navbar-default navbar-static-top" role="navigation"> <div class="container-fluid"> - <div class="navbar-header"> - <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target=".bs-example-js-navbar-collapse"> - <span class="sr-only">Toggle navigation</span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - </button> - <a class="navbar-brand" href="#">Project Name</a> - </div> - <div class="collapse navbar-collapse bs-example-js-navbar-collapse"> - <ul class="nav navbar-nav"> - <li class="dropdown"> - <a id="drop1" href="#" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a> + <button class="navbar-toggler hidden-sm-up" type="button" data-toggle="collapse" data-target="#exCollapsingNavbar2"> + ☰ + </button> + <div class="collapse navbar-toggleable-xs" id="exCollapsingNavbar2"> + <ul class="nav navbar-nav pull-left"> + <li class="dropdown nav-item"> + <a id="drop1" href="#" role="button" class="dropdown-toggle nav-link" data-toggle="dropdown">Dropdown <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop1"> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> @@ -45,8 +39,8 @@ <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> - <li class="dropdown"> - <a href="#" id="drop2" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown 2 <b class="caret"></b></a> + <li class="dropdown nav-item"> + <a href="#" id="drop2" role="button" class="dropdown-toggle nav-link" data-toggle="dropdown">Dropdown 2 <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop2"> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> @@ -56,9 +50,9 @@ </ul> </li> </ul> - <ul class="nav navbar-nav navbar-right"> - <li id="fat-menu" class="dropdown"> - <a href="#" id="drop3" role="button" class="dropdown-toggle" data-toggle="dropdown">Dropdown 3 <b class="caret"></b></a> + <ul class="nav navbar-nav pull-right"> + <li id="fat-menu" class="dropdown nav-item"> + <a href="#" id="drop3" role="button" class="dropdown-toggle nav-link" data-toggle="dropdown">Dropdown 3 <b class="caret"></b></a> <ul class="dropdown-menu" role="menu" aria-labelledby="drop3"> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> @@ -73,9 +67,9 @@ </nav> <ul class="nav nav-pills"> - <li class="active"><a href="#">Regular link</a></li> - <li class="dropdown"> - <a id="drop4" role="button" data-toggle="dropdown" href="#">Dropdown <b class="caret"></b></a> + <li class="active nav-item"><a href="#" class="nav-link">Regular link</a></li> + <li class="dropdown nav-item"> + <a id="drop4" class="nav-link" role="button" data-toggle="dropdown" href="#">Dropdown <b class="caret"></b></a> <ul id="menu1" class="dropdown-menu" role="menu" aria-labelledby="drop4"> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> @@ -84,8 +78,8 @@ <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Separated link</a></li> </ul> </li> - <li class="dropdown"> - <a id="drop5" role="button" data-toggle="dropdown" href="#">Dropdown 2 <b class="caret"></b></a> + <li class="dropdown nav-item"> + <a id="drop5" class="nav-link" role="button" data-toggle="dropdown" href="#">Dropdown 2 <b class="caret"></b></a> <ul id="menu2" class="dropdown-menu" role="menu" aria-labelledby="drop5"> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Action</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="https://twitter.com/fat">Another action</a></li> @@ -100,9 +94,9 @@ <!-- JavaScript Includes --> <script src="../vendor/jquery.min.js"></script> -<script src="../../transition.js"></script> -<script src="../../dropdown.js"></script> -<script src="../../collapse.js"></script> +<script src="../../dist/util.js"></script> +<script src="../../dist/dropdown.js"></script> +<script src="../../dist/collapse.js"></script> </body> </html>