bootstrap 5.1.3 → 5.2.2

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -4
  3. data/assets/javascripts/bootstrap/alert.js +11 -146
  4. data/assets/javascripts/bootstrap/base-component.js +37 -120
  5. data/assets/javascripts/bootstrap/button.js +10 -74
  6. data/assets/javascripts/bootstrap/carousel.js +213 -485
  7. data/assets/javascripts/bootstrap/collapse.js +65 -249
  8. data/assets/javascripts/bootstrap/dom/data.js +3 -5
  9. data/assets/javascripts/bootstrap/dom/event-handler.js +94 -132
  10. data/assets/javascripts/bootstrap/dom/manipulator.js +23 -27
  11. data/assets/javascripts/bootstrap/dom/selector-engine.js +16 -58
  12. data/assets/javascripts/bootstrap/dropdown.js +103 -317
  13. data/assets/javascripts/bootstrap/modal.js +107 -749
  14. data/assets/javascripts/bootstrap/offcanvas.js +90 -659
  15. data/assets/javascripts/bootstrap/popover.js +36 -118
  16. data/assets/javascripts/bootstrap/scrollspy.js +183 -262
  17. data/assets/javascripts/bootstrap/tab.js +215 -214
  18. data/assets/javascripts/bootstrap/toast.js +36 -218
  19. data/assets/javascripts/bootstrap/tooltip.js +280 -629
  20. data/assets/javascripts/bootstrap/util/backdrop.js +165 -0
  21. data/assets/javascripts/bootstrap/util/component-functions.js +46 -0
  22. data/assets/javascripts/bootstrap/util/config.js +79 -0
  23. data/assets/javascripts/bootstrap/util/focustrap.js +129 -0
  24. data/assets/javascripts/bootstrap/util/index.js +350 -0
  25. data/assets/javascripts/bootstrap/util/sanitizer.js +122 -0
  26. data/assets/javascripts/bootstrap/util/scrollbar.js +138 -0
  27. data/assets/javascripts/bootstrap/util/swipe.js +155 -0
  28. data/assets/javascripts/bootstrap/util/template-factory.js +177 -0
  29. data/assets/javascripts/bootstrap-global-this-define.js +1 -1
  30. data/assets/javascripts/bootstrap-sprockets.js +17 -8
  31. data/assets/javascripts/bootstrap.js +2093 -1881
  32. data/assets/javascripts/bootstrap.min.js +3 -3
  33. data/assets/stylesheets/_bootstrap-grid.scss +3 -6
  34. data/assets/stylesheets/_bootstrap-reboot.scss +3 -7
  35. data/assets/stylesheets/_bootstrap.scss +4 -6
  36. data/assets/stylesheets/bootstrap/_accordion.scss +56 -25
  37. data/assets/stylesheets/bootstrap/_alert.scss +18 -4
  38. data/assets/stylesheets/bootstrap/_badge.scss +14 -5
  39. data/assets/stylesheets/bootstrap/_breadcrumb.scss +22 -10
  40. data/assets/stylesheets/bootstrap/_button-group.scss +4 -1
  41. data/assets/stylesheets/bootstrap/_buttons.scss +125 -29
  42. data/assets/stylesheets/bootstrap/_card.scss +55 -37
  43. data/assets/stylesheets/bootstrap/_close.scss +1 -1
  44. data/assets/stylesheets/bootstrap/_containers.scss +1 -1
  45. data/assets/stylesheets/bootstrap/_dropdown.scss +85 -76
  46. data/assets/stylesheets/bootstrap/_functions.scss +8 -8
  47. data/assets/stylesheets/bootstrap/_grid.scss +3 -3
  48. data/assets/stylesheets/bootstrap/_helpers.scss +1 -0
  49. data/assets/stylesheets/bootstrap/_list-group.scss +48 -30
  50. data/assets/stylesheets/bootstrap/_maps.scss +54 -0
  51. data/assets/stylesheets/bootstrap/_modal.scss +71 -43
  52. data/assets/stylesheets/bootstrap/_nav.scss +53 -20
  53. data/assets/stylesheets/bootstrap/_navbar.scss +93 -150
  54. data/assets/stylesheets/bootstrap/_offcanvas.scss +120 -59
  55. data/assets/stylesheets/bootstrap/_pagination.scss +66 -21
  56. data/assets/stylesheets/bootstrap/_placeholders.scss +1 -1
  57. data/assets/stylesheets/bootstrap/_popover.scss +90 -52
  58. data/assets/stylesheets/bootstrap/_progress.scss +20 -9
  59. data/assets/stylesheets/bootstrap/_reboot.scss +25 -40
  60. data/assets/stylesheets/bootstrap/_root.scss +40 -21
  61. data/assets/stylesheets/bootstrap/_spinners.scss +38 -22
  62. data/assets/stylesheets/bootstrap/_tables.scss +32 -23
  63. data/assets/stylesheets/bootstrap/_toasts.scss +38 -16
  64. data/assets/stylesheets/bootstrap/_tooltip.scss +61 -56
  65. data/assets/stylesheets/bootstrap/_type.scss +2 -0
  66. data/assets/stylesheets/bootstrap/_utilities.scss +43 -26
  67. data/assets/stylesheets/bootstrap/_variables.scss +128 -135
  68. data/assets/stylesheets/bootstrap/bootstrap-utilities.scss +3 -6
  69. data/assets/stylesheets/bootstrap/forms/_floating-labels.scss +15 -3
  70. data/assets/stylesheets/bootstrap/forms/_form-check.scss +28 -5
  71. data/assets/stylesheets/bootstrap/forms/_form-control.scss +12 -37
  72. data/assets/stylesheets/bootstrap/forms/_form-select.scss +0 -1
  73. data/assets/stylesheets/bootstrap/forms/_input-group.scss +19 -8
  74. data/assets/stylesheets/bootstrap/helpers/_color-bg.scss +10 -0
  75. data/assets/stylesheets/bootstrap/helpers/_colored-links.scss +2 -2
  76. data/assets/stylesheets/bootstrap/helpers/_position.scss +7 -1
  77. data/assets/stylesheets/bootstrap/helpers/_ratio.scss +2 -2
  78. data/assets/stylesheets/bootstrap/helpers/_vr.scss +1 -1
  79. data/assets/stylesheets/bootstrap/mixins/_alert.scss +7 -3
  80. data/assets/stylesheets/bootstrap/mixins/_banner.scss +9 -0
  81. data/assets/stylesheets/bootstrap/mixins/_breakpoints.scss +8 -8
  82. data/assets/stylesheets/bootstrap/mixins/_buttons.scss +32 -95
  83. data/assets/stylesheets/bootstrap/mixins/_container.scss +4 -2
  84. data/assets/stylesheets/bootstrap/mixins/_forms.scss +18 -10
  85. data/assets/stylesheets/bootstrap/mixins/_gradients.scss +1 -1
  86. data/assets/stylesheets/bootstrap/mixins/_grid.scss +12 -12
  87. data/assets/stylesheets/bootstrap/mixins/_pagination.scss +4 -25
  88. data/assets/stylesheets/bootstrap/mixins/_reset-text.scss +1 -1
  89. data/assets/stylesheets/bootstrap/mixins/_table-variants.scss +12 -9
  90. data/assets/stylesheets/bootstrap/mixins/_utilities.scss +13 -5
  91. data/bootstrap.gemspec +1 -1
  92. data/lib/bootstrap/version.rb +2 -2
  93. data/tasks/updater/js.rb +10 -5
  94. data/tasks/updater.rb +2 -2
  95. metadata +16 -4
