Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F102317482
carousel.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Feb 19, 10:58
Size
11 KB
Mime Type
text/x-c++
Expires
Fri, Feb 21, 10:58 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
24329691
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
carousel.js
View Options
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.2.3): carousel.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import
{
defineJQueryPlugin
,
getElementFromSelector
,
getNextActiveElement
,
isRTL
,
isVisible
,
reflow
,
triggerTransitionEnd
}
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'
/**
* Constants
*/
const
NAME
=
'carousel'
const
DATA_KEY
=
'bs.carousel'
const
EVENT_KEY
=
`
.
$
{
DATA_KEY
}
`
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
ORDER_NEXT
=
'next'
const
ORDER_PREV
=
'prev'
const
DIRECTION_LEFT
=
'left'
const
DIRECTION_RIGHT
=
'right'
const
EVENT_SLIDE
=
`
slide$
{
EVENT_KEY
}
`
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_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
}
`
const
CLASS_NAME_CAROUSEL
=
'carousel'
const
CLASS_NAME_ACTIVE
=
'active'
const
CLASS_NAME_SLIDE
=
'slide'
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
SELECTOR_ACTIVE
=
'.active'
const
SELECTOR_ITEM
=
'.carousel-item'
const
SELECTOR_ACTIVE_ITEM
=
SELECTOR_ACTIVE
+
SELECTOR_ITEM
const
SELECTOR_ITEM_IMG
=
'.carousel-item img'
const
SELECTOR_INDICATORS
=
'.carousel-indicators'
const
SELECTOR_DATA_SLIDE
=
'[data-bs-slide], [data-bs-slide-to]'
const
SELECTOR_DATA_RIDE
=
'[data-bs-ride="carousel"]'
const
KEY_TO_DIRECTION
=
{
[
ARROW_LEFT_KEY
]
:
DIRECTION_RIGHT
,
[
ARROW_RIGHT_KEY
]
:
DIRECTION_LEFT
}
const
Default
=
{
interval
:
5000
,
keyboard
:
true
,
pause
:
'hover'
,
ride
:
false
,
touch
:
true
,
wrap
:
true
}
const
DefaultType
=
{
interval
:
'(number|boolean)'
,
// TODO:v6 remove boolean support
keyboard
:
'boolean'
,
pause
:
'(string|boolean)'
,
ride
:
'(boolean|string)'
,
touch
:
'boolean'
,
wrap
:
'boolean'
}
/**
* Class definition
*/
class
Carousel
extends
BaseComponent
{
constructor
(
element
,
config
)
{
super
(
element
,
config
)
this
.
_interval
=
null
this
.
_activeElement
=
null
this
.
_isSliding
=
false
this
.
touchTimeout
=
null
this
.
_swipeHelper
=
null
this
.
_indicatorsElement
=
SelectorEngine
.
findOne
(
SELECTOR_INDICATORS
,
this
.
_element
)
this
.
_addEventListeners
()
if
(
this
.
_config
.
ride
===
CLASS_NAME_CAROUSEL
)
{
this
.
cycle
()
}
}
// Getters
static
get
Default
()
{
return
Default
}
static
get
DefaultType
()
{
return
DefaultType
}
static
get
NAME
()
{
return
NAME
}
// Public
next
()
{
this
.
_slide
(
ORDER_NEXT
)
}
nextWhenVisible
()
{
// FIXME TODO use `document.visibilityState`
// Don't call next when the page isn't visible
// or the carousel or its parent isn't visible
if
(
!
document
.
hidden
&&
isVisible
(
this
.
_element
))
{
this
.
next
()
}
}
prev
()
{
this
.
_slide
(
ORDER_PREV
)
}
pause
()
{
if
(
this
.
_isSliding
)
{
triggerTransitionEnd
(
this
.
_element
)
}
this
.
_clearInterval
()
}
cycle
()
{
this
.
_clearInterval
()
this
.
_updateInterval
()
this
.
_interval
=
setInterval
(()
=>
this
.
nextWhenVisible
(),
this
.
_config
.
interval
)
}
_maybeEnableCycle
()
{
if
(
!
this
.
_config
.
ride
)
{
return
}
if
(
this
.
_isSliding
)
{
EventHandler
.
one
(
this
.
_element
,
EVENT_SLID
,
()
=>
this
.
cycle
())
return
}
this
.
cycle
()
}
to
(
index
)
{
const
items
=
this
.
_getItems
()
if
(
index
>
items
.
length
-
1
||
index
<
0
)
{
return
}
if
(
this
.
_isSliding
)
{
EventHandler
.
one
(
this
.
_element
,
EVENT_SLID
,
()
=>
this
.
to
(
index
))
return
}
const
activeIndex
=
this
.
_getItemIndex
(
this
.
_getActive
())
if
(
activeIndex
===
index
)
{
return
}
const
order
=
index
>
activeIndex
?
ORDER_NEXT
:
ORDER_PREV
this
.
_slide
(
order
,
items
[
index
])
}
dispose
()
{
if
(
this
.
_swipeHelper
)
{
this
.
_swipeHelper
.
dispose
()
}
super
.
dispose
()
}
// Private
_configAfterMerge
(
config
)
{
config
.
defaultInterval
=
config
.
interval
return
config
}
_addEventListeners
()
{
if
(
this
.
_config
.
keyboard
)
{
EventHandler
.
on
(
this
.
_element
,
EVENT_KEYDOWN
,
event
=>
this
.
_keydown
(
event
))
}
if
(
this
.
_config
.
pause
===
'hover'
)
{
EventHandler
.
on
(
this
.
_element
,
EVENT_MOUSEENTER
,
()
=>
this
.
pause
())
EventHandler
.
on
(
this
.
_element
,
EVENT_MOUSELEAVE
,
()
=>
this
.
_maybeEnableCycle
())
}
if
(
this
.
_config
.
touch
&&
Swipe
.
isSupported
())
{
this
.
_addTouchEventListeners
()
}
}
_addTouchEventListeners
()
{
for
(
const
img
of
SelectorEngine
.
find
(
SELECTOR_ITEM_IMG
,
this
.
_element
))
{
EventHandler
.
on
(
img
,
EVENT_DRAG_START
,
event
=>
event
.
preventDefault
())
}
const
endCallBack
=
()
=>
{
if
(
this
.
_config
.
pause
!==
'hover'
)
{
return
}
// 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
.
pause
()
if
(
this
.
touchTimeout
)
{
clearTimeout
(
this
.
touchTimeout
)
}
this
.
touchTimeout
=
setTimeout
(()
=>
this
.
_maybeEnableCycle
(),
TOUCHEVENT_COMPAT_WAIT
+
this
.
_config
.
interval
)
}
const
swipeConfig
=
{
leftCallback
:
()
=>
this
.
_slide
(
this
.
_directionToOrder
(
DIRECTION_LEFT
)),
rightCallback
:
()
=>
this
.
_slide
(
this
.
_directionToOrder
(
DIRECTION_RIGHT
)),
endCallback
:
endCallBack
}
this
.
_swipeHelper
=
new
Swipe
(
this
.
_element
,
swipeConfig
)
}
_keydown
(
event
)
{
if
(
/input|textarea/i
.
test
(
event
.
target
.
tagName
))
{
return
}
const
direction
=
KEY_TO_DIRECTION
[
event
.
key
]
if
(
direction
)
{
event
.
preventDefault
()
this
.
_slide
(
this
.
_directionToOrder
(
direction
))
}
}
_getItemIndex
(
element
)
{
return
this
.
_getItems
().
indexOf
(
element
)
}
_setActiveIndicatorElement
(
index
)
{
if
(
!
this
.
_indicatorsElement
)
{
return
}
const
activeIndicator
=
SelectorEngine
.
findOne
(
SELECTOR_ACTIVE
,
this
.
_indicatorsElement
)
activeIndicator
.
classList
.
remove
(
CLASS_NAME_ACTIVE
)
activeIndicator
.
removeAttribute
(
'aria-current'
)
const
newActiveIndicator
=
SelectorEngine
.
findOne
(
`
[
data
-
bs
-
slide
-
to
=
"${index}"
]
`
,
this
.
_indicatorsElement
)
if
(
newActiveIndicator
)
{
newActiveIndicator
.
classList
.
add
(
CLASS_NAME_ACTIVE
)
newActiveIndicator
.
setAttribute
(
'aria-current'
,
'true'
)
}
}
_updateInterval
()
{
const
element
=
this
.
_activeElement
||
this
.
_getActive
()
if
(
!
element
)
{
return
}
const
elementInterval
=
Number
.
parseInt
(
element
.
getAttribute
(
'data-bs-interval'
),
10
)
this
.
_config
.
interval
=
elementInterval
||
this
.
_config
.
defaultInterval
}
_slide
(
order
,
element
=
null
)
{
if
(
this
.
_isSliding
)
{
return
}
const
activeElement
=
this
.
_getActive
()
const
isNext
=
order
===
ORDER_NEXT
const
nextElement
=
element
||
getNextActiveElement
(
this
.
_getItems
(),
activeElement
,
isNext
,
this
.
_config
.
wrap
)
if
(
nextElement
===
activeElement
)
{
return
}
const
nextElementIndex
=
this
.
_getItemIndex
(
nextElement
)
const
triggerEvent
=
eventName
=>
{
return
EventHandler
.
trigger
(
this
.
_element
,
eventName
,
{
relatedTarget
:
nextElement
,
direction
:
this
.
_orderToDirection
(
order
),
from
:
this
.
_getItemIndex
(
activeElement
),
to
:
nextElementIndex
})
}
const
slideEvent
=
triggerEvent
(
EVENT_SLIDE
)
if
(
slideEvent
.
defaultPrevented
)
{
return
}
if
(
!
activeElement
||
!
nextElement
)
{
// Some weirdness is happening, so we bail
// todo: change tests that use empty divs to avoid this check
return
}
const
isCycling
=
Boolean
(
this
.
_interval
)
this
.
pause
()
this
.
_isSliding
=
true
this
.
_setActiveIndicatorElement
(
nextElementIndex
)
this
.
_activeElement
=
nextElement
const
directionalClassName
=
isNext
?
CLASS_NAME_START
:
CLASS_NAME_END
const
orderClassName
=
isNext
?
CLASS_NAME_NEXT
:
CLASS_NAME_PREV
nextElement
.
classList
.
add
(
orderClassName
)
reflow
(
nextElement
)
activeElement
.
classList
.
add
(
directionalClassName
)
nextElement
.
classList
.
add
(
directionalClassName
)
const
completeCallBack
=
()
=>
{
nextElement
.
classList
.
remove
(
directionalClassName
,
orderClassName
)
nextElement
.
classList
.
add
(
CLASS_NAME_ACTIVE
)
activeElement
.
classList
.
remove
(
CLASS_NAME_ACTIVE
,
orderClassName
,
directionalClassName
)
this
.
_isSliding
=
false
triggerEvent
(
EVENT_SLID
)
}
this
.
_queueCallback
(
completeCallBack
,
activeElement
,
this
.
_isAnimated
())
if
(
isCycling
)
{
this
.
cycle
()
}
}
_isAnimated
()
{
return
this
.
_element
.
classList
.
contains
(
CLASS_NAME_SLIDE
)
}
_getActive
()
{
return
SelectorEngine
.
findOne
(
SELECTOR_ACTIVE_ITEM
,
this
.
_element
)
}
_getItems
()
{
return
SelectorEngine
.
find
(
SELECTOR_ITEM
,
this
.
_element
)
}
_clearInterval
()
{
if
(
this
.
_interval
)
{
clearInterval
(
this
.
_interval
)
this
.
_interval
=
null
}
}
_directionToOrder
(
direction
)
{
if
(
isRTL
())
{
return
direction
===
DIRECTION_LEFT
?
ORDER_PREV
:
ORDER_NEXT
}
return
direction
===
DIRECTION_LEFT
?
ORDER_NEXT
:
ORDER_PREV
}
_orderToDirection
(
order
)
{
if
(
isRTL
())
{
return
order
===
ORDER_PREV
?
DIRECTION_LEFT
:
DIRECTION_RIGHT
}
return
order
===
ORDER_PREV
?
DIRECTION_RIGHT
:
DIRECTION_LEFT
}
// Static
static
jQueryInterface
(
config
)
{
return
this
.
each
(
function
()
{
const
data
=
Carousel
.
getOrCreateInstance
(
this
,
config
)
if
(
typeof
config
===
'number'
)
{
data
.
to
(
config
)
return
}
if
(
typeof
config
===
'string'
)
{
if
(
data
[
config
]
===
undefined
||
config
.
startsWith
(
'_'
)
||
config
===
'constructor'
)
{
throw
new
TypeError
(
`
No
method
named
"${config}"
`
)
}
data
[
config
]()
}
})
}
}
/**
* Data API implementation
*/
EventHandler
.
on
(
document
,
EVENT_CLICK_DATA_API
,
SELECTOR_DATA_SLIDE
,
function
(
event
)
{
const
target
=
getElementFromSelector
(
this
)
if
(
!
target
||
!
target
.
classList
.
contains
(
CLASS_NAME_CAROUSEL
))
{
return
}
event
.
preventDefault
()
const
carousel
=
Carousel
.
getOrCreateInstance
(
target
)
const
slideIndex
=
this
.
getAttribute
(
'data-bs-slide-to'
)
if
(
slideIndex
)
{
carousel
.
to
(
slideIndex
)
carousel
.
_maybeEnableCycle
()
return
}
if
(
Manipulator
.
getDataAttribute
(
this
,
'slide'
)
===
'next'
)
{
carousel
.
next
()
carousel
.
_maybeEnableCycle
()
return
}
carousel
.
prev
()
carousel
.
_maybeEnableCycle
()
})
EventHandler
.
on
(
window
,
EVENT_LOAD_DATA_API
,
()
=>
{
const
carousels
=
SelectorEngine
.
find
(
SELECTOR_DATA_RIDE
)
for
(
const
carousel
of
carousels
)
{
Carousel
.
getOrCreateInstance
(
carousel
)
}
})
/**
* jQuery
*/
defineJQueryPlugin
(
Carousel
)
export
default
Carousel
Event Timeline
Log In to Comment