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