@@ -1,215 +1,82 @@
1
1
  /*!
2
- * Bootstrap scrollspy.js v5.1.3 (https://getbootstrap.com/)
3
- * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
2
+ * Bootstrap scrollspy.js v5.2.2 (https://getbootstrap.com/)
3
+ * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  */
6
6
  (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./base-component.js')) :
8
- typeof define === 'function' && define.amd ? define(['./dom/event-handler', './dom/manipulator', './dom/selector-engine', './base-component'], factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ScrollSpy = factory(global.EventHandler, global.Manipulator, global.SelectorEngine, global.Base));
10
- })(this, (function (EventHandler, Manipulator, SelectorEngine, BaseComponent) { 'use strict';
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./base-component')) :
8
+ typeof define === 'function' && define.amd ? define(['./util/index', './dom/event-handler', './dom/selector-engine', './base-component'], factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollspy = factory(global.Index, global.EventHandler, global.SelectorEngine, global.BaseComponent));
10
+ })(this, (function (index, EventHandler, SelectorEngine, BaseComponent) { 'use strict';
11
11
 
12
12
  const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };
13
13
 
14
14
  const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);
15
- const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);
16
15
  const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);
17
16
  const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);
18
17
 
19
18
  /**
20
19
  * --------------------------------------------------------------------------
21
- * Bootstrap (v5.1.3): util/index.js
22
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
23
- * --------------------------------------------------------------------------
24
- */
25
-
26
- const toType = obj => {
27
- if (obj === null || obj === undefined) {
28
- return `${obj}`;
29
- }
30
-
31
- return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase();
32
- };
33
-
34
- const getSelector = element => {
35
- let selector = element.getAttribute('data-bs-target');
36
-
37
- if (!selector || selector === '#') {
38
- let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,
39
- // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
40
- // `document.querySelector` will rightfully complain it is invalid.
41
- // See https://github.com/twbs/bootstrap/issues/32273
42
-
43
- if (!hrefAttr || !hrefAttr.includes('#') && !hrefAttr.startsWith('.')) {
44
- return null;
45
- } // Just in case some CMS puts out a full URL with the anchor appended
46
-
47
-
48
- if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
49
- hrefAttr = `#${hrefAttr.split('#')[1]}`;
50
- }
51
-
52
- selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null;
53
- }
54
-
55
- return selector;
56
- };
57
-
58
- const getSelectorFromElement = element => {
59
- const selector = getSelector(element);
60
-
61
- if (selector) {
62
- return document.querySelector(selector) ? selector : null;
63
- }
64
-
65
- return null;
66
- };
67
-
68
- const isElement = obj => {
69
- if (!obj || typeof obj !== 'object') {
70
- return false;
71
- }
72
-
73
- if (typeof obj.jquery !== 'undefined') {
74
- obj = obj[0];
75
- }
76
-
77
- return typeof obj.nodeType !== 'undefined';
78
- };
79
-
80
- const getElement = obj => {
81
- if (isElement(obj)) {
82
- // it's a jQuery object or a node element
83
- return obj.jquery ? obj[0] : obj;
84
- }
85
-
86
- if (typeof obj === 'string' && obj.length > 0) {
87
- return document.querySelector(obj);
88
- }
89
-
90
- return null;
91
- };
92
-
93
- const typeCheckConfig = (componentName, config, configTypes) => {
94
- Object.keys(configTypes).forEach(property => {
95
- const expectedTypes = configTypes[property];
96
- const value = config[property];
97
- const valueType = value && isElement(value) ? 'element' : toType(value);
98
-
99
- if (!new RegExp(expectedTypes).test(valueType)) {
100
- throw new TypeError(`${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`);
101
- }
102
- });
103
- };
104
-
105
- const getjQuery = () => {
106
- const {
107
- jQuery
108
- } = window;
109
-
110
- if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
111
- return jQuery;
112
- }
113
-
114
- return null;
115
- };
116
-
117
- const DOMContentLoadedCallbacks = [];
118
-
119
- const onDOMContentLoaded = callback => {
120
- if (document.readyState === 'loading') {
121
- // add listener on the first call when the document is in loading state
122
- if (!DOMContentLoadedCallbacks.length) {
123
- document.addEventListener('DOMContentLoaded', () => {
124
- DOMContentLoadedCallbacks.forEach(callback => callback());
125
- });
126
- }
127
-
128
- DOMContentLoadedCallbacks.push(callback);
129
- } else {
130
- callback();
131
- }
132
- };
133
-
134
- const defineJQueryPlugin = plugin => {
135
- onDOMContentLoaded(() => {
136
- const $ = getjQuery();
137
- /* istanbul ignore if */
138
-
139
- if ($) {
140
- const name = plugin.NAME;
141
- const JQUERY_NO_CONFLICT = $.fn[name];
142
- $.fn[name] = plugin.jQueryInterface;
143
- $.fn[name].Constructor = plugin;
144
-
145
- $.fn[name].noConflict = () => {
146
- $.fn[name] = JQUERY_NO_CONFLICT;
147
- return plugin.jQueryInterface;
148
- };
149
- }
150
- });
151
- };
152
-
153
- /**
154
- * --------------------------------------------------------------------------
155
- * Bootstrap (v5.1.3): scrollspy.js
20
+ * Bootstrap (v5.2.2): scrollspy.js
156
21
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
157
22
  * --------------------------------------------------------------------------
158
23
  */
