@openeuropa/bcl-bootstrap 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bootstrap-icons.svg +1 -1
  2. package/icons/apple.svg +2 -2
  3. package/icons/boombox-fill.svg +2 -2
  4. package/icons/cup-fill.svg +1 -1
  5. package/icons/cup.svg +1 -1
  6. package/js/dist/alert.js +3 -12
  7. package/js/dist/alert.js.map +1 -1
  8. package/js/dist/base-component.js +32 -18
  9. package/js/dist/base-component.js.map +1 -1
  10. package/js/dist/button.js +3 -12
  11. package/js/dist/button.js.map +1 -1
  12. package/js/dist/carousel.js +207 -307
  13. package/js/dist/carousel.js.map +1 -1
  14. package/js/dist/collapse.js +57 -88
  15. package/js/dist/collapse.js.map +1 -1
  16. package/js/dist/dom/data.js +1 -3
  17. package/js/dist/dom/data.js.map +1 -1
  18. package/js/dist/dom/event-handler.js +87 -106
  19. package/js/dist/dom/event-handler.js.map +1 -1
  20. package/js/dist/dom/manipulator.js +21 -25
  21. package/js/dist/dom/manipulator.js.map +1 -1
  22. package/js/dist/dom/selector-engine.js +11 -10
  23. package/js/dist/dom/selector-engine.js.map +1 -1
  24. package/js/dist/dropdown.js +83 -115
  25. package/js/dist/dropdown.js.map +1 -1
  26. package/js/dist/modal.js +95 -152
  27. package/js/dist/modal.js.map +1 -1
  28. package/js/dist/offcanvas.js +75 -58
  29. package/js/dist/offcanvas.js.map +1 -1
  30. package/js/dist/popover.js +29 -56
  31. package/js/dist/popover.js.map +1 -1
  32. package/js/dist/scrollspy.js +176 -125
  33. package/js/dist/scrollspy.js.map +1 -1
  34. package/js/dist/tab.js +207 -92
  35. package/js/dist/tab.js.map +1 -1
  36. package/js/dist/toast.js +23 -37
  37. package/js/dist/toast.js.map +1 -1
  38. package/js/dist/tooltip.js +259 -348
  39. package/js/dist/tooltip.js.map +1 -1
  40. package/js/dist/util/backdrop.js +62 -39
  41. package/js/dist/util/backdrop.js.map +1 -1
  42. package/js/dist/util/component-functions.js +1 -1
  43. package/js/dist/util/component-functions.js.map +1 -1
  44. package/js/dist/util/config.js +75 -0
  45. package/js/dist/util/config.js.map +1 -0
  46. package/js/dist/util/focustrap.js +41 -34
  47. package/js/dist/util/focustrap.js.map +1 -1
  48. package/js/dist/util/index.js +56 -52
  49. package/js/dist/util/index.js.map +1 -1
  50. package/js/dist/util/sanitizer.js +12 -19
  51. package/js/dist/util/sanitizer.js.map +1 -1
  52. package/js/dist/util/scrollbar.js +49 -34
  53. package/js/dist/util/scrollbar.js.map +1 -1
  54. package/js/dist/util/swipe.js +151 -0
  55. package/js/dist/util/swipe.js.map +1 -0
  56. package/js/dist/util/template-factory.js +173 -0
  57. package/js/dist/util/template-factory.js.map +1 -0
  58. package/js/src/alert.js +3 -15
  59. package/js/src/base-component.js +28 -18
  60. package/js/src/button.js +3 -17
  61. package/js/src/carousel.js +203 -319
  62. package/js/src/collapse.js +61 -94
  63. package/js/src/dom/data.js +1 -3
  64. package/js/src/dom/event-handler.js +80 -108
  65. package/js/src/dom/manipulator.js +22 -31
  66. package/js/src/dom/selector-engine.js +10 -19
  67. package/js/src/dropdown.js +84 -138
  68. package/js/src/modal.js +94 -158
  69. package/js/src/offcanvas.js +72 -61
  70. package/js/src/popover.js +31 -62
  71. package/js/src/scrollspy.js +166 -171
  72. package/js/src/tab.js +193 -110
  73. package/js/src/toast.js +19 -41
  74. package/js/src/tooltip.js +259 -371
  75. package/js/src/util/backdrop.js +55 -36
  76. package/js/src/util/component-functions.js +1 -1
  77. package/js/src/util/config.js +66 -0
  78. package/js/src/util/focustrap.js +38 -28
  79. package/js/src/util/index.js +67 -64
  80. package/js/src/util/sanitizer.js +11 -19
  81. package/js/src/util/scrollbar.js +47 -30
  82. package/js/src/util/swipe.js +146 -0
  83. package/js/src/util/template-factory.js +160 -0
  84. package/package.json +4 -4
  85. package/scss/_accordion.scss +52 -24
  86. package/scss/_alert.scss +18 -4
  87. package/scss/_badge.scss +14 -5
  88. package/scss/_breadcrumb.scss +22 -10
  89. package/scss/_button-group.scss +3 -0
  90. package/scss/_buttons.scss +97 -22
  91. package/scss/_card.scss +55 -37
  92. package/scss/_close.scss +1 -1
  93. package/scss/_containers.scss +1 -1
  94. package/scss/_dropdown.scss +83 -75
  95. package/scss/_functions.scss +7 -7
  96. package/scss/_grid.scss +3 -3
  97. package/scss/_helpers.scss +1 -0
  98. package/scss/_list-group.scss +44 -27
  99. package/scss/_maps.scss +54 -0
  100. package/scss/_modal.scss +71 -43
  101. package/scss/_nav.scss +53 -20
  102. package/scss/_navbar.scss +91 -150
  103. package/scss/_offcanvas.scss +119 -59
  104. package/scss/_pagination.scss +66 -21
  105. package/scss/_placeholders.scss +1 -1
  106. package/scss/_popover.scss +90 -52
  107. package/scss/_progress.scss +20 -9
  108. package/scss/_reboot.scss +25 -40
  109. package/scss/_root.scss +40 -21
  110. package/scss/_spinners.scss +38 -22
  111. package/scss/_tables.scss +32 -23
  112. package/scss/_toasts.scss +35 -16
  113. package/scss/_tooltip.scss +61 -56
  114. package/scss/_type.scss +2 -0
  115. package/scss/_utilities.scss +43 -26
  116. package/scss/_variables.scss +113 -121
  117. package/scss/bootstrap-grid.scss +3 -6
  118. package/scss/bootstrap-reboot.scss +3 -7
  119. package/scss/bootstrap-utilities.scss +3 -6
  120. package/scss/bootstrap.scss +4 -6
  121. package/scss/forms/_floating-labels.scss +14 -3
  122. package/scss/forms/_form-check.scss +28 -5
  123. package/scss/forms/_form-control.scss +12 -37
  124. package/scss/forms/_form-select.scss +0 -1
  125. package/scss/forms/_input-group.scss +15 -7
  126. package/scss/helpers/_color-bg.scss +10 -0
  127. package/scss/helpers/_colored-links.scss +2 -2
  128. package/scss/helpers/_position.scss +7 -1
  129. package/scss/helpers/_ratio.scss +2 -2
  130. package/scss/helpers/_vr.scss +1 -1
  131. package/scss/mixins/_alert.scss +7 -3
  132. package/scss/mixins/_banner.scss +9 -0
  133. package/scss/mixins/_breakpoints.scss +8 -8
  134. package/scss/mixins/_buttons.scss +32 -95
  135. package/scss/mixins/_container.scss +4 -2
  136. package/scss/mixins/_forms.scss +8 -0
  137. package/scss/mixins/_gradients.scss +1 -1
  138. package/scss/mixins/_grid.scss +12 -12
  139. package/scss/mixins/_pagination.scss +4 -25
  140. package/scss/mixins/_reset-text.scss +1 -1
  141. package/scss/mixins/_table-variants.scss +12 -9
  142. package/scss/mixins/_utilities.scss +12 -4
