@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
@@ -1,25 +1,17 @@
1
1
  /**
2
2
  * --------------------------------------------------------------------------
3
- * Bootstrap (v5.1.3): scrollspy.js
3
+ * Bootstrap (v5.2.0): scrollspy.js
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  * --------------------------------------------------------------------------
6
6
  */
7
7
 
8
- import {
9
- defineJQueryPlugin,
10
- getElement,
11
- getSelectorFromElement,
12
- typeCheckConfig
13
- } from './util/index'
8
+ import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index'
14
9
  import EventHandler from './dom/event-handler'
15
- import Manipulator from './dom/manipulator'
16
10
  import SelectorEngine from './dom/selector-engine'
17
11
  import BaseComponent from './base-component'
18
12
 
19
13
  /**
20
- * ------------------------------------------------------------------------
21
14
  * Constants
22
- * ------------------------------------------------------------------------
23
15
  */
24
16
 
25
17
  const NAME = 'scrollspy'
@@ -27,234 +19,241 @@ const DATA_KEY = 'bs.scrollspy'
27
19
  const EVENT_KEY = `.${DATA_KEY}`
28
20
  const DATA_API_KEY = '.data-api'
29
21
 
30
- const Default = {
31
- offset: 10,
32
- method: 'auto',
33
- target: ''
34
- }
35
-
36
- const DefaultType = {
37
- offset: 'number',
38
- method: 'string',
39
- target: '(string|element)'
40
- }
41
-
42
22
  const EVENT_ACTIVATE = `activate${EVENT_KEY}`
43
- const EVENT_SCROLL = `scroll${EVENT_KEY}`
23
+ const EVENT_CLICK = `click${EVENT_KEY}`
44
24
  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
45
25
 
46
26
  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'
47
27
  const CLASS_NAME_ACTIVE = 'active'
48
28
 
49
29
  const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'
30
+ const SELECTOR_TARGET_LINKS = '[href]'
50
31
  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'
51
32
  const SELECTOR_NAV_LINKS = '.nav-link'
52
33
  const SELECTOR_NAV_ITEMS = '.nav-item'
53
34
  const SELECTOR_LIST_ITEMS = '.list-group-item'
54
- const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}`
35
+ const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`
55
36
  const SELECTOR_DROPDOWN = '.dropdown'
56
37
  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'
57
38
 
58
- const METHOD_OFFSET = 'offset'
59
- const METHOD_POSITION = 'position'
39
+ const Default = {
40
+ offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons
41
+ rootMargin: '0px 0px -25%',
42
+ smoothScroll: false,
43
+ target: null
44
+ }
45
+
46
+ const DefaultType = {
47
+ offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons
48
+ rootMargin: 'string',
49
+ smoothScroll: 'boolean',
50
+ target: 'element'
51
+ }
60
52
 
61
53
  /**
62
- * ------------------------------------------------------------------------
63
- * Class Definition
64
- * ------------------------------------------------------------------------
54
+ * Class definition
65
55
  */
66
56
 
67
57
  class ScrollSpy extends BaseComponent {
68
58
  constructor(element, config) {
69
- super(element)
70
- this._scrollElement = this._element.tagName === 'BODY' ? window : this._element
71
- this._config = this._getConfig(config)
72
- this._offsets = []
73
- this._targets = []
74
- this._activeTarget = null
75
- this._scrollHeight = 0
76
-
77
- EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process())
59
+ super(element, config)
78
60
 
79
- this.refresh()
80
- this._process()
61
+ // this._element is the observablesContainer and config.target the menu links wrapper
62
+ this._targetLinks = new Map()
63
+ this._observableSections = new Map()
64
+ this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element
65
+ this._activeTarget = null
66
+ this._observer = null
67
+ this._previousScrollData = {
68
+ visibleEntryTop: 0,
69
+ parentScrollTop: 0
70
+ }
71
+ this.refresh() // initialize
81
72
  }
