-
-
Notifications
You must be signed in to change notification settings - Fork 107
/
active.js
110 lines (94 loc) · 3.3 KB
/
active.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {parse} from 'regexparam'
import {loc} from './Router.svelte'
// List of nodes to update
const nodes = []
// Current location
let location
// Function that updates all nodes marking the active ones
function checkActive(el) {
const matchesLocation = el.pattern.test(location)
toggleClasses(el, el.className, matchesLocation)
toggleClasses(el, el.inactiveClassName, !matchesLocation)
}
function toggleClasses(el, className, shouldAdd) {
(className || '').split(' ').forEach((cls) => {
if (!cls) {
return
}
// Remove the class firsts
el.node.classList.remove(cls)
// If the pattern doesn't match, then set the class
if (shouldAdd) {
el.node.classList.add(cls)
}
})
}
// Listen to changes in the location
loc.subscribe((value) => {
// Update the location
location = value.location + (value.querystring ? '?' + value.querystring : '')
// Update all nodes
nodes.map(checkActive)
})
/**
* @typedef {Object} ActiveOptions
* @property {string|RegExp} [path] - Path expression that makes the link active when matched (must start with '/' or '*'); default is the link's href
* @property {string} [className] - CSS class to apply to the element when active; default value is "active"
*/
/**
* Svelte Action for automatically adding the "active" class to elements (links, or any other DOM element) when the current location matches a certain path.
*
* @param {HTMLElement} node - The target node (automatically set by Svelte)
* @param {ActiveOptions|string|RegExp} [opts] - Can be an object of type ActiveOptions, or a string (or regular expressions) representing ActiveOptions.path.
* @returns {{destroy: function(): void}} Destroy function
*/
export default function active(node, opts) {
// Check options
if (opts && (typeof opts == 'string' || (typeof opts == 'object' && opts instanceof RegExp))) {
// Interpret strings and regular expressions as opts.path
opts = {
path: opts
}
}
else {
// Ensure opts is a dictionary
opts = opts || {}
}
// Path defaults to link target
if (!opts.path && node.hasAttribute('href')) {
opts.path = node.getAttribute('href')
if (opts.path && opts.path.length > 1 && opts.path.charAt(0) == '#') {
opts.path = opts.path.substring(1)
}
}
// Default class name
if (!opts.className) {
opts.className = 'active'
}
// If path is a string, it must start with '/' or '*'
if (!opts.path ||
typeof opts.path == 'string' && (opts.path.length < 1 || (opts.path.charAt(0) != '/' && opts.path.charAt(0) != '*'))
) {
throw Error('Invalid value for "path" argument')
}
// If path is not a regular expression already, make it
const {pattern} = typeof opts.path == 'string' ?
parse(opts.path) :
{pattern: opts.path}
// Add the node to the list
const el = {
node,
className: opts.className,
inactiveClassName: opts.inactiveClassName,
pattern
}
nodes.push(el)
// Trigger the action right away
checkActive(el)
return {
// When the element is destroyed, remove it from the list
destroy() {
nodes.splice(nodes.indexOf(el), 1)
}
}
}