159
24
  /**
160
- * ------------------------------------------------------------------------
161
25
  * Constants
162
- * ------------------------------------------------------------------------
163
26
  */
164
27
 
165
28
  const NAME = 'scrollspy';
166
29
  const DATA_KEY = 'bs.scrollspy';
167
30
  const EVENT_KEY = `.${DATA_KEY}`;
168
31
  const DATA_API_KEY = '.data-api';
169
- const Default = {
170
- offset: 10,
171
- method: 'auto',
172
- target: ''
173
- };
174
- const DefaultType = {
175
- offset: 'number',
176
- method: 'string',
177
- target: '(string|element)'
178
- };
179
32
  const EVENT_ACTIVATE = `activate${EVENT_KEY}`;
180
- const EVENT_SCROLL = `scroll${EVENT_KEY}`;
33
+ const EVENT_CLICK = `click${EVENT_KEY}`;
181
34
  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;
182
35
  const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';
183
36
  const CLASS_NAME_ACTIVE = 'active';
184
37
  const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]';
38
+ const SELECTOR_TARGET_LINKS = '[href]';
185
39
  const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';
186
40
  const SELECTOR_NAV_LINKS = '.nav-link';
187
41
  const SELECTOR_NAV_ITEMS = '.nav-item';
188
42
  const SELECTOR_LIST_ITEMS = '.list-group-item';