82
73
 
83
74
  // Getters
84
-
85
75
  static get Default() {
86
76
  return Default
87
77
  }
88
78
 
79
+ static get DefaultType() {
80
+ return DefaultType
81
+ }
82
+
89
83
  static get NAME() {
90
84
  return NAME
91
85
  }
92
86
 
93
87
  // Public
94
-
95
88
  refresh() {
96
- const autoMethod = this._scrollElement === this._scrollElement.window ?
97
- METHOD_OFFSET :
98
- METHOD_POSITION
99
-
100
- const offsetMethod = this._config.method === 'auto' ?
101
- autoMethod :
102
- this._config.method
103
-
104
- const offsetBase = offsetMethod === METHOD_POSITION ?
105
- this._getScrollTop() :
106
- 0
107
-
108
- this._offsets = []
109
- this._targets = []
110
- this._scrollHeight = this._getScrollHeight()
111
-
112
- const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target)
113
-
114
- targets.map(element => {
115
- const targetSelector = getSelectorFromElement(element)
116
- const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null
117
-
118
- if (target) {
119
- const targetBCR = target.getBoundingClientRect()
120
- if (targetBCR.width || targetBCR.height) {
121
- return [
122
- Manipulator[offsetMethod](target).top + offsetBase,
123
- targetSelector
124
- ]
125
- }
126
- }
89
+ this._initializeTargetsAndObservables()
90
+ this._maybeEnableSmoothScroll()
127
91
 
128
- return null
129
- })
130
- .filter(item => item)
131
- .sort((a, b) => a[0] - b[0])
132
- .forEach(item => {
133
- this._offsets.push(item[0])
134
- this._targets.push(item[1])
135
- })
92
+ if (this._observer) {
93
+ this._observer.disconnect()
94
+ } else {
95
+ this._observer = this._getNewObserver()
96
+ }
97
+
98
+ for (const section of this._observableSections.values()) {
99
+ this._observer.observe(section)
100
+ }
136
101
  }
137
102
 
138
103
  dispose() {
139
- EventHandler.off(this._scrollElement, EVENT_KEY)
104
+ this._observer.disconnect()
140
105
  super.dispose()
141
106
  }
142
107
 
143
108
  // Private
109
+ _configAfterMerge(config) {
110
+ // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case
111
+ config.target = getElement(config.target) || document.body
144
112
 
145
- _getConfig(config) {
146
- config = {
147
- ...Default,
148
- ...Manipulator.getDataAttributes(this._element),
149
- ...(typeof config === 'object' && config ? config : {})
150
- }
113
+ return config
114
+ }
151
115
 
152
- config.target = getElement(config.target) || document.documentElement
116
+ _maybeEnableSmoothScroll() {
117
+ if (!this._config.smoothScroll) {
118
+ return
119
+ }
153
120
 
154
- typeCheckConfig(NAME, config, DefaultType)
121
+ // unregister any previous listeners
122
+ EventHandler.off(this._config.target, EVENT_CLICK)
123
+
124
+ EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {
125
+ const observableSection = this._observableSections.get(event.target.hash)
126
+ if (observableSection) {
127
+ event.preventDefault()
128
+ const root = this._rootElement || window
129
+ const height = observableSection.offsetTop - this._element.offsetTop
130
+ if (root.scrollTo) {
131
+ root.scrollTo({ top: height, behavior: 'smooth' })
132
+ return
133
+ }
155
134
 
156
- return config
135
+ // Chrome 60 doesn't support `scrollTo`
136
+ root.scrollTop = height
137
+ }
138
+ })
157
139
  }
158
140
 
