Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F99781105
tooltip.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
Sun, Jan 26, 14:35
Size
15 KB
Mime Type
text/x-c++
Expires
Tue, Jan 28, 14:35 (1 d, 5 h)
Engine
blob
Format
Raw Data
Handle
23821224
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
tooltip.js
View Options
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.2.3): tooltip.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import
*
as
Popper
from
'@popperjs/core'
import
{
defineJQueryPlugin
,
findShadowRoot
,
getElement
,
getUID
,
isRTL
,
noop
}
from
'./util/index'
import
{
DefaultAllowlist
}
from
'./util/sanitizer'
import
EventHandler
from
'./dom/event-handler'
import
Manipulator
from
'./dom/manipulator'
import
BaseComponent
from
'./base-component'
import
TemplateFactory
from
'./util/template-factory'
/**
* Constants
*/
const
NAME
=
'tooltip'
const
DISALLOWED_ATTRIBUTES
=
new
Set
([
'sanitize'
,
'allowList'
,
'sanitizeFn'
])
const
CLASS_NAME_FADE
=
'fade'
const
CLASS_NAME_MODAL
=
'modal'
const
CLASS_NAME_SHOW
=
'show'
const
SELECTOR_TOOLTIP_INNER
=
'.tooltip-inner'
const
SELECTOR_MODAL
=
`
.
$
{
CLASS_NAME_MODAL
}
`
const
EVENT_MODAL_HIDE
=
'hide.bs.modal'
const
TRIGGER_HOVER
=
'hover'
const
TRIGGER_FOCUS
=
'focus'
const
TRIGGER_CLICK
=
'click'
const
TRIGGER_MANUAL
=
'manual'
const
EVENT_HIDE
=
'hide'
const
EVENT_HIDDEN
=
'hidden'
const
EVENT_SHOW
=
'show'
const
EVENT_SHOWN
=
'shown'
const
EVENT_INSERTED
=
'inserted'
const
EVENT_CLICK
=
'click'
const
EVENT_FOCUSIN
=
'focusin'
const
EVENT_FOCUSOUT
=
'focusout'
const
EVENT_MOUSEENTER
=
'mouseenter'
const
EVENT_MOUSELEAVE
=
'mouseleave'
const
AttachmentMap
=
{
AUTO
:
'auto'
,
TOP
:
'top'
,
RIGHT
:
isRTL
()
?
'left'
:
'right'
,
BOTTOM
:
'bottom'
,
LEFT
:
isRTL
()
?
'right'
:
'left'
}
const
Default
=
{
allowList
:
DefaultAllowlist
,
animation
:
true
,
boundary
:
'clippingParents'
,
container
:
false
,
customClass
:
''
,
delay
:
0
,
fallbackPlacements
:
[
'top'
,
'right'
,
'bottom'
,
'left'
],
html
:
false
,
offset
:
[
0
,
0
],
placement
:
'top'
,
popperConfig
:
null
,
sanitize
:
true
,
sanitizeFn
:
null
,
selector
:
false
,
template
:
'<div class="tooltip" role="tooltip">'
+
'<div class="tooltip-arrow"></div>'
+
'<div class="tooltip-inner"></div>'
+
'</div>'
,
title
:
''
,
trigger
:
'hover focus'
}
const
DefaultType
=
{
allowList
:
'object'
,
animation
:
'boolean'
,
boundary
:
'(string|element)'
,
container
:
'(string|element|boolean)'
,
customClass
:
'(string|function)'
,
delay
:
'(number|object)'
,
fallbackPlacements
:
'array'
,
html
:
'boolean'
,
offset
:
'(array|string|function)'
,
placement
:
'(string|function)'
,
popperConfig
:
'(null|object|function)'
,
sanitize
:
'boolean'
,
sanitizeFn
:
'(null|function)'
,
selector
:
'(string|boolean)'
,
template
:
'string'
,
title
:
'(string|element|function)'
,
trigger
:
'string'
}
/**
* Class definition
*/
class
Tooltip
extends
BaseComponent
{
constructor
(
element
,
config
)
{
if
(
typeof
Popper
===
'undefined'
)
{
throw
new
TypeError
(
'Bootstrap\'s tooltips require Popper (https://popper.js.org)'
)
}
super
(
element
,
config
)
// Private
this
.
_isEnabled
=
true
this
.
_timeout
=
0
this
.
_isHovered
=
null
this
.
_activeTrigger
=
{}
this
.
_popper
=
null
this
.
_templateFactory
=
null
this
.
_newContent
=
null
// Protected
this
.
tip
=
null
this
.
_setListeners
()
if
(
!
this
.
_config
.
selector
)
{
this
.
_fixTitle
()
}
}
// Getters
static
get
Default
()
{
return
Default
}
static
get
DefaultType
()
{
return
DefaultType
}
static
get
NAME
()
{
return
NAME
}
// Public
enable
()
{
this
.
_isEnabled
=
true
}
disable
()
{
this
.
_isEnabled
=
false
}
toggleEnabled
()
{
this
.
_isEnabled
=
!
this
.
_isEnabled
}
toggle
()
{
if
(
!
this
.
_isEnabled
)
{
return
}
this
.
_activeTrigger
.
click
=
!
this
.
_activeTrigger
.
click
if
(
this
.
_isShown
())
{
this
.
_leave
()
return
}
this
.
_enter
()
}
dispose
()
{
clearTimeout
(
this
.
_timeout
)
EventHandler
.
off
(
this
.
_element
.
closest
(
SELECTOR_MODAL
),
EVENT_MODAL_HIDE
,
this
.
_hideModalHandler
)
if
(
this
.
_element
.
getAttribute
(
'data-bs-original-title'
))
{
this
.
_element
.
setAttribute
(
'title'
,
this
.
_element
.
getAttribute
(
'data-bs-original-title'
))
}
this
.
_disposePopper
()
super
.
dispose
()
}
show
()
{
if
(
this
.
_element
.
style
.
display
===
'none'
)
{
throw
new
Error
(
'Please use show on visible elements'
)
}
if
(
!
(
this
.
_isWithContent
()
&&
this
.
_isEnabled
))
{
return
}
const
showEvent
=
EventHandler
.
trigger
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_SHOW
))
const
shadowRoot
=
findShadowRoot
(
this
.
_element
)
const
isInTheDom
=
(
shadowRoot
||
this
.
_element
.
ownerDocument
.
documentElement
).
contains
(
this
.
_element
)
if
(
showEvent
.
defaultPrevented
||
!
isInTheDom
)
{
return
}
// todo v6 remove this OR make it optional
this
.
_disposePopper
()
const
tip
=
this
.
_getTipElement
()
this
.
_element
.
setAttribute
(
'aria-describedby'
,
tip
.
getAttribute
(
'id'
))
const
{
container
}
=
this
.
_config
if
(
!
this
.
_element
.
ownerDocument
.
documentElement
.
contains
(
this
.
tip
))
{
container
.
append
(
tip
)
EventHandler
.
trigger
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_INSERTED
))
}
this
.
_popper
=
this
.
_createPopper
(
tip
)
tip
.
classList
.
add
(
CLASS_NAME_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
)
{
for
(
const
element
of
[].
concat
(...
document
.
body
.
children
))
{
EventHandler
.
on
(
element
,
'mouseover'
,
noop
)
}
}
const
complete
=
()
=>
{
EventHandler
.
trigger
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_SHOWN
))
if
(
this
.
_isHovered
===
false
)
{
this
.
_leave
()
}
this
.
_isHovered
=
false
}
this
.
_queueCallback
(
complete
,
this
.
tip
,
this
.
_isAnimated
())
}
hide
()
{
if
(
!
this
.
_isShown
())
{
return
}
const
hideEvent
=
EventHandler
.
trigger
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_HIDE
))
if
(
hideEvent
.
defaultPrevented
)
{
return
}
const
tip
=
this
.
_getTipElement
()
tip
.
classList
.
remove
(
CLASS_NAME_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
)
{
for
(
const
element
of
[].
concat
(...
document
.
body
.
children
))
{
EventHandler
.
off
(
element
,
'mouseover'
,
noop
)
}
}
this
.
_activeTrigger
[
TRIGGER_CLICK
]
=
false
this
.
_activeTrigger
[
TRIGGER_FOCUS
]
=
false
this
.
_activeTrigger
[
TRIGGER_HOVER
]
=
false
this
.
_isHovered
=
null
// it is a trick to support manual triggering
const
complete
=
()
=>
{
if
(
this
.
_isWithActiveTrigger
())
{
return
}
if
(
!
this
.
_isHovered
)
{
this
.
_disposePopper
()
}
this
.
_element
.
removeAttribute
(
'aria-describedby'
)
EventHandler
.
trigger
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_HIDDEN
))
}
this
.
_queueCallback
(
complete
,
this
.
tip
,
this
.
_isAnimated
())
}
update
()
{
if
(
this
.
_popper
)
{
this
.
_popper
.
update
()
}
}
// Protected
_isWithContent
()
{
return
Boolean
(
this
.
_getTitle
())
}
_getTipElement
()
{
if
(
!
this
.
tip
)
{
this
.
tip
=
this
.
_createTipElement
(
this
.
_newContent
||
this
.
_getContentForTemplate
())
}
return
this
.
tip
}
_createTipElement
(
content
)
{
const
tip
=
this
.
_getTemplateFactory
(
content
).
toHtml
()
// todo: remove this check on v6
if
(
!
tip
)
{
return
null
}
tip
.
classList
.
remove
(
CLASS_NAME_FADE
,
CLASS_NAME_SHOW
)
// todo: on v6 the following can be achieved with CSS only
tip
.
classList
.
add
(
`
bs
-
$
{
this
.
constructor
.
NAME
}
-
auto
`
)
const
tipId
=
getUID
(
this
.
constructor
.
NAME
).
toString
()
tip
.
setAttribute
(
'id'
,
tipId
)
if
(
this
.
_isAnimated
())
{
tip
.
classList
.
add
(
CLASS_NAME_FADE
)
}
return
tip
}
setContent
(
content
)
{
this
.
_newContent
=
content
if
(
this
.
_isShown
())
{
this
.
_disposePopper
()
this
.
show
()
}
}
_getTemplateFactory
(
content
)
{
if
(
this
.
_templateFactory
)
{
this
.
_templateFactory
.
changeContent
(
content
)
}
else
{
this
.
_templateFactory
=
new
TemplateFactory
({
...
this
.
_config
,
// the `content` var has to be after `this._config`
// to override config.content in case of popover
content
,
extraClass
:
this
.
_resolvePossibleFunction
(
this
.
_config
.
customClass
)
})
}
return
this
.
_templateFactory
}
_getContentForTemplate
()
{
return
{
[
SELECTOR_TOOLTIP_INNER
]
:
this
.
_getTitle
()
}
}
_getTitle
()
{
return
this
.
_resolvePossibleFunction
(
this
.
_config
.
title
)
||
this
.
_element
.
getAttribute
(
'data-bs-original-title'
)
}
// Private
_initializeOnDelegatedTarget
(
event
)
{
return
this
.
constructor
.
getOrCreateInstance
(
event
.
delegateTarget
,
this
.
_getDelegateConfig
())
}
_isAnimated
()
{
return
this
.
_config
.
animation
||
(
this
.
tip
&&
this
.
tip
.
classList
.
contains
(
CLASS_NAME_FADE
))
}
_isShown
()
{
return
this
.
tip
&&
this
.
tip
.
classList
.
contains
(
CLASS_NAME_SHOW
)
}
_createPopper
(
tip
)
{
const
placement
=
typeof
this
.
_config
.
placement
===
'function'
?
this
.
_config
.
placement
.
call
(
this
,
tip
,
this
.
_element
)
:
this
.
_config
.
placement
const
attachment
=
AttachmentMap
[
placement
.
toUpperCase
()]
return
Popper
.
createPopper
(
this
.
_element
,
tip
,
this
.
_getPopperConfig
(
attachment
))
}
_getOffset
()
{
const
{
offset
}
=
this
.
_config
if
(
typeof
offset
===
'string'
)
{
return
offset
.
split
(
','
).
map
(
value
=>
Number
.
parseInt
(
value
,
10
))
}
if
(
typeof
offset
===
'function'
)
{
return
popperData
=>
offset
(
popperData
,
this
.
_element
)
}
return
offset
}
_resolvePossibleFunction
(
arg
)
{
return
typeof
arg
===
'function'
?
arg
.
call
(
this
.
_element
)
:
arg
}
_getPopperConfig
(
attachment
)
{
const
defaultBsPopperConfig
=
{
placement
:
attachment
,
modifiers
:
[
{
name
:
'flip'
,
options
:
{
fallbackPlacements
:
this
.
_config
.
fallbackPlacements
}
},
{
name
:
'offset'
,
options
:
{
offset
:
this
.
_getOffset
()
}
},
{
name
:
'preventOverflow'
,
options
:
{
boundary
:
this
.
_config
.
boundary
}
},
{
name
:
'arrow'
,
options
:
{
element
:
`
.
$
{
this
.
constructor
.
NAME
}
-
arrow
`
}
},
{
name
:
'preSetPlacement'
,
enabled
:
true
,
phase
:
'beforeMain'
,
fn
:
data
=>
{
// Pre-set Popper's placement attribute in order to read the arrow sizes properly.
// Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
this
.
_getTipElement
().
setAttribute
(
'data-popper-placement'
,
data
.
state
.
placement
)
}
}
]
}
return
{
...
defaultBsPopperConfig
,
...(
typeof
this
.
_config
.
popperConfig
===
'function'
?
this
.
_config
.
popperConfig
(
defaultBsPopperConfig
)
:
this
.
_config
.
popperConfig
)
}
}
_setListeners
()
{
const
triggers
=
this
.
_config
.
trigger
.
split
(
' '
)
for
(
const
trigger
of
triggers
)
{
if
(
trigger
===
'click'
)
{
EventHandler
.
on
(
this
.
_element
,
this
.
constructor
.
eventName
(
EVENT_CLICK
),
this
.
_config
.
selector
,
event
=>
{
const
context
=
this
.
_initializeOnDelegatedTarget
(
event
)
context
.
toggle
()
})
}
else
if
(
trigger
!==
TRIGGER_MANUAL
)
{
const
eventIn
=
trigger
===
TRIGGER_HOVER
?
this
.
constructor
.
eventName
(
EVENT_MOUSEENTER
)
:
this
.
constructor
.
eventName
(
EVENT_FOCUSIN
)
const
eventOut
=
trigger
===
TRIGGER_HOVER
?
this
.
constructor
.
eventName
(
EVENT_MOUSELEAVE
)
:
this
.
constructor
.
eventName
(
EVENT_FOCUSOUT
)
EventHandler
.
on
(
this
.
_element
,
eventIn
,
this
.
_config
.
selector
,
event
=>
{
const
context
=
this
.
_initializeOnDelegatedTarget
(
event
)
context
.
_activeTrigger
[
event
.
type
===
'focusin'
?
TRIGGER_FOCUS
:
TRIGGER_HOVER
]
=
true
context
.
_enter
()
})
EventHandler
.
on
(
this
.
_element
,
eventOut
,
this
.
_config
.
selector
,
event
=>
{
const
context
=
this
.
_initializeOnDelegatedTarget
(
event
)
context
.
_activeTrigger
[
event
.
type
===
'focusout'
?
TRIGGER_FOCUS
:
TRIGGER_HOVER
]
=
context
.
_element
.
contains
(
event
.
relatedTarget
)
context
.
_leave
()
})
}
}
this
.
_hideModalHandler
=
()
=>
{
if
(
this
.
_element
)
{
this
.
hide
()
}
}
EventHandler
.
on
(
this
.
_element
.
closest
(
SELECTOR_MODAL
),
EVENT_MODAL_HIDE
,
this
.
_hideModalHandler
)
}
_fixTitle
()
{
const
title
=
this
.
_element
.
getAttribute
(
'title'
)
if
(
!
title
)
{
return
}
if
(
!
this
.
_element
.
getAttribute
(
'aria-label'
)
&&
!
this
.
_element
.
textContent
.
trim
())
{
this
.
_element
.
setAttribute
(
'aria-label'
,
title
)
}
this
.
_element
.
setAttribute
(
'data-bs-original-title'
,
title
)
// DO NOT USE IT. Is only for backwards compatibility
this
.
_element
.
removeAttribute
(
'title'
)
}
_enter
()
{
if
(
this
.
_isShown
()
||
this
.
_isHovered
)
{
this
.
_isHovered
=
true
return
}
this
.
_isHovered
=
true
this
.
_setTimeout
(()
=>
{
if
(
this
.
_isHovered
)
{
this
.
show
()
}
},
this
.
_config
.
delay
.
show
)
}
_leave
()
{
if
(
this
.
_isWithActiveTrigger
())
{
return
}
this
.
_isHovered
=
false
this
.
_setTimeout
(()
=>
{
if
(
!
this
.
_isHovered
)
{
this
.
hide
()
}
},
this
.
_config
.
delay
.
hide
)
}
_setTimeout
(
handler
,
timeout
)
{
clearTimeout
(
this
.
_timeout
)
this
.
_timeout
=
setTimeout
(
handler
,
timeout
)
}
_isWithActiveTrigger
()
{
return
Object
.
values
(
this
.
_activeTrigger
).
includes
(
true
)
}
_getConfig
(
config
)
{
const
dataAttributes
=
Manipulator
.
getDataAttributes
(
this
.
_element
)
for
(
const
dataAttribute
of
Object
.
keys
(
dataAttributes
))
{
if
(
DISALLOWED_ATTRIBUTES
.
has
(
dataAttribute
))
{
delete
dataAttributes
[
dataAttribute
]
}
}
config
=
{
...
dataAttributes
,
...(
typeof
config
===
'object'
&&
config
?
config
:
{})
}
config
=
this
.
_mergeConfigObj
(
config
)
config
=
this
.
_configAfterMerge
(
config
)
this
.
_typeCheckConfig
(
config
)
return
config
}
_configAfterMerge
(
config
)
{
config
.
container
=
config
.
container
===
false
?
document
.
body
:
getElement
(
config
.
container
)
if
(
typeof
config
.
delay
===
'number'
)
{
config
.
delay
=
{
show
:
config
.
delay
,
hide
:
config
.
delay
}
}
if
(
typeof
config
.
title
===
'number'
)
{
config
.
title
=
config
.
title
.
toString
()
}
if
(
typeof
config
.
content
===
'number'
)
{
config
.
content
=
config
.
content
.
toString
()
}
return
config
}
_getDelegateConfig
()
{
const
config
=
{}
for
(
const
key
in
this
.
_config
)
{
if
(
this
.
constructor
.
Default
[
key
]
!==
this
.
_config
[
key
])
{
config
[
key
]
=
this
.
_config
[
key
]
}
}
config
.
selector
=
false
config
.
trigger
=
'manual'
// In the future can be replaced with:
// const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])
// `Object.fromEntries(keysWithDifferentValues)`
return
config
}
_disposePopper
()
{
if
(
this
.
_popper
)
{
this
.
_popper
.
destroy
()
this
.
_popper
=
null
}
if
(
this
.
tip
)
{
this
.
tip
.
remove
()
this
.
tip
=
null
}
}
// Static
static
jQueryInterface
(
config
)
{
return
this
.
each
(
function
()
{
const
data
=
Tooltip
.
getOrCreateInstance
(
this
,
config
)
if
(
typeof
config
!==
'string'
)
{
return
}
if
(
typeof
data
[
config
]
===
'undefined'
)
{
throw
new
TypeError
(
`
No
method
named
"${config}"
`
)
}
data
[
config
]()
})
}
}
/**
* jQuery
*/
defineJQueryPlugin
(
Tooltip
)
export
default
Tooltip
Event Timeline
Log In to Comment