189
- const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}`;
43
+ const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;
190
44
  const SELECTOR_DROPDOWN = '.dropdown';
191
45
  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
192
- const METHOD_OFFSET = 'offset';
193
- const METHOD_POSITION = 'position';
46
+ const Default = {
47
+ offset: null,
48
+ // TODO: v6 @deprecated, keep it for backwards compatibility reasons
49
+ rootMargin: '0px 0px -25%',
50
+ smoothScroll: false,
51
+ target: null,
52
+ threshold: [0.1, 0.5, 1]
53
+ };
54
+ const DefaultType = {
55
+ offset: '(number|null)',
56
+ // TODO v6 @deprecated, keep it for backwards compatibility reasons
57
+ rootMargin: 'string',
58
+ smoothScroll: 'boolean',
59
+ target: 'element',
60
+ threshold: 'array'
61
+ };
194
62
  /**
195
- * ------------------------------------------------------------------------
196
- * Class Definition
197
- * ------------------------------------------------------------------------
63
+ * Class definition
198
64
  */
199
65
 
200
66
  class ScrollSpy extends BaseComponent__default.default {
201
67
  constructor(element, config) {
202
- super(element);
203
- this._scrollElement = this._element.tagName === 'BODY' ? window : this._element;
204
- this._config = this._getConfig(config);
205
- this._offsets = [];
206
- this._targets = [];
207
- this._activeTarget = null;
208
- this._scrollHeight = 0;
209
- EventHandler__default.default.on(this._scrollElement, EVENT_SCROLL, () => this._process());
210
- this.refresh();
68
+ super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper
211
69
 
212
- this._process();
70
+ this._targetLinks = new Map();
71
+ this._observableSections = new Map();
72
+ this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;
73
+ this._activeTarget = null;
74
+ this._observer = null;
75
+ this._previousScrollData = {
76
+ visibleEntryTop: 0,
77
+ parentScrollTop: 0
78
+ };
79
+ this.refresh(); // initialize
213
80
  } // Getters
214
81
 
215
82
 
@@ -217,135 +84,192 @@
217
84
  return Default;
218
85
  }
219
86
 
87
+ static get DefaultType() {
88
+ return DefaultType;
89
+ }
90
+
220
91
  static get NAME() {
221
92
  return NAME;
222
93
  } // Public
223
94
 
224
95
 
225
96
  refresh() {
226
- const autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION;
227
- const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method;
228
- const offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0;
229
- this._offsets = [];
230
- this._targets = [];
231
- this._scrollHeight = this._getScrollHeight();
232
- const targets = SelectorEngine__default.default.find(SELECTOR_LINK_ITEMS, this._config.target);
233
- targets.map(element => {
234
- const targetSelector = getSelectorFromElement(element);
235
- const target = targetSelector ? SelectorEngine__default.default.findOne(targetSelector) : null;
236
-
237
- if (target) {
238
- const targetBCR = target.getBoundingClientRect();
239
-
240
- if (targetBCR.width || targetBCR.height) {
241
- return [Manipulator__default.default[offsetMethod](target).top + offsetBase, targetSelector];
242
- }
243
- }
97
+ this._initializeTargetsAndObservables();
244
98
 
245
- return null;
246
- }).filter(item => item).sort((a, b) => a[0] - b[0]).forEach(item => {
247
- this._offsets.push(item[0]);
99
+ this._maybeEnableSmoothScroll();
248
100
 
249
- this._targets.push(item[1]);
250
- });
101
+ if (this._observer) {
102
+ this._observer.disconnect();
103
+ } else {
104
+ this._observer = this._getNewObserver();
105
+ }
106
+
107
+ for (const section of this._observableSections.values()) {
108
+ this._observer.observe(section);
109
+ }
251
110
  }
252
111
 
253
112
  dispose() {
254
- EventHandler__default.default.off(this._scrollElement, EVENT_KEY);
113
+ this._observer.disconnect();
114
+
255
115
  super.dispose();
256
116
  } // Private
257
117
 
258
118
 
259
- _getConfig(config) {
260
- config = { ...Default,
261
- ...Manipulator__default.default.getDataAttributes(this._element),
262
- ...(typeof config === 'object' && config ? config : {})
263
- };
264
- config.target = getElement(config.target) || document.documentElement;
265
- typeCheckConfig(NAME, config, DefaultType);
119
+ _configAfterMerge(config) {
120
+ // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case
121
+ config.target = index.getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only
122
+
123
+ config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;
124
+
125
+ if (typeof config.threshold === 'string') {
126
+ config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));
127
+ }
128
+
266
129
  return config;
267
130
  }
268
131
 
269
- _getScrollTop() {
270
- return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop;
271
- }
132
+ _maybeEnableSmoothScroll() {
133
+ if (!this._config.smoothScroll) {
134
+ return;
135
+ } // unregister any previous listeners
272
136
 
273
- _getScrollHeight() {
274
- return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
275
- }
276
137
 
277
- _getOffsetHeight() {
278
- return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height;
138
+ EventHandler__default.default.off(this._config.target, EVENT_CLICK);
139
+ EventHandler__default.default.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {
140
+ const observableSection = this._observableSections.get(event.target.hash);
141
+
142
+ if (observableSection) {
143
+ event.preventDefault();
144
+ const root = this._rootElement || window;
145
+ const height = observableSection.offsetTop - this._element.offsetTop;
146
+
147
+ if (root.scrollTo) {
148
+ root.scrollTo({
149
+ top: height,
150
+ behavior: 'smooth'
151
+ });
152
+ return;
153
+ } // Chrome 60 doesn't support `scrollTo`
154
+
155
+
156
+ root.scrollTop = height;
157
+ }
158
+ });
279
159
  }
280
160
 
281
- _process() {
282
- const scrollTop = this._getScrollTop() + this._config.offset;
161
+ _getNewObserver() {
162
+ const options = {
163
+ root: this._rootElement,
164
+ threshold: this._config.threshold,
165
+ rootMargin: this._config.rootMargin
166
+ };
167
+ return new IntersectionObserver(entries => this._observerCallback(entries), options);
168
+ } // The logic of selection
283
169
 
284
- const scrollHeight = this._getScrollHeight();
285
170
 
286
- const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight();
171
+ _observerCallback(entries) {
172
+ const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);
287
173
 
288
- if (this._scrollHeight !== scrollHeight) {
289
- this.refresh();
290
- }
174
+ const activate = entry => {
175
+ this._previousScrollData.visibleEntryTop = entry.target.offsetTop;
176
+
177
+ this._process(targetElement(entry));
178
+ };
179
+
180
+ const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;
181
+ const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;
182
+ this._previousScrollData.parentScrollTop = parentScrollTop;
291
183
 
292
- if (scrollTop >= maxScroll) {
293
- const target = this._targets[this._targets.length - 1];
184
+ for (const entry of entries) {
185
+ if (!entry.isIntersecting) {
186
+ this._activeTarget = null;
294
187
 
295
- if (this._activeTarget !== target) {
296
- this._activate(target);
188
+ this._clearActiveClass(targetElement(entry));
189
+
190
+ continue;
297
191
  }
298
192
 
299
- return;
300
- }
193
+ const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop
301
194
 
302
- if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
303
- this._activeTarget = null;
195
+ if (userScrollsDown && entryIsLowerThanPrevious) {
196
+ activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration
304
197
 
305
- this._clear();
198
+ if (!parentScrollTop) {
199
+ return;
200
+ }
306
201
 
307
- return;
308
- }
202
+ continue;
203
+ } // if we are scrolling up, pick the smallest offsetTop
309
204
 
310
- for (let i = this._offsets.length; i--;) {
311
- const isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]);
312
205
 
313
- if (isActiveTarget) {
314
- this._activate(this._targets[i]);
206
+ if (!userScrollsDown && !entryIsLowerThanPrevious) {
207
+ activate(entry);
315
208
  }
316
209
  }
317
210
  }
318
211
 
319
- _activate(target) {
320
- this._activeTarget = target;
212
+ _initializeTargetsAndObservables() {
213
+ this._targetLinks = new Map();
214
+ this._observableSections = new Map();
215
+ const targetLinks = SelectorEngine__default.default.find(SELECTOR_TARGET_LINKS, this._config.target);
216
+
217
+ for (const anchor of targetLinks) {
218
+ // ensure that the anchor has an id and is not disabled
219
+ if (!anchor.hash || index.isDisabled(anchor)) {
220
+ continue;
221
+ }
321
222
 
322
- this._clear();
223
+ const observableSection = SelectorEngine__default.default.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible
323
224
 
324
- const queries = SELECTOR_LINK_ITEMS.split(',').map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`);
325
- const link = SelectorEngine__default.default.findOne(queries.join(','), this._config.target);
326
- link.classList.add(CLASS_NAME_ACTIVE);
225
+ if (index.isVisible(observableSection)) {
226
+ this._targetLinks.set(anchor.hash, anchor);
327
227
 
328
- if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
329
- SelectorEngine__default.default.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE);
330
- } else {
331
- SelectorEngine__default.default.parents(link, SELECTOR_NAV_LIST_GROUP).forEach(listGroup => {
332
- // Set triggered links parents as active
333
- // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
334
- SelectorEngine__default.default.prev(listGroup, `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`).forEach(item => item.classList.add(CLASS_NAME_ACTIVE)); // Handle special case when .nav-link is inside .nav-item
335
-
336
- SelectorEngine__default.default.prev(listGroup, SELECTOR_NAV_ITEMS).forEach(navItem => {
337
- SelectorEngine__default.default.children(navItem, SELECTOR_NAV_LINKS).forEach(item => item.classList.add(CLASS_NAME_ACTIVE));
338
- });
339
- });
228
+ this._observableSections.set(anchor.hash, observableSection);
229
+ }
340
230
  }