package/js/src/tooltip.js CHANGED
@@ -1,60 +1,49 @@
1
1
  /**
2
2
  * --------------------------------------------------------------------------
3
- * Bootstrap (v5.1.3): tooltip.js
3
+ * Bootstrap (v5.2.0): tooltip.js
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  * --------------------------------------------------------------------------
6
6
  */
7
7
 
8
8
  import * as Popper from '@popperjs/core'
9
-
10
- import {
11
- defineJQueryPlugin,
12
- findShadowRoot,
13
- getElement,
14
- getUID,
15
- isElement,
16
- isRTL,
17
- noop,
18
- typeCheckConfig
19
- } from './util/index'
20
- import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer'
21
- import Data from './dom/data'
9
+ import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'
10
+ import { DefaultAllowlist } from './util/sanitizer'
22
11
  import EventHandler from './dom/event-handler'
23
12
  import Manipulator from './dom/manipulator'
24
- import SelectorEngine from './dom/selector-engine'
25
13
  import BaseComponent from './base-component'
14
+ import TemplateFactory from './util/template-factory'
26
15
 
27
16
  /**
28
- * ------------------------------------------------------------------------
29
17
  * Constants
30
- * ------------------------------------------------------------------------
31
18
  */
32
19
 
33
20
  const NAME = 'tooltip'
34
- const DATA_KEY = 'bs.tooltip'
35
- const EVENT_KEY = `.${DATA_KEY}`
36
- const CLASS_PREFIX = 'bs-tooltip'
37
21
  const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])
38
22
 
