bootstrap 5.1.3 → 5.3.5

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