aboutsummaryrefslogtreecommitdiff
path: root/js/src
diff options
context:
space:
mode:
authorMark Otto <[email protected]>2017-04-21 23:36:24 -0700
committerMark Otto <[email protected]>2017-04-21 23:36:24 -0700
commit5463d8436b6404f5c647fb12e0cd0eafa4abf5e0 (patch)
treeada6f9b1f4990a182d829055ee079db9601dbf9b /js/src
parent7efe4ddee499396efc40f53d1421b61fe5da328d (diff)
parent638b97f19c4df6f51475f8088555e3eefd2b986f (diff)
downloadbootstrap-5463d8436b6404f5c647fb12e0cd0eafa4abf5e0.tar.xz
bootstrap-5463d8436b6404f5c647fb12e0cd0eafa4abf5e0.zip
Merge branch 'v4-dev' into form-tweaks
Diffstat (limited to 'js/src')
-rw-r--r--js/src/button.js8
-rw-r--r--js/src/carousel.js41
-rw-r--r--js/src/collapse.js4
-rw-r--r--js/src/dropdown.js51
-rw-r--r--js/src/modal.js1
-rw-r--r--js/src/tooltip.js15
6 files changed, 75 insertions, 45 deletions
diff --git a/js/src/button.js b/js/src/button.js
index 76c5cdd15..6295d0db0 100644
--- a/js/src/button.js
+++ b/js/src/button.js
@@ -66,6 +66,7 @@ const Button = (($) => {
toggle() {
let triggerChangeEvent = true
+ let addAriaPressed = true
const rootElement = $(this._element).closest(
Selector.DATA_TOGGLE
)[0]
@@ -94,12 +95,15 @@ const Button = (($) => {
}
input.focus()
+ addAriaPressed = false
}
}
- this._element.setAttribute('aria-pressed',
- !$(this._element).hasClass(ClassName.ACTIVE))
+ if (addAriaPressed) {
+ this._element.setAttribute('aria-pressed',
+ !$(this._element).hasClass(ClassName.ACTIVE))
+ }
if (triggerChangeEvent) {
$(this._element).toggleClass(ClassName.ACTIVE)
diff --git a/js/src/carousel.js b/js/src/carousel.js
index 7c2da45ad..5993de256 100644
--- a/js/src/carousel.js
+++ b/js/src/carousel.js
@@ -17,15 +17,16 @@ const Carousel = (($) => {
* ------------------------------------------------------------------------
*/
- const NAME = 'carousel'
- const VERSION = '4.0.0-alpha.6'
- const DATA_KEY = 'bs.carousel'
- const EVENT_KEY = `.${DATA_KEY}`
- const DATA_API_KEY = '.data-api'
- const JQUERY_NO_CONFLICT = $.fn[NAME]
- const TRANSITION_DURATION = 600
- const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
- const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
+ const NAME = 'carousel'
+ const VERSION = '4.0.0-alpha.6'
+ const DATA_KEY = 'bs.carousel'
+ const EVENT_KEY = `.${DATA_KEY}`
+ const DATA_API_KEY = '.data-api'
+ const JQUERY_NO_CONFLICT = $.fn[NAME]
+ const TRANSITION_DURATION = 600
+ const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
+ const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
+ const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const Default = {
interval : 5000,
@@ -56,6 +57,7 @@ const Carousel = (($) => {
KEYDOWN : `keydown${EVENT_KEY}`,
MOUSEENTER : `mouseenter${EVENT_KEY}`,
MOUSELEAVE : `mouseleave${EVENT_KEY}`,
+ TOUCHEND : `touchend${EVENT_KEY}`,
LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
}
@@ -98,6 +100,8 @@ const Carousel = (($) => {
this._isPaused = false
this._isSliding = false
+ this.touchTimeout = null
+
this._config = this._getConfig(config)
this._element = $(element)[0]
this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0]
@@ -227,11 +231,26 @@ const Carousel = (($) => {
.on(Event.KEYDOWN, (event) => this._keydown(event))
}
- if (this._config.pause === 'hover' &&
- !('ontouchstart' in document.documentElement)) {
+ if (this._config.pause === 'hover') {
$(this._element)
.on(Event.MOUSEENTER, (event) => this.pause(event))
.on(Event.MOUSELEAVE, (event) => this.cycle(event))
+ if ('ontouchstart' in document.documentElement) {
+ // if it's a touch-enabled device, mouseenter/leave are fired as
+ // part of the mouse compatibility events on first tap - the carousel
+ // would stop cycling until user tapped out of it;
+ // here, we listen for touchend, explicitly pause the carousel
+ // (as if it's the second time we tap on it, mouseenter compat event
+ // is NOT fired) and after a timeout (to allow for mouse compatibility
+ // events to fire) we explicitly restart cycling
+ $(this._element).on(Event.TOUCHEND, () => {
+ this.pause()
+ if (this.touchTimeout) {
+ clearTimeout(this.touchTimeout)
+ }
+ this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
+ })
+ }
}
}
diff --git a/js/src/collapse.js b/js/src/collapse.js
index 88428310d..dec272297 100644
--- a/js/src/collapse.js
+++ b/js/src/collapse.js
@@ -162,7 +162,6 @@ const Collapse = (($) => {
.addClass(ClassName.COLLAPSING)
this._element.style[dimension] = 0
- this._element.setAttribute('aria-expanded', true)
if (this._triggerArray.length) {
$(this._triggerArray)
@@ -223,8 +222,6 @@ const Collapse = (($) => {
.removeClass(ClassName.COLLAPSE)
.removeClass(ClassName.SHOW)
- this._element.setAttribute('aria-expanded', false)
-
if (this._triggerArray.length) {
$(this._triggerArray)
.addClass(ClassName.COLLAPSED)
@@ -300,7 +297,6 @@ const Collapse = (($) => {
_addAriaAndCollapsedClass(element, triggerArray) {
if (element) {
const isOpen = $(element).hasClass(ClassName.SHOW)
- element.setAttribute('aria-expanded', isOpen)
if (triggerArray.length) {
$(triggerArray)
diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index 644273a0a..eb536dc7d 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -25,10 +25,11 @@ const Dropdown = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME]
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key
+ const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key
const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
- const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}|${SPACE_KEYCODE}`)
+ const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)
const Event = {
HIDE : `hide${EVENT_KEY}`,
@@ -37,25 +38,21 @@ const Dropdown = (($) => {
SHOWN : `shown${EVENT_KEY}`,
CLICK : `click${EVENT_KEY}`,
CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
- FOCUSIN_DATA_API : `focusin${EVENT_KEY}${DATA_API_KEY}`,
- KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`
+ KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,
+ KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}`
}
const ClassName = {
- BACKDROP : 'dropdown-backdrop',
DISABLED : 'disabled',
SHOW : 'show'
}
const Selector = {
- BACKDROP : '.dropdown-backdrop',
DATA_TOGGLE : '[data-toggle="dropdown"]',
FORM_CHILD : '.dropdown form',
- ROLE_MENU : '[role="menu"]',
- ROLE_LISTBOX : '[role="listbox"]',
+ MENU : '.dropdown-menu',
NAVBAR_NAV : '.navbar-nav',
- VISIBLE_ITEMS : '[role="menu"] li:not(.disabled) a, '
- + '[role="listbox"] li:not(.disabled) a'
+ VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled)'
}
@@ -108,15 +105,13 @@ const Dropdown = (($) => {
return false
}
- // set the backdrop only if the dropdown menu will be opened
+ // if this is a touch-enabled device we add extra
+ // empty mouseover listeners to the body's immediate children;
+ // only needed because of broken event delegation on iOS
+ // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement &&
!$(parent).closest(Selector.NAVBAR_NAV).length) {
-
- // if mobile we use a backdrop because click events don't delegate
- const dropdown = document.createElement('div')
- dropdown.className = ClassName.BACKDROP
- $(dropdown).insertBefore(this)
- $(dropdown).on('click', Dropdown._clearMenus)
+ $('body').children().on('mouseover', null, $.noop)
}
this.focus()
@@ -163,7 +158,8 @@ const Dropdown = (($) => {
}
static _clearMenus(event) {
- if (event && event.which === RIGHT_MOUSE_BUTTON_WHICH) {
+ if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
+ event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
return
}
@@ -180,7 +176,7 @@ const Dropdown = (($) => {
}
if (event && (event.type === 'click' &&
- /input|textarea/i.test(event.target.tagName) || event.type === 'focusin')
+ /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE)
&& $.contains(parent, event.target)) {
continue
}
@@ -191,10 +187,10 @@ const Dropdown = (($) => {
continue
}
- // remove backdrop only if the dropdown menu will be hidden
- const backdrop = $(parent).find(Selector.BACKDROP)[0]
- if (backdrop) {
- backdrop.parentNode.removeChild(backdrop)
+ // if this is a touch-enabled device we remove the extra
+ // empty mouseover listeners we added for iOS support
+ if ('ontouchstart' in document.documentElement) {
+ $('body').children().off('mouseover', null, $.noop)
}
toggles[i].setAttribute('aria-expanded', 'false')
@@ -217,7 +213,7 @@ const Dropdown = (($) => {
}
static _dataApiKeydownHandler(event) {
- if (!REGEXP_KEYDOWN.test(event.which) ||
+ if (!REGEXP_KEYDOWN.test(event.which) || /button/i.test(event.target.tagName) && event.which === SPACE_KEYCODE ||
/input|textarea/i.test(event.target.tagName)) {
return
}
@@ -232,8 +228,8 @@ const Dropdown = (($) => {
const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW)
- if (!isActive && event.which !== ESCAPE_KEYCODE ||
- isActive && event.which === ESCAPE_KEYCODE) {
+ if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
+ isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {
if (event.which === ESCAPE_KEYCODE) {
const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
@@ -278,9 +274,8 @@ const Dropdown = (($) => {
$(document)
.on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)
- .on(Event.KEYDOWN_DATA_API, Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler)
- .on(Event.KEYDOWN_DATA_API, Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler)
- .on(`${Event.CLICK_DATA_API} ${Event.FOCUSIN_DATA_API}`, Dropdown._clearMenus)
+ .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)
+ .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)
.on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, Dropdown.prototype.toggle)
.on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {
e.stopPropagation()
diff --git a/js/src/modal.js b/js/src/modal.js
index 779b9a402..02d463945 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -289,6 +289,7 @@ const Modal = (($) => {
if (this._isShown && this._config.keyboard) {
$(this._element).on(Event.KEYDOWN_DISMISS, (event) => {
if (event.which === ESCAPE_KEYCODE) {
+ event.preventDefault()
this.hide()
}
})
diff --git a/js/src/tooltip.js b/js/src/tooltip.js
index 1ff2c4f6e..47c3d8d05 100644
--- a/js/src/tooltip.js
+++ b/js/src/tooltip.js
@@ -304,6 +304,14 @@ const Tooltip = (($) => {
$(tip).addClass(ClassName.SHOW)
+ // if this is a touch-enabled device we add extra
+ // empty mouseover listeners to the body's immediate children;
+ // only needed because of broken event delegation on iOS
+ // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
+ if ('ontouchstart' in document.documentElement) {
+ $('body').children().on('mouseover', null, $.noop)
+ }
+
const complete = () => {
const prevHoverState = this._hoverState
this._hoverState = null
@@ -352,6 +360,12 @@ const Tooltip = (($) => {
$(tip).removeClass(ClassName.SHOW)
+ // if this is a touch-enabled device we remove the extra
+ // empty mouseover listeners we added for iOS support
+ if ('ontouchstart' in document.documentElement) {
+ $('body').children().off('mouseover', null, $.noop)
+ }
+
this._activeTrigger[Trigger.CLICK] = false
this._activeTrigger[Trigger.FOCUS] = false
this._activeTrigger[Trigger.HOVER] = false
@@ -368,6 +382,7 @@ const Tooltip = (($) => {
}
this._hoverState = ''
+
}