Skip to content

Commit

Permalink
Merge branch 'main' into gs/dropdown-clean-up
Browse files Browse the repository at this point in the history
  • Loading branch information
XhmikosR committed Oct 11, 2021
2 parents 3ac991c + 8ec6c94 commit f9226b5
Show file tree
Hide file tree
Showing 7 changed files with 420 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "16 kB"
"maxSize": "16.25 kB"
}
],
"ci": {
Expand Down
91 changes: 20 additions & 71 deletions js/src/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
import {
defineJQueryPlugin,
getElementFromSelector,
getNextActiveElement,
isRTL,
isVisible,
getNextActiveElement,
reflow,
triggerTransitionEnd,
typeCheckConfig
} from './util/index'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
import Swipe from './util/swipe'
import BaseComponent from './base-component'

/**
Expand All @@ -34,7 +35,6 @@ const DATA_API_KEY = '.data-api'
const ARROW_LEFT_KEY = 'ArrowLeft'
const ARROW_RIGHT_KEY = 'ArrowRight'
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
const SWIPE_THRESHOLD = 40

const Default = {
interval: 5000,
Expand Down Expand Up @@ -69,11 +69,6 @@ const EVENT_SLID = `slid${EVENT_KEY}`
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
Expand All @@ -85,7 +80,6 @@ const CLASS_NAME_END = 'carousel-item-end'
const CLASS_NAME_START = 'carousel-item-start'
const CLASS_NAME_NEXT = 'carousel-item-next'
const CLASS_NAME_PREV = 'carousel-item-prev'
const CLASS_NAME_POINTER_EVENT = 'pointer-event'

const SELECTOR_ACTIVE = '.active'
const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'
Expand All @@ -97,9 +91,6 @@ const SELECTOR_INDICATOR = '[data-bs-target]'
const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'
const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'

const POINTER_TYPE_TOUCH = 'touch'
const POINTER_TYPE_PEN = 'pen'

/**
* ------------------------------------------------------------------------
* Class Definition
Expand All @@ -115,14 +106,10 @@ class Carousel extends BaseComponent {
this._isPaused = false
this._isSliding = false
this.touchTimeout = null
this.touchStartX = 0
this.touchDeltaX = 0
this._swipeHelper = null

this._config = this._getConfig(config)
this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
this._pointerEvent = Boolean(window.PointerEvent)

this._addEventListeners()
}

Expand Down Expand Up @@ -214,6 +201,14 @@ class Carousel extends BaseComponent {
this._slide(order, this._items[index])
}

dispose() {
if (this._swipeHelper) {
this._swipeHelper.dispose()
}

super.dispose()
}

// Private

_getConfig(config) {
Expand All @@ -226,24 +221,6 @@ class Carousel extends BaseComponent {
return config
}

_handleSwipe() {
const absDeltax = Math.abs(this.touchDeltaX)

if (absDeltax <= SWIPE_THRESHOLD) {
return
}

const direction = absDeltax / this.touchDeltaX

this.touchDeltaX = 0

if (!direction) {
return
}

this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT)
}

_addEventListeners() {
if (this._config.keyboard) {
EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))
Expand All @@ -254,38 +231,17 @@ class Carousel extends BaseComponent {
EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event))
}

if (this._config.touch && this._touchSupported) {
if (this._config.touch && Swipe.isSupported()) {
this._addTouchEventListeners()
}
}

_addTouchEventListeners() {
const hasPointerPenTouch = event => {
return this._pointerEvent &&
(event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)
}

const start = event => {
if (hasPointerPenTouch(event)) {
this.touchStartX = event.clientX
} else if (!this._pointerEvent) {
this.touchStartX = event.touches[0].clientX
}
}

const move = event => {
// ensure swiping with one touch and not pinching
this.touchDeltaX = event.touches && event.touches.length > 1 ?
0 :
event.touches[0].clientX - this.touchStartX
for (const itemImg of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {
EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault())
}

const end = event => {
if (hasPointerPenTouch(event)) {
this.touchDeltaX = event.clientX - this.touchStartX
}

this._handleSwipe()
const endCallBack = () => {
if (this._config.pause === 'hover') {
// If it's a touch-enabled device, mouseenter/leave are fired as
// part of the mouse compatibility events on first tap - the carousel
Expand All @@ -304,20 +260,13 @@ class Carousel extends BaseComponent {
}
}

for (const itemImg of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {
EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault())
const swipeConfig = {
leftCallback: () => this._slide(DIRECTION_LEFT),
rightCallback: () => this._slide(DIRECTION_RIGHT),
endCallback: endCallBack
}

if (this._pointerEvent) {
EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event))
EventHandler.on(this._element, EVENT_POINTERUP, event => end(event))

this._element.classList.add(CLASS_NAME_POINTER_EVENT)
} else {
EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event))
EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event))
EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event))
}
this._swipeHelper = new Swipe(this._element, swipeConfig)
}

_keydown(event) {
Expand Down
2 changes: 1 addition & 1 deletion js/src/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ class Tooltip extends BaseComponent {
content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn)
}

element.innerHTML = content
element.innerHTML = content // lgtm [js/xss-through-dom]
} else {
element.textContent = content
}
Expand Down
122 changes: 122 additions & 0 deletions js/src/util/swipe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import EventHandler from '../dom/event-handler'
import { execute, typeCheckConfig } from './index'

const EVENT_KEY = '.bs.swipe'
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
const POINTER_TYPE_TOUCH = 'touch'
const POINTER_TYPE_PEN = 'pen'
const CLASS_NAME_POINTER_EVENT = 'pointer-event'
const SWIPE_THRESHOLD = 40
const NAME = 'swipe'

const Default = {
leftCallback: null,
rightCallback: null,
endCallback: null
}

const DefaultType = {
leftCallback: '(function|null)',
rightCallback: '(function|null)',
endCallback: '(function|null)'
}

class Swipe {
constructor(element, config) {
this._element = element

if (!element || !Swipe.isSupported()) {
return
}

this._config = this._getConfig(config)
this._deltaX = 0
this._supportPointerEvents = Boolean(window.PointerEvent)
this._initEvents()
}

dispose() {
EventHandler.off(this._element, EVENT_KEY)
}

_start(event) {
if (!this._supportPointerEvents) {
this._deltaX = event.touches[0].clientX

return
}

if (this._eventIsPointerPenTouch(event)) {
this._deltaX = event.clientX
}
}

_end(event) {
if (this._eventIsPointerPenTouch(event)) {
this._deltaX = event.clientX - this._deltaX
}

this._handleSwipe()
execute(this._config.endCallback)
}

_move(event) {
this._deltaX = event.touches && event.touches.length > 1 ?
0 :
event.touches[0].clientX - this._deltaX
}

_handleSwipe() {
const absDeltaX = Math.abs(this._deltaX)

if (absDeltaX <= SWIPE_THRESHOLD) {
return
}

const direction = absDeltaX / this._deltaX

this._deltaX = 0

if (!direction) {
return
}

execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)
}

_initEvents() {
if (this._supportPointerEvents) {
EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))
EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))

this._element.classList.add(CLASS_NAME_POINTER_EVENT)
} else {
EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))
EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))
EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))
}
}

_getConfig(config) {
config = {
...Default,
...(typeof config === 'object' ? config : {})
}
typeCheckConfig(NAME, config, DefaultType)
return config
}

_eventIsPointerPenTouch(event) {
return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)
}

static isSupported() {
return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
}
}

export default Swipe
Loading

0 comments on commit f9226b5

Please sign in to comment.