39
- const DefaultType = {
40
- animation: 'boolean',
41
- template: 'string',
42
- title: '(string|element|function)',
43
- trigger: 'string',
44
- delay: '(number|object)',
45
- html: 'boolean',
46
- selector: '(string|boolean)',
47
- placement: '(string|function)',
48
- offset: '(array|string|function)',
49
- container: '(string|element|boolean)',
50
- fallbackPlacements: 'array',
51
- boundary: '(string|element)',
52
- customClass: '(string|function)',
53
- sanitize: 'boolean',
54
- sanitizeFn: '(null|function)',
55
- allowList: 'object',
56
- popperConfig: '(null|object|function)'
57
- }
23
+ const CLASS_NAME_FADE = 'fade'
24
+ const CLASS_NAME_MODAL = 'modal'
25
+ const CLASS_NAME_SHOW = 'show'
26
+
27
+ const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'
28
+ const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`
29
+
30
+ const EVENT_MODAL_HIDE = 'hide.bs.modal'
31
+
32
+ const TRIGGER_HOVER = 'hover'
33
+ const TRIGGER_FOCUS = 'focus'
34
+ const TRIGGER_CLICK = 'click'
35
+ const TRIGGER_MANUAL = 'manual'
36
+
37
+ const EVENT_HIDE = 'hide'
38
+ const EVENT_HIDDEN = 'hidden'
39
+ const EVENT_SHOW = 'show'
40
+ const EVENT_SHOWN = 'shown'
41
+ const EVENT_INSERTED = 'inserted'
42
+ const EVENT_CLICK = 'click'
43
+ const EVENT_FOCUSIN = 'focusin'
44
+ const EVENT_FOCUSOUT = 'focusout'
45
+ const EVENT_MOUSEENTER = 'mouseenter'
46
+ const EVENT_MOUSELEAVE = 'mouseleave'
58
47
 
59
48
  const AttachmentMap = {
60
49
  AUTO: 'auto',
@@ -65,62 +54,50 @@ const AttachmentMap = {
65
54
  }
66
55
 
67
56
  const Default = {
57
+ allowList: DefaultAllowlist,
68
58
  animation: true,
69
- template: '<div class="tooltip" role="tooltip">' +
70
- '<div class="tooltip-arrow"></div>' +
71
- '<div class="tooltip-inner"></div>' +
72
- '</div>',
73
- trigger: 'hover focus',
74
- title: '',
59
+ boundary: 'clippingParents',
60
+ container: false,
61
+ customClass: '',
75
62
  delay: 0,
63
+ fallbackPlacements: ['top', 'right', 'bottom', 'left'],
76
64
  html: false,
77
- selector: false,
78
- placement: 'top',
79
65
  offset: [0, 0],
80
- container: false,
81
- fallbackPlacements: ['top', 'right', 'bottom', 'left'],
82
- boundary: 'clippingParents',
83
- customClass: '',
66
+ placement: 'top',
67
+ popperConfig: null,
84
68
  sanitize: true,
85
69
  sanitizeFn: null,
86
- allowList: DefaultAllowlist,
87
- popperConfig: null
70
+ selector: false,
71
+ template: '<div class="tooltip" role="tooltip">' +
72
+ '<div class="tooltip-arrow"></div>' +
73
+ '<div class="tooltip-inner"></div>' +
74
+ '</div>',
75
+ title: '',
76
+ trigger: 'hover focus'
88
77
  }
89
78
 
90
- const Event = {
91
- HIDE: `hide${EVENT_KEY}`,
92
- HIDDEN: `hidden${EVENT_KEY}`,
93
- SHOW: `show${EVENT_KEY}`,
94
- SHOWN: `shown${EVENT_KEY}`,
95
- INSERTED: `inserted${EVENT_KEY}`,
96
- CLICK: `click${EVENT_KEY}`,
97
- FOCUSIN: `focusin${EVENT_KEY}`,
98
- FOCUSOUT: `focusout${EVENT_KEY}`,
99
- MOUSEENTER: `mouseenter${EVENT_KEY}`,
100
- MOUSELEAVE: `mouseleave${EVENT_KEY}`
79
+ const DefaultType = {
80
+ allowList: 'object',
81
+ animation: 'boolean',
82
+ boundary: '(string|element)',
83
+ container: '(string|element|boolean)',
84
+ customClass: '(string|function)',
85
+ delay: '(number|object)',
86
+ fallbackPlacements: 'array',
87
+ html: 'boolean',
88
+ offset: '(array|string|function)',
89
+ placement: '(string|function)',
90
+ popperConfig: '(null|object|function)',
91
+ sanitize: 'boolean',
92
+ sanitizeFn: '(null|function)',
93
+ selector: '(string|boolean)',
94
+ template: 'string',
95
+ title: '(string|element|function)',
96
+ trigger: 'string'
101
97
  }
102
98
 
103
- const CLASS_NAME_FADE = 'fade'
104
- const CLASS_NAME_MODAL = 'modal'
105
- const CLASS_NAME_SHOW = 'show'
106
-
107
- const HOVER_STATE_SHOW = 'show'
108
- const HOVER_STATE_OUT = 'out'
109
-
110
- const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'
111
- const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`
112
-
113
- const EVENT_MODAL_HIDE = 'hide.bs.modal'
114
-
115
- const TRIGGER_HOVER = 'hover'
116
- const TRIGGER_FOCUS = 'focus'
117
- const TRIGGER_CLICK = 'click'
118
- const TRIGGER_MANUAL = 'manual'
119
-
120
99
  /**
121
- * ------------------------------------------------------------------------
122
- * Class Definition
123
- * ------------------------------------------------------------------------
100
+ * Class definition
124
101
  */
