vuikit
Version:
A responsive Vue UI library for web site interfaces based on UIkit
118 lines (101 loc) • 3.01 kB
JavaScript
/* eslint-disable no-mixed-operators */
import { warn } from 'vuikit/src/util/debug'
import { $, $$ } from 'vuikit/src/util/core'
import { filter } from 'vuikit/src/util/filter'
import { trigger } from 'vuikit/src/util/event'
import { closest } from 'vuikit/src/util/selector'
import { addClass, removeClass } from 'vuikit/src/util/class'
import { filterOutTextNodes } from 'vuikit/src/util/vue'
import { isInView, height, offset } from 'vuikit/src/util/dimensions'
import MixinEvents from 'vuikit/src/mixins/events'
import MixinFastdom from 'vuikit/src/mixins/fastdom'
export default {
name: 'VkScrollspyNav',
abstract: true,
mixins: [MixinEvents, MixinFastdom],
props: {
cls: {
type: String,
default: 'uk-active'
},
closest: {
type: String,
default: ''
},
overflow: {
type: Boolean,
default: true
},
offset: {
type: Number,
default: 0
}
},
methods: {
setComputed () {
this.links = $$('a[href^="#"]', this.$el).filter(el => el.hash)
this.elements = this.closest ? closest(this.links, this.closest) : this.links
this.targets = $$(this.links.map(el => el.hash).join(','))
}
},
fastdom: [
{
read (data) {
const scroll = window.pageYOffset + this.offset + 1
const max = height(document) - height(window) + this.offset
data.active = false
this.targets.every((el, i) => {
const {top} = offset(el)
const last = i + 1 === this.targets.length
if (!this.overflow && (i === 0 && top > scroll || last && top + el.offsetTop < scroll)) {
return false
}
if (!last && offset(this.targets[i + 1]).top <= scroll) {
return true
}
if (scroll >= max) {
for (let j = this.targets.length - 1; j > i; j--) {
if (isInView(this.targets[j])) {
el = this.targets[j]
break
}
}
}
return !(data.active = $(filter(this.links, `[href="#${el.id}"]`)))
})
},
write ({ active }) {
this.links.forEach(el => el.blur())
removeClass(this.elements, this.cls)
if (active) {
trigger(this.$el, 'active', [active, addClass(this.closest ? closest(active, this.closest) : active, this.cls)])
}
},
events: ['scroll', 'load', 'resize']
}
],
mounted () {
this.setComputed()
},
updated () {
this.$nextTick(() => {
this.setComputed()
this.fastdomUpdate()
})
},
render (h) {
let children = this.$slots.default
if (!children) {
return
}
children = filterOutTextNodes(children)
if (!children.length) {
return
}
// warn if using multiple elements
if (process.env.NODE_ENV !== 'production' && children.length > 1) {
warn('vk-scrollspy can only be used on a single element', this.$parent)
}
return children[0]
}
}