Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

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