125
102
 
126
103
  class Tooltip extends BaseComponent {
@@ -129,42 +106,37 @@ class Tooltip extends BaseComponent {
129
106
  throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)')
130
107
  }
131
108
 
132
- super(element)
109
+ super(element, config)
133
110
 
134
- // private
111
+ // Private
135
112
  this._isEnabled = true
136
113
  this._timeout = 0
137
- this._hoverState = ''
114
+ this._isHovered = false
138
115
  this._activeTrigger = {}
139
116
  this._popper = null
117
+ this._templateFactory = null
118
+ this._newContent = null
140
119
 
141
120
  // Protected
142
- this._config = this._getConfig(config)
143
121
  this.tip = null
144
122
 
145
123
  this._setListeners()
146
124
  }
147
125
 
148
126
  // Getters
149
-
150
127
  static get Default() {
151
128
  return Default
152
129
  }
153
130
 
154
- static get NAME() {
155
- return NAME
156
- }
157
-
158
- static get Event() {
159
- return Event
160
- }
161
-
162
131
  static get DefaultType() {
163
132
  return DefaultType
164
133
  }
165
134
 
166
- // Public
135
+ static get NAME() {
136
+ return NAME
137
+ }
167
138
 
139
+ // Public
168
140
  enable() {
169
141
  this._isEnabled = true
170
142
  }
@@ -188,18 +160,20 @@ class Tooltip extends BaseComponent {
188
160
  context._activeTrigger.click = !context._activeTrigger.click
189
161
 
190
162
  if (context._isWithActiveTrigger()) {
191
- context._enter(null, context)
163
+ context._enter()
192
164
  } else {
193
- context._leave(null, context)
194
- }
195
- } else {
196
- if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) {
197
- this._leave(null, this)
198
- return
165
+ context._leave()
199
166
  }
200
167
 
201
- this._enter(null, this)
168
+ return
202
169
  }
170
+
171
+ if (this._isShown()) {
172
+ this._leave()
173
+ return
174
+ }
175
+
176
+ this._enter()
203
177
  }
204
178
 
205
179
  dispose() {
@@ -220,241 +194,213 @@ class Tooltip extends BaseComponent {
220
194
  throw new Error('Please use show on visible elements')
221
195
  }
222
196
 
223
- if (!(this.isWithContent() && this._isEnabled)) {
197
+ if (!(this._isWithContent() && this._isEnabled)) {
224
198
  return
225
199
  }
226
200
 
227
- const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW)
201
+ const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))
228
202
  const shadowRoot = findShadowRoot(this._element)
229
- const isInTheDom = shadowRoot === null ?
230
- this._element.ownerDocument.documentElement.contains(this._element) :
231
- shadowRoot.contains(this._element)
203
+ const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)
232
204
 
233
205
  if (showEvent.defaultPrevented || !isInTheDom) {
234
206
  return
235
207
  }
236
208
 
237
- // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title`
238
- // This will be removed later in favor of a `setContent` method
239
- if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) {
240
- this._disposePopper()
209
+ // todo v6 remove this OR make it optional
210
+ if (this.tip) {
241
211
  this.tip.remove()
242
212
  this.tip = null
243
213
  }
244
214
 
245
- const tip = this.getTipElement()
246
- const tipId = getUID(this.constructor.NAME)
247
-
248
- tip.setAttribute('id', tipId)
249
- this._element.setAttribute('aria-describedby', tipId)
250
-
251
- if (this._config.animation) {
252
- tip.classList.add(CLASS_NAME_FADE)
253
- }
254
-
255
- const placement = typeof this._config.placement === 'function' ?
256
- this._config.placement.call(this, tip, this._element) :
257
- this._config.placement
215
+ const tip = this._getTipElement()
258
216
 
259
- const attachment = this._getAttachment(placement)
260
- this._addAttachmentClass(attachment)
217
+ this._element.setAttribute('aria-describedby', tip.getAttribute('id'))
261
218
 
262
219
  const { container } = this._config
263
- Data.set(tip, this.constructor.DATA_KEY, this)
264
220
 
265
221
  if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
266
222
  container.append(tip)
267
- EventHandler.trigger(this._element, this.constructor.Event.INSERTED)
223
+ EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))
268
224
  }
269
225
 
270
226
  if (this._popper) {
271
227
  this._popper.update()
272
228
  } else {
273
- this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
229
+ this._popper = this._createPopper(tip)
274
230
  }
275
231
 