231
+ }
341
232
 
342
- EventHandler__default.default.trigger(this._scrollElement, EVENT_ACTIVATE, {
233
+ _process(target) {
234
+ if (this._activeTarget === target) {
235
+ return;
236
+ }
237
+
238
+ this._clearActiveClass(this._config.target);
239
+
240
+ this._activeTarget = target;
241
+ target.classList.add(CLASS_NAME_ACTIVE);
242
+
243
+ this._activateParents(target);
244
+
245
+ EventHandler__default.default.trigger(this._element, EVENT_ACTIVATE, {
343
246
  relatedTarget: target
344
247
  });
345
248
  }
346
249
 
347
- _clear() {
348
- SelectorEngine__default.default.find(SELECTOR_LINK_ITEMS, this._config.target).filter(node => node.classList.contains(CLASS_NAME_ACTIVE)).forEach(node => node.classList.remove(CLASS_NAME_ACTIVE));
250
+ _activateParents(target) {
251
+ // Activate dropdown parents
252
+ if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
253
+ SelectorEngine__default.default.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE);
254
+ return;
255
+ }
256
+
257
+ for (const listGroup of SelectorEngine__default.default.parents(target, SELECTOR_NAV_LIST_GROUP)) {
258
+ // Set triggered links parents as active
259
+ // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
260
+ for (const item of SelectorEngine__default.default.prev(listGroup, SELECTOR_LINK_ITEMS)) {
261
+ item.classList.add(CLASS_NAME_ACTIVE);
262
+ }
263
+ }
264
+ }
265
+
266
+ _clearActiveClass(parent) {
267
+ parent.classList.remove(CLASS_NAME_ACTIVE);
268
+ const activeNodes = SelectorEngine__default.default.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent);
269
+
270
+ for (const node of activeNodes) {
271
+ node.classList.remove(CLASS_NAME_ACTIVE);
272
+ }
349
273
  } // Static
