]> git.ipfire.org Git - ipfire.org.git/blob - src/scss/bootstrap-4.0.0-alpha.6/js/src/scrollspy.js
.gitignore: Add .vscode
[ipfire.org.git] / src / scss / bootstrap-4.0.0-alpha.6 / js / src / scrollspy.js
1 import Util from './util'
2
3
4 /**
5 * --------------------------------------------------------------------------
6 * Bootstrap (v4.0.0-alpha.6): scrollspy.js
7 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
8 * --------------------------------------------------------------------------
9 */
10
11 const ScrollSpy = (($) => {
12
13
14 /**
15 * ------------------------------------------------------------------------
16 * Constants
17 * ------------------------------------------------------------------------
18 */
19
20 const NAME = 'scrollspy'
21 const VERSION = '4.0.0-alpha.6'
22 const DATA_KEY = 'bs.scrollspy'
23 const EVENT_KEY = `.${DATA_KEY}`
24 const DATA_API_KEY = '.data-api'
25 const JQUERY_NO_CONFLICT = $.fn[NAME]
26
27 const Default = {
28 offset : 10,
29 method : 'auto',
30 target : ''
31 }
32
33 const DefaultType = {
34 offset : 'number',
35 method : 'string',
36 target : '(string|element)'
37 }
38
39 const Event = {
40 ACTIVATE : `activate${EVENT_KEY}`,
41 SCROLL : `scroll${EVENT_KEY}`,
42 LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`
43 }
44
45 const ClassName = {
46 DROPDOWN_ITEM : 'dropdown-item',
47 DROPDOWN_MENU : 'dropdown-menu',
48 NAV_LINK : 'nav-link',
49 NAV : 'nav',
50 ACTIVE : 'active'
51 }
52
53 const Selector = {
54 DATA_SPY : '[data-spy="scroll"]',
55 ACTIVE : '.active',
56 LIST_ITEM : '.list-item',
57 LI : 'li',
58 LI_DROPDOWN : 'li.dropdown',
59 NAV_LINKS : '.nav-link',
60 DROPDOWN : '.dropdown',
61 DROPDOWN_ITEMS : '.dropdown-item',
62 DROPDOWN_TOGGLE : '.dropdown-toggle'
63 }
64
65 const OffsetMethod = {
66 OFFSET : 'offset',
67 POSITION : 'position'
68 }
69
70
71 /**
72 * ------------------------------------------------------------------------
73 * Class Definition
74 * ------------------------------------------------------------------------
75 */
76
77 class ScrollSpy {
78
79 constructor(element, config) {
80 this._element = element
81 this._scrollElement = element.tagName === 'BODY' ? window : element
82 this._config = this._getConfig(config)
83 this._selector = `${this._config.target} ${Selector.NAV_LINKS},`
84 + `${this._config.target} ${Selector.DROPDOWN_ITEMS}`
85 this._offsets = []
86 this._targets = []
87 this._activeTarget = null
88 this._scrollHeight = 0
89
90 $(this._scrollElement).on(Event.SCROLL, (event) => this._process(event))
91
92 this.refresh()
93 this._process()
94 }
95
96
97 // getters
98
99 static get VERSION() {
100 return VERSION
101 }
102
103 static get Default() {
104 return Default
105 }
106
107
108 // public
109
110 refresh() {
111 const autoMethod = this._scrollElement !== this._scrollElement.window ?
112 OffsetMethod.POSITION : OffsetMethod.OFFSET
113
114 const offsetMethod = this._config.method === 'auto' ?
115 autoMethod : this._config.method
116
117 const offsetBase = offsetMethod === OffsetMethod.POSITION ?
118 this._getScrollTop() : 0
119
120 this._offsets = []
121 this._targets = []
122
123 this._scrollHeight = this._getScrollHeight()
124
125 const targets = $.makeArray($(this._selector))
126
127 targets
128 .map((element) => {
129 let target
130 const targetSelector = Util.getSelectorFromElement(element)
131
132 if (targetSelector) {
133 target = $(targetSelector)[0]
134 }
135
136 if (target && (target.offsetWidth || target.offsetHeight)) {
137 // todo (fat): remove sketch reliance on jQuery position/offset
138 return [
139 $(target)[offsetMethod]().top + offsetBase,
140 targetSelector
141 ]
142 }
143 return null
144 })
145 .filter((item) => item)
146 .sort((a, b) => a[0] - b[0])
147 .forEach((item) => {
148 this._offsets.push(item[0])
149 this._targets.push(item[1])
150 })
151 }
152
153 dispose() {
154 $.removeData(this._element, DATA_KEY)
155 $(this._scrollElement).off(EVENT_KEY)
156
157 this._element = null
158 this._scrollElement = null
159 this._config = null
160 this._selector = null
161 this._offsets = null
162 this._targets = null
163 this._activeTarget = null
164 this._scrollHeight = null
165 }
166
167
168 // private
169
170 _getConfig(config) {
171 config = $.extend({}, Default, config)
172
173 if (typeof config.target !== 'string') {
174 let id = $(config.target).attr('id')
175 if (!id) {
176 id = Util.getUID(NAME)
177 $(config.target).attr('id', id)
178 }
179 config.target = `#${id}`
180 }
181
182 Util.typeCheckConfig(NAME, config, DefaultType)
183
184 return config
185 }
186
187 _getScrollTop() {
188 return this._scrollElement === window ?
189 this._scrollElement.pageYOffset : this._scrollElement.scrollTop
190 }
191
192 _getScrollHeight() {
193 return this._scrollElement.scrollHeight || Math.max(
194 document.body.scrollHeight,
195 document.documentElement.scrollHeight
196 )
197 }
198
199 _getOffsetHeight() {
200 return this._scrollElement === window ?
201 window.innerHeight : this._scrollElement.offsetHeight
202 }
203
204 _process() {
205 const scrollTop = this._getScrollTop() + this._config.offset
206 const scrollHeight = this._getScrollHeight()
207 const maxScroll = this._config.offset
208 + scrollHeight
209 - this._getOffsetHeight()
210
211 if (this._scrollHeight !== scrollHeight) {
212 this.refresh()
213 }
214
215 if (scrollTop >= maxScroll) {
216 const target = this._targets[this._targets.length - 1]
217
218 if (this._activeTarget !== target) {
219 this._activate(target)
220 }
221 return
222 }
223
224 if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
225 this._activeTarget = null
226 this._clear()
227 return
228 }
229
230 for (let i = this._offsets.length; i--;) {
231 const isActiveTarget = this._activeTarget !== this._targets[i]
232 && scrollTop >= this._offsets[i]
233 && (this._offsets[i + 1] === undefined ||
234 scrollTop < this._offsets[i + 1])
235
236 if (isActiveTarget) {
237 this._activate(this._targets[i])
238 }
239 }
240 }
241
242 _activate(target) {
243 this._activeTarget = target
244
245 this._clear()
246
247 let queries = this._selector.split(',')
248 queries = queries.map((selector) => {
249 return `${selector}[data-target="${target}"],` +
250 `${selector}[href="${target}"]`
251 })
252
253 const $link = $(queries.join(','))
254
255 if ($link.hasClass(ClassName.DROPDOWN_ITEM)) {
256 $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE)
257 $link.addClass(ClassName.ACTIVE)
258 } else {
259 // todo (fat) this is kinda sus...
260 // recursively add actives to tested nav-links
261 $link.parents(Selector.LI).find(`> ${Selector.NAV_LINKS}`).addClass(ClassName.ACTIVE)
262 }
263
264 $(this._scrollElement).trigger(Event.ACTIVATE, {
265 relatedTarget: target
266 })
267 }
268
269 _clear() {
270 $(this._selector).filter(Selector.ACTIVE).removeClass(ClassName.ACTIVE)
271 }
272
273
274 // static
275
276 static _jQueryInterface(config) {
277 return this.each(function () {
278 let data = $(this).data(DATA_KEY)
279 const _config = typeof config === 'object' && config
280
281 if (!data) {
282 data = new ScrollSpy(this, _config)
283 $(this).data(DATA_KEY, data)
284 }
285
286 if (typeof config === 'string') {
287 if (data[config] === undefined) {
288 throw new Error(`No method named "${config}"`)
289 }
290 data[config]()
291 }
292 })
293 }
294
295
296 }
297
298
299 /**
300 * ------------------------------------------------------------------------
301 * Data Api implementation
302 * ------------------------------------------------------------------------
303 */
304
305 $(window).on(Event.LOAD_DATA_API, () => {
306 const scrollSpys = $.makeArray($(Selector.DATA_SPY))
307
308 for (let i = scrollSpys.length; i--;) {
309 const $spy = $(scrollSpys[i])
310 ScrollSpy._jQueryInterface.call($spy, $spy.data())
311 }
312 })
313
314
315 /**
316 * ------------------------------------------------------------------------
317 * jQuery
318 * ------------------------------------------------------------------------
319 */
320
321 $.fn[NAME] = ScrollSpy._jQueryInterface
322 $.fn[NAME].Constructor = ScrollSpy
323 $.fn[NAME].noConflict = function () {
324 $.fn[NAME] = JQUERY_NO_CONFLICT
325 return ScrollSpy._jQueryInterface
326 }
327
328 return ScrollSpy
329
330 })(jQuery)
331
332 export default ScrollSpy