276
232
  tip.classList.add(CLASS_NAME_SHOW)
277
233
 
278
- const customClass = this._resolvePossibleFunction(this._config.customClass)
279
- if (customClass) {
280
- tip.classList.add(...customClass.split(' '))
281
- }
282
-
283
234
  // If this is a touch-enabled device we add extra
284
235
  // empty mouseover listeners to the body's immediate children;
285
236
  // only needed because of broken event delegation on iOS
286
237
  // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
287
238
  if ('ontouchstart' in document.documentElement) {
288
- [].concat(...document.body.children).forEach(element => {
239
+ for (const element of [].concat(...document.body.children)) {
289
240
  EventHandler.on(element, 'mouseover', noop)
290
- })
241
+ }
291
242
  }
292
243
 
293
244
  const complete = () => {
294
- const prevHoverState = this._hoverState
245
+ const previousHoverState = this._isHovered
295
246
 
296
- this._hoverState = null
297
- EventHandler.trigger(this._element, this.constructor.Event.SHOWN)
247
+ this._isHovered = false
248
+ EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))
298
249
 
299
- if (prevHoverState === HOVER_STATE_OUT) {
300
- this._leave(null, this)
250
+ if (previousHoverState) {
251
+ this._leave()
301
252
  }
302
253
  }
303
254
 
304
- const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE)
305
- this._queueCallback(complete, this.tip, isAnimated)
255
+ this._queueCallback(complete, this.tip, this._isAnimated())
306
256
  }
307
257
 
308
258
  hide() {
309
- if (!this._popper) {
259
+ if (!this._isShown()) {
310
260
  return
311
261
  }
312
262
 
313
- const tip = this.getTipElement()
314
- const complete = () => {
315
- if (this._isWithActiveTrigger()) {
316
- return
317
- }
318
-
319
- if (this._hoverState !== HOVER_STATE_SHOW) {
320
- tip.remove()
321
- }
322
-
323
- this._cleanTipClass()
324
- this._element.removeAttribute('aria-describedby')
325
- EventHandler.trigger(this._element, this.constructor.Event.HIDDEN)
326
-
327
- this._disposePopper()
328
- }
329
-
330
- const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE)
263
+ const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))
331
264
  if (hideEvent.defaultPrevented) {
332
265
  return
333
266
  }
334
267
 
268
+ const tip = this._getTipElement()
335
269
  tip.classList.remove(CLASS_NAME_SHOW)
336
270
 
337
271
  // If this is a touch-enabled device we remove the extra
338
272
  // empty mouseover listeners we added for iOS support
339
273
  if ('ontouchstart' in document.documentElement) {
340
- [].concat(...document.body.children)
341
- .forEach(element => EventHandler.off(element, 'mouseover', noop))
274
+ for (const element of [].concat(...document.body.children)) {
275
+ EventHandler.off(element, 'mouseover', noop)
276
+ }
342
277
  }
343
278
 
344
279
  this._activeTrigger[TRIGGER_CLICK] = false
345
280
  this._activeTrigger[TRIGGER_FOCUS] = false
346
281
  this._activeTrigger[TRIGGER_HOVER] = false
282
+ this._isHovered = false
283
+
284
+ const complete = () => {
285
+ if (this._isWithActiveTrigger()) {
286
+ return
287
+ }
347
288
 
348
- const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE)
349
- this._queueCallback(complete, this.tip, isAnimated)
350
- this._hoverState = ''
289
+ if (!this._isHovered) {
290
+ tip.remove()
291
+ }
292
+
293
+ this._element.removeAttribute('aria-describedby')
294
+ EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))
295
+
296
+ this._disposePopper()
297
+ }
298
+
299
+ this._queueCallback(complete, this.tip, this._isAnimated())
351
300
  }
352
301
 