350
274
 
351
275
 
@@ -357,7 +281,7 @@
357
281
  return;
358
282
  }
359
283
 
360
- if (typeof data[config] === 'undefined') {
284
+ if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
361
285
  throw new TypeError(`No method named "${config}"`);
362
286
  }
363
287
 
@@ -367,23 +291,20 @@
367
291
 
368
292
  }
369
293
  /**
370
- * ------------------------------------------------------------------------
371
- * Data Api implementation
372
- * ------------------------------------------------------------------------
294
+ * Data API implementation
373
295
  */
374
296
 
375
297
 
376
298
  EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => {
377
- SelectorEngine__default.default.find(SELECTOR_DATA_SPY).forEach(spy => new ScrollSpy(spy));
299
+ for (const spy of SelectorEngine__default.default.find(SELECTOR_DATA_SPY)) {
300
+ ScrollSpy.getOrCreateInstance(spy);
301
+ }
378
302
  });
379
303
  /**
380
- * ------------------------------------------------------------------------
381
304
  * jQuery
382
- * ------------------------------------------------------------------------
383
- * add .ScrollSpy to jQuery only if jQuery is present
384
305
  */
385
306
 
386
- defineJQueryPlugin(ScrollSpy);
307
+ index.defineJQueryPlugin(ScrollSpy);
387
308
 
388
309
  return ScrollSpy;
389
310