159
- _getScrollTop() {
160
- return this._scrollElement === window ?
161
- this._scrollElement.pageYOffset :
162
- this._scrollElement.scrollTop
163
- }
141
+ _getNewObserver() {
142
+ const options = {
143
+ root: this._rootElement,
144
+ threshold: [0.1, 0.5, 1],
145
+ rootMargin: this._getRootMargin()
146
+ }
164
147
 
165
- _getScrollHeight() {
166
- return this._scrollElement.scrollHeight || Math.max(
167
- document.body.scrollHeight,
168
- document.documentElement.scrollHeight
169
- )
148
+ return new IntersectionObserver(entries => this._observerCallback(entries), options)
170
149
  }
171
150
 
172
- _getOffsetHeight() {
173
- return this._scrollElement === window ?
174
- window.innerHeight :
175
- this._scrollElement.getBoundingClientRect().height
176
- }
151
+ // The logic of selection
152
+ _observerCallback(entries) {
153
+ const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)
154
+ const activate = entry => {
155
+ this._previousScrollData.visibleEntryTop = entry.target.offsetTop
156
+ this._process(targetElement(entry))
157
+ }
177
158
 
178
- _process() {
179
- const scrollTop = this._getScrollTop() + this._config.offset
180
- const scrollHeight = this._getScrollHeight()
181
- const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight()
159
+ const parentScrollTop = (this._rootElement || document.documentElement).scrollTop
160
+ const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop
161
+ this._previousScrollData.parentScrollTop = parentScrollTop
182
162
 
183
- if (this._scrollHeight !== scrollHeight) {
184
- this.refresh()
185
- }
163
+ for (const entry of entries) {
164
+ if (!entry.isIntersecting) {
165
+ this._activeTarget = null
166
+ this._clearActiveClass(targetElement(entry))
167
+
168
+ continue
169
+ }
186
170
 
187
- if (scrollTop >= maxScroll) {
188
- const target = this._targets[this._targets.length - 1]
171
+ const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop
172
+ // if we are scrolling down, pick the bigger offsetTop
173
+ if (userScrollsDown && entryIsLowerThanPrevious) {
174
+ activate(entry)
175
+ // if parent isn't scrolled, let's keep the first visible item, breaking the iteration
176
+ if (!parentScrollTop) {
177
+ return
178
+ }
189
179
 
190
- if (this._activeTarget !== target) {
191
- this._activate(target)
180
+ continue
192
181
  }
193
182
 
194
- return
183
+ // if we are scrolling up, pick the smallest offsetTop
184
+ if (!userScrollsDown && !entryIsLowerThanPrevious) {
185
+ activate(entry)
186
+ }
195
187
  }
188
+ }
196
189
 
197
- if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
198
- this._activeTarget = null
199
- this._clear()
200
- return
201
- }
190
+ // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only
191
+ _getRootMargin() {
192
+ return this._config.offset ? `${this._config.offset}px 0px -30%` : this._config.rootMargin
193
+ }
194
+
195
+ _initializeTargetsAndObservables() {
196
+ this._targetLinks = new Map()
197
+ this._observableSections = new Map()
198
+
199
+ const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)
202
200
 
203
- for (let i = this._offsets.length; i--;) {
204
- const isActiveTarget = this._activeTarget !== this._targets[i] &&
205
- scrollTop >= this._offsets[i] &&
206
- (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1])
201
+ for (const anchor of targetLinks) {
202
+ // ensure that the anchor has an id and is not disabled
203
+ if (!anchor.hash || isDisabled(anchor)) {
204
+ continue
205
+ }
206
+
207
+ const observableSection = SelectorEngine.findOne(anchor.hash, this._element)
207
208
 
208
- if (isActiveTarget) {
209
- this._activate(this._targets[i])
209
+ // ensure that the observableSection exists & is visible
210
+ if (isVisible(observableSection)) {
211
+ this._targetLinks.set(anchor.hash, anchor)
212
+ this._observableSections.set(anchor.hash, observableSection)
210
213
  }
211
214
  }
212
215
  }
213
216
 
214
- _activate(target) {
215
- this._activeTarget = target
216
-
217
- this._clear()
217
+ _process(target) {
218
+ if (this._activeTarget === target) {
219
+ return
220
+ }
218
221
 
219
- const queries = SELECTOR_LINK_ITEMS.split(',')
220
- .map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`)
222
+ this._clearActiveClass(this._config.target)
223
+ this._activeTarget = target
224
+ target.classList.add(CLASS_NAME_ACTIVE)
225
+ this._activateParents(target)
221
226
 
222
- const link = SelectorEngine.findOne(queries.join(','), this._config.target)
227
+ EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })
228
+ }
223
229
 
224
- link.classList.add(CLASS_NAME_ACTIVE)
225
- if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
226
- SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN))
230
+ _activateParents(target) {
231
+ // Activate dropdown parents
232
+ if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
233
+ SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))
227
234
  .classList.add(CLASS_NAME_ACTIVE)
228
- } else {
229
- SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP)
230
- .forEach(listGroup => {
231
- // Set triggered links parents as active
232
- // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
233
- SelectorEngine.prev(listGroup, `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`)
234
- .forEach(item => item.classList.add(CLASS_NAME_ACTIVE))
235
-
236
- // Handle special case when .nav-link is inside .nav-item
237
- SelectorEngine.prev(listGroup, SELECTOR_NAV_ITEMS)
238
- .forEach(navItem => {
239
- SelectorEngine.children(navItem, SELECTOR_NAV_LINKS)
240
- .forEach(item => item.classList.add(CLASS_NAME_ACTIVE))
241
- })
242
- })
235
+ return
243
236
  }
244
237
 
245
- EventHandler.trigger(this._scrollElement, EVENT_ACTIVATE, {
246
- relatedTarget: target
247
- })
238
+ for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {
239
+ // Set triggered links parents as active
240
+ // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
241
+ for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {
242
+ item.classList.add(CLASS_NAME_ACTIVE)
243
+ }
244
+ }
248
245
  }
249
246
 
250
- _clear() {
251
- SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target)
252
- .filter(node => node.classList.contains(CLASS_NAME_ACTIVE))
253
- .forEach(node => node.classList.remove(CLASS_NAME_ACTIVE))
247
+ _clearActiveClass(parent) {
248
+ parent.classList.remove(CLASS_NAME_ACTIVE)
249
+
250
+ const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)
251
+ for (const node of activeNodes) {
252
+ node.classList.remove(CLASS_NAME_ACTIVE)
253
+ }
254
254
  }
255
255
 
256
256
  // Static
257
-
258
257
  static jQueryInterface(config) {
259
258
  return this.each(function () {
260
259
  const data = ScrollSpy.getOrCreateInstance(this, config)
@@ -263,7 +262,7 @@ class ScrollSpy extends BaseComponent {
263
262
  return
264
263
  }
265
264
 
266
- if (typeof data[config] === 'undefined') {
265
+ if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
267
266
  throw new TypeError(`No method named "${config}"`)
268
267
  }
269
268
 
@@ -273,21 +272,17 @@ class ScrollSpy extends BaseComponent {
273
272
  }
274
273
 
275
274
  /**
276
- * ------------------------------------------------------------------------
277
- * Data Api implementation
278
- * ------------------------------------------------------------------------
275
+ * Data API implementation
279
276
  */
280
277
 
281
278
  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
282
- SelectorEngine.find(SELECTOR_DATA_SPY)
283
- .forEach(spy => new ScrollSpy(spy))
279
+ for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {
280
+ ScrollSpy.getOrCreateInstance(spy)
281
+ }
284
282
  })
285
283
 
286
284
  /**
287
- * ------------------------------------------------------------------------
288
285
  * jQuery
289
- * ------------------------------------------------------------------------
290
- * add .ScrollSpy to jQuery only if jQuery is present
291
286
  */
292
287
 
293
288
  defineJQueryPlugin(ScrollSpy)