353
302
  update() {
354
- if (this._popper !== null) {
303
+ if (this._popper) {
355
304
  this._popper.update()
356
305
  }
357
306
  }
358
307
 
359
308
  // Protected
360
-
361
- isWithContent() {
362
- return Boolean(this.getTitle())
309
+ _isWithContent() {
310
+ return Boolean(this._getTitle())
363
311
  }
364
312
 
365
- getTipElement() {
366
- if (this.tip) {
367
- return this.tip
313
+ _getTipElement() {
314
+ if (!this.tip) {
315
+ this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())
368
316
  }
369
317
 
370
- const element = document.createElement('div')
371
- element.innerHTML = this._config.template
372
-
373
- const tip = element.children[0]
374
- this.setContent(tip)
375
- tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
376
-
377
- this.tip = tip
378
318
  return this.tip
379
319
  }
380
320
 
381
- setContent(tip) {
382
- this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER)
383
- }
384
-
385
- _sanitizeAndSetContent(template, content, selector) {
386
- const templateElement = SelectorEngine.findOne(selector, template)
321
+ _createTipElement(content) {
322
+ const tip = this._getTemplateFactory(content).toHtml()
387
323
 
388
- if (!content && templateElement) {
389
- templateElement.remove()
390
- return
324
+ // todo: remove this check on v6
325
+ if (!tip) {
326
+ return null
391
327
  }
392
328
 
393
- // we use append for html objects to maintain js events
394
- this.setElementContent(templateElement, content)
395
- }
396
-
397
- setElementContent(element, content) {
398
- if (element === null) {
399
- return
400
- }
329
+ tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
330
+ // todo: on v6 the following can be achieved with CSS only
331
+ tip.classList.add(`bs-${this.constructor.NAME}-auto`)
401
332
 
402
- if (isElement(content)) {
403
- content = getElement(content)
333
+ const tipId = getUID(this.constructor.NAME).toString()
404
334
 
405
- // content is a DOM node or a jQuery
406
- if (this._config.html) {
407
- if (content.parentNode !== element) {
408
- element.innerHTML = ''
409
- element.append(content)
410
- }
411
- } else {
412
- element.textContent = content.textContent
413
- }
335
+ tip.setAttribute('id', tipId)
414
336
 
415
- return
337
+ if (this._isAnimated()) {
338
+ tip.classList.add(CLASS_NAME_FADE)
416
339
  }
417
340
 
418
- if (this._config.html) {
419
- if (this._config.sanitize) {
420
- content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn)
421
- }
341
+ return tip
342
+ }
422
343
 
423
- element.innerHTML = content
424
- } else {
425
- element.textContent = content
344
+ setContent(content) {
345
+ this._newContent = content
346
+ if (this._isShown()) {
347
+ this._disposePopper()
348
+ this.show()
426
349
  }
427
350
  }
428
351
 
429
- getTitle() {
430
- const title = this._element.getAttribute('data-bs-original-title') || this._config.title
352
+ _getTemplateFactory(content) {
353
+ if (this._templateFactory) {
354
+ this._templateFactory.changeContent(content)
355
+ } else {
356
+ this._templateFactory = new TemplateFactory({
357
+ ...this._config,
358
+ // the `content` var has to be after `this._config`
359
+ // to override config.content in case of popover
360
+ content,
361
+ extraClass: this._resolvePossibleFunction(this._config.customClass)
362
+ })
363
+ }
431
364
 
432
- return this._resolvePossibleFunction(title)
365
+ return this._templateFactory
433
366
  }
434
367
 
435
- updateAttachment(attachment) {
436
- if (attachment === 'right') {
437
- return 'end'
438
- }
439
-
440
- if (attachment === 'left') {
441
- return 'start'
368
+ _getContentForTemplate() {
369
+ return {
370
+ [SELECTOR_TOOLTIP_INNER]: this._getTitle()
442
371
  }
372
+ }
443
373
 
444
- return attachment
374
+ _getTitle() {
375
+ return this._resolvePossibleFunction(this._config.title) || this._config.originalTitle
445
376
  }
446
377
 
447
378
  // Private
379
+ _initializeOnDelegatedTarget(event) {
380
+ return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())
381
+ }
382
+
383
+ _isAnimated() {
384
+ return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))
385
+ }
448
386
 
449
- _initializeOnDelegatedTarget(event, context) {
450
- return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())
387
+ _isShown() {
388
+ return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)
389
+ }
390
+
391
+ _createPopper(tip) {
392
+ const placement = typeof this._config.placement === 'function' ?
393
+ this._config.placement.call(this, tip, this._element) :
394
+ this._config.placement
395
+ const attachment = AttachmentMap[placement.toUpperCase()]
396
+ return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
451
397
  }
452
398
 
453
399
  _getOffset() {
454
400
  const { offset } = this._config
455
401
 
456
402
  if (typeof offset === 'string') {
457
- return offset.split(',').map(val => Number.parseInt(val, 10))
403
+ return offset.split(',').map(value => Number.parseInt(value, 10))
458
404
  }
459
405
 
460
406
  if (typeof offset === 'function') {
@@ -464,8 +410,8 @@ class Tooltip extends BaseComponent {
464
410
  return offset
465
411
  }
466
412
 
467
- _resolvePossibleFunction(content) {
468
- return typeof content === 'function' ? content.call(this._element) : content
413
+ _resolvePossibleFunction(arg) {
414
+ return typeof arg === 'function' ? arg.call(this._element) : arg
469
415
  }
470
416
 
471
417
  _getPopperConfig(attachment) {
@@ -497,17 +443,16 @@ class Tooltip extends BaseComponent {
497
443
  }
498
444
  },
499
445
  {
500
- name: 'onChange',
446
+ name: 'preSetPlacement',
501
447
  enabled: true,
502
- phase: 'afterWrite',
503
- fn: data => this._handlePopperPlacementChange(data)
504
- }
505
- ],
506
- onFirstUpdate: data => {
507
- if (data.options.placement !== data.placement) {
508
- this._handlePopperPlacementChange(data)
448
+ phase: 'beforeMain',
449
+ fn: data => {
450
+ // Pre-set Popper's placement attribute in order to read the arrow sizes properly.
451
+ // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
452
+ this._getTipElement().setAttribute('data-popper-placement', data.state.placement)
453
+ }
509
454
  }
510
- }
455
+ ]
511
456
  }
512
457
 
513
458
  return {
@@ -516,32 +461,34 @@ class Tooltip extends BaseComponent {
516
461
  }
517
462
  }
518
463
 
519
- _addAttachmentClass(attachment) {
520
- this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`)
521
- }
522
-
523
- _getAttachment(placement) {
524
- return AttachmentMap[placement.toUpperCase()]
525
- }
526
-
527
464
  _setListeners() {
528
465
  const triggers = this._config.trigger.split(' ')
529
466
 
530
- triggers.forEach(trigger => {
467
+ for (const trigger of triggers) {
531
468
  if (trigger === 'click') {
532
- EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event))
469
+ EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => this.toggle(event))
533
470
  } else if (trigger !== TRIGGER_MANUAL) {
534
471
  const eventIn = trigger === TRIGGER_HOVER ?
535
- this.constructor.Event.MOUSEENTER :
536
- this.constructor.Event.FOCUSIN
472
+ this.constructor.eventName(EVENT_MOUSEENTER) :
473
+ this.constructor.eventName(EVENT_FOCUSIN)
537
474
  const eventOut = trigger === TRIGGER_HOVER ?
538
- this.constructor.Event.MOUSELEAVE :
539
- this.constructor.Event.FOCUSOUT
540
-
541
- EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event))
542
- EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event))
475
+ this.constructor.eventName(EVENT_MOUSELEAVE) :
476
+ this.constructor.eventName(EVENT_FOCUSOUT)
477
+
478
+ EventHandler.on(this._element, eventIn, this._config.selector, event => {
479
+ const context = this._initializeOnDelegatedTarget(event)
480
+ context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true
481
+ context._enter()
482
+ })
483
+ EventHandler.on(this._element, eventOut, this._config.selector, event => {
484
+ const context = this._initializeOnDelegatedTarget(event)
485
+ context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =
486
+ context._element.contains(event.relatedTarget)
487
+
488
+ context._leave()
489
+ })
543
490
  }
544
- })
491
+ }
545
492
 
546
493
  this._hideModalHandler = () => {
547
494
  if (this._element) {
@@ -563,103 +510,77 @@ class Tooltip extends BaseComponent {
563
510
  }
564
511
 
565
512
  _fixTitle() {
566
- const title = this._element.getAttribute('title')
567
- const originalTitleType = typeof this._element.getAttribute('data-bs-original-title')
568
-
569
- if (title || originalTitleType !== 'string') {
570
- this._element.setAttribute('data-bs-original-title', title || '')
571
- if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) {
572
- this._element.setAttribute('aria-label', title)
573
- }
574
-
575
- this._element.setAttribute('title', '')
576
- }
577
- }
513
+ const title = this._config.originalTitle
578
514
 
579
- _enter(event, context) {
580
- context = this._initializeOnDelegatedTarget(event, context)
581
-
582
- if (event) {
583
- context._activeTrigger[
584
- event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER
585
- ] = true
586
- }
587
-
588
- if (context.getTipElement().classList.contains(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) {
589
- context._hoverState = HOVER_STATE_SHOW
515
+ if (!title) {
590
516
  return
591
517
  }
592
518
 
593
- clearTimeout(context._timeout)
594
-
595
- context._hoverState = HOVER_STATE_SHOW
596
-
597
- if (!context._config.delay || !context._config.delay.show) {
598
- context.show()
599
- return
519
+ if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {
520
+ this._element.setAttribute('aria-label', title)
600
521
  }
601
522
 
602
- context._timeout = setTimeout(() => {
603
- if (context._hoverState === HOVER_STATE_SHOW) {
604
- context.show()
605
- }
606
- }, context._config.delay.show)
523
+ this._element.removeAttribute('title')
607
524
  }
608
525
 
609
- _leave(event, context) {
610
- context = this._initializeOnDelegatedTarget(event, context)
611
-
612
- if (event) {
613
- context._activeTrigger[
614
- event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER
615
- ] = context._element.contains(event.relatedTarget)
616
- }
617
-
618
- if (context._isWithActiveTrigger()) {
526
+ _enter() {
527
+ if (this._isShown() || this._isHovered) {
528
+ this._isHovered = true
619
529
  return
620
530
  }
621
531
 
622
- clearTimeout(context._timeout)
532
+ this._isHovered = true
623
533
 
624
- context._hoverState = HOVER_STATE_OUT
534
+ this._setTimeout(() => {
535
+ if (this._isHovered) {
536
+ this.show()
537
+ }
538
+ }, this._config.delay.show)
539
+ }
625
540
 
626
- if (!context._config.delay || !context._config.delay.hide) {
627
- context.hide()
541
+ _leave() {
542
+ if (this._isWithActiveTrigger()) {
628
543
  return
629
544
  }
630
545
 
631
- context._timeout = setTimeout(() => {
632
- if (context._hoverState === HOVER_STATE_OUT) {
633
- context.hide()
546
+ this._isHovered = false
547
+
548
+ this._setTimeout(() => {
549
+ if (!this._isHovered) {
550
+ this.hide()
634
551
  }
635
- }, context._config.delay.hide)
552
+ }, this._config.delay.hide)
636
553
  }
637
554
 
638
- _isWithActiveTrigger() {
639
- for (const trigger in this._activeTrigger) {
640
- if (this._activeTrigger[trigger]) {
641
- return true
642
- }
643
- }
555
+ _setTimeout(handler, timeout) {
556
+ clearTimeout(this._timeout)
557
+ this._timeout = setTimeout(handler, timeout)
558
+ }
644
559
 
645
- return false
560
+ _isWithActiveTrigger() {
561
+ return Object.values(this._activeTrigger).includes(true)
646
562
  }
647
563
 
648
564
  _getConfig(config) {
649
565
  const dataAttributes = Manipulator.getDataAttributes(this._element)
650
566
 
651
- Object.keys(dataAttributes).forEach(dataAttr => {
652
- if (DISALLOWED_ATTRIBUTES.has(dataAttr)) {
653
- delete dataAttributes[dataAttr]
567
+ for (const dataAttribute of Object.keys(dataAttributes)) {
568
+ if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {
569
+ delete dataAttributes[dataAttribute]
654
570
  }
655
- })
571
+ }
656
572
 
657
573
  config = {
658
- ...this.constructor.Default,
659
574
  ...dataAttributes,
660
575
  ...(typeof config === 'object' && config ? config : {})
661
576
  }
577
+ config = this._mergeConfigObj(config)
578
+ config = this._configAfterMerge(config)
579
+ this._typeCheckConfig(config)
580
+ return config
581
+ }
662
582
 
583
+ _configAfterMerge(config) {
663
584
  config.container = config.container === false ? document.body : getElement(config.container)
664
585
 
665
586
  if (typeof config.delay === 'number') {
@@ -669,6 +590,7 @@ class Tooltip extends BaseComponent {
669
590
  }
670
591
  }
671
592
 
593
+ config.originalTitle = this._element.getAttribute('title') || ''
672
594
  if (typeof config.title === 'number') {
673
595
  config.title = config.title.toString()
674
596
  }
@@ -677,12 +599,6 @@ class Tooltip extends BaseComponent {
677
599
  config.content = config.content.toString()
678
600
  }
679
601
 
680
- typeCheckConfig(NAME, config, this.constructor.DefaultType)
681
-
682
- if (config.sanitize) {
683
- config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn)
684
- }
685
-
686
602
  return config
687
603
  }
688
604
 
@@ -701,32 +617,6 @@ class Tooltip extends BaseComponent {
701
617
  return config
702
618
  }
703
619
 
704
- _cleanTipClass() {
705
- const tip = this.getTipElement()
706
- const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g')
707
- const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex)
708
- if (tabClass !== null && tabClass.length > 0) {
709
- tabClass.map(token => token.trim())
710
- .forEach(tClass => tip.classList.remove(tClass))
711
- }
712
- }
713
-
714
- _getBasicClassPrefix() {
715
- return CLASS_PREFIX
716
- }
717
-
718
- _handlePopperPlacementChange(popperData) {
719
- const { state } = popperData
720
-
721
- if (!state) {
722
- return
723
- }
724
-
725
- this.tip = state.elements.popper
726
- this._cleanTipClass()
727
- this._addAttachmentClass(this._getAttachment(state.placement))
728
- }
729
-
730
620
  _disposePopper() {
731
621
  if (this._popper) {
732
622
  this._popper.destroy()
@@ -735,27 +625,25 @@ class Tooltip extends BaseComponent {
735
625
  }
736
626
 
737
627
  // Static
738
-
739
628
  static jQueryInterface(config) {
740
629
  return this.each(function () {
741
630
  const data = Tooltip.getOrCreateInstance(this, config)
742
631
 
743
- if (typeof config === 'string') {
744
- if (typeof data[config] === 'undefined') {
745
- throw new TypeError(`No method named "${config}"`)
746
- }
632
+ if (typeof config !== 'string') {
633
+ return
634
+ }
747
635
 
748
- data[config]()
636
+ if (typeof data[config] === 'undefined') {
637
+ throw new TypeError(`No method named "${config}"`)
749
638
  }
639
+
640
+ data[config]()
750
641
  })
751
642
  }
752
643
  }
753
644
 
754
645
  /**
755
- * ------------------------------------------------------------------------
756
646
  * jQuery
757
- * ------------------------------------------------------------------------
758
- * add .Tooltip to jQuery only if jQuery is present
759
647
  */
760
648
 
761
649
  defineJQueryPlugin(Tooltip)