UNPKG

7.06 kBJavaScriptView Raw
1/* eslint-env browser */
2
3/**
4 * Utility module to work with the DOM.
5 *
6 * @module dom
7 */
8
9import * as pair from './pair.js'
10import * as map from './map.js'
11
12/* istanbul ignore next */
13/**
14 * @type {Document}
15 */
16export const doc = /** @type {Document} */ (typeof document !== 'undefined' ? document : {})
17
18/**
19 * @param {string} name
20 * @return {HTMLElement}
21 */
22/* istanbul ignore next */
23export const createElement = name => doc.createElement(name)
24
25/**
26 * @return {DocumentFragment}
27 */
28/* istanbul ignore next */
29export const createDocumentFragment = () => doc.createDocumentFragment()
30
31/**
32 * @param {string} text
33 * @return {Text}
34 */
35/* istanbul ignore next */
36export const createTextNode = text => doc.createTextNode(text)
37
38/* istanbul ignore next */
39export const domParser = /** @type {DOMParser} */ (typeof DOMParser !== 'undefined' ? new DOMParser() : null)
40
41/**
42 * @param {HTMLElement} el
43 * @param {string} name
44 * @param {Object} opts
45 */
46/* istanbul ignore next */
47export const emitCustomEvent = (el, name, opts) => el.dispatchEvent(new CustomEvent(name, opts))
48
49/**
50 * @param {Element} el
51 * @param {Array<pair.Pair<string,string|boolean>>} attrs Array of key-value pairs
52 * @return {Element}
53 */
54/* istanbul ignore next */
55export const setAttributes = (el, attrs) => {
56 pair.forEach(attrs, (key, value) => {
57 if (value === false) {
58 el.removeAttribute(key)
59 } else if (value === true) {
60 el.setAttribute(key, '')
61 } else {
62 // @ts-ignore
63 el.setAttribute(key, value)
64 }
65 })
66 return el
67}
68
69/**
70 * @param {Element} el
71 * @param {Map<string, string>} attrs Array of key-value pairs
72 * @return {Element}
73 */
74/* istanbul ignore next */
75export const setAttributesMap = (el, attrs) => {
76 attrs.forEach((value, key) => { el.setAttribute(key, value) })
77 return el
78}
79
80/**
81 * @param {Array<Node>|HTMLCollection} children
82 * @return {DocumentFragment}
83 */
84/* istanbul ignore next */
85export const fragment = children => {
86 const fragment = createDocumentFragment()
87 for (let i = 0; i < children.length; i++) {
88 appendChild(fragment, children[i])
89 }
90 return fragment
91}
92
93/**
94 * @param {Element} parent
95 * @param {Array<Node>} nodes
96 * @return {Element}
97 */
98/* istanbul ignore next */
99export const append = (parent, nodes) => {
100 appendChild(parent, fragment(nodes))
101 return parent
102}
103
104/**
105 * @param {HTMLElement} el
106 */
107/* istanbul ignore next */
108export const remove = el => el.remove()
109
110/**
111 * @param {EventTarget} el
112 * @param {string} name
113 * @param {EventListener} f
114 */
115/* istanbul ignore next */
116export const addEventListener = (el, name, f) => el.addEventListener(name, f)
117
118/**
119 * @param {EventTarget} el
120 * @param {string} name
121 * @param {EventListener} f
122 */
123/* istanbul ignore next */
124export const removeEventListener = (el, name, f) => el.removeEventListener(name, f)
125
126/**
127 * @param {Node} node
128 * @param {Array<pair.Pair<string,EventListener>>} listeners
129 * @return {Node}
130 */
131/* istanbul ignore next */
132export const addEventListeners = (node, listeners) => {
133 pair.forEach(listeners, (name, f) => addEventListener(node, name, f))
134 return node
135}
136
137/**
138 * @param {Node} node
139 * @param {Array<pair.Pair<string,EventListener>>} listeners
140 * @return {Node}
141 */
142/* istanbul ignore next */
143export const removeEventListeners = (node, listeners) => {
144 pair.forEach(listeners, (name, f) => removeEventListener(node, name, f))
145 return node
146}
147
148/**
149 * @param {string} name
150 * @param {Array<pair.Pair<string,string>|pair.Pair<string,boolean>>} attrs Array of key-value pairs
151 * @param {Array<Node>} children
152 * @return {Element}
153 */
154/* istanbul ignore next */
155export const element = (name, attrs = [], children = []) =>
156 append(setAttributes(createElement(name), attrs), children)
157
158/**
159 * @param {number} width
160 * @param {number} height
161 */
162/* istanbul ignore next */
163export const canvas = (width, height) => {
164 const c = /** @type {HTMLCanvasElement} */ (createElement('canvas'))
165 c.height = height
166 c.width = width
167 return c
168}
169
170/**
171 * @param {string} t
172 * @return {Text}
173 */
174/* istanbul ignore next */
175export const text = createTextNode
176
177/**
178 * @param {pair.Pair<string,string>} pair
179 */
180/* istanbul ignore next */
181export const pairToStyleString = pair => `${pair.left}:${pair.right};`
182
183/**
184 * @param {Array<pair.Pair<string,string>>} pairs
185 * @return {string}
186 */
187/* istanbul ignore next */
188export const pairsToStyleString = pairs => pairs.map(pairToStyleString).join('')
189
190/**
191 * @param {Map<string,string>} m
192 * @return {string}
193 */
194/* istanbul ignore next */
195export const mapToStyleString = m => map.map(m, (value, key) => `${key}:${value};`).join('')
196
197/**
198 * @todo should always query on a dom element
199 *
200 * @param {HTMLElement|ShadowRoot} el
201 * @param {string} query
202 * @return {HTMLElement | null}
203 */
204/* istanbul ignore next */
205export const querySelector = (el, query) => el.querySelector(query)
206
207/**
208 * @param {HTMLElement|ShadowRoot} el
209 * @param {string} query
210 * @return {NodeListOf<HTMLElement>}
211 */
212/* istanbul ignore next */
213export const querySelectorAll = (el, query) => el.querySelectorAll(query)
214
215/**
216 * @param {string} id
217 * @return {HTMLElement}
218 */
219/* istanbul ignore next */
220export const getElementById = id => /** @type {HTMLElement} */ (doc.getElementById(id))
221
222/**
223 * @param {string} html
224 * @return {HTMLElement}
225 */
226/* istanbul ignore next */
227const _parse = html => domParser.parseFromString(`<html><body>${html}</body></html>`, 'text/html').body
228
229/**
230 * @param {string} html
231 * @return {DocumentFragment}
232 */
233/* istanbul ignore next */
234export const parseFragment = html => fragment(/** @type {any} */ (_parse(html).childNodes))
235
236/**
237 * @param {string} html
238 * @return {HTMLElement}
239 */
240/* istanbul ignore next */
241export const parseElement = html => /** @type HTMLElement */ (_parse(html).firstElementChild)
242
243/**
244 * @param {HTMLElement} oldEl
245 * @param {HTMLElement|DocumentFragment} newEl
246 */
247/* istanbul ignore next */
248export const replaceWith = (oldEl, newEl) => oldEl.replaceWith(newEl)
249
250/**
251 * @param {HTMLElement} parent
252 * @param {HTMLElement} el
253 * @param {Node|null} ref
254 * @return {HTMLElement}
255 */
256/* istanbul ignore next */
257export const insertBefore = (parent, el, ref) => parent.insertBefore(el, ref)
258
259/**
260 * @param {Node} parent
261 * @param {Node} child
262 * @return {Node}
263 */
264/* istanbul ignore next */
265export const appendChild = (parent, child) => parent.appendChild(child)
266
267export const ELEMENT_NODE = doc.ELEMENT_NODE
268export const TEXT_NODE = doc.TEXT_NODE
269export const CDATA_SECTION_NODE = doc.CDATA_SECTION_NODE
270export const COMMENT_NODE = doc.COMMENT_NODE
271export const DOCUMENT_NODE = doc.DOCUMENT_NODE
272export const DOCUMENT_TYPE_NODE = doc.DOCUMENT_TYPE_NODE
273export const DOCUMENT_FRAGMENT_NODE = doc.DOCUMENT_FRAGMENT_NODE
274
275/**
276 * @param {any} node
277 * @param {number} type
278 */
279export const checkNodeType = (node, type) => node.nodeType === type
280
281/**
282 * @param {Node} parent
283 * @param {HTMLElement} child
284 */
285export const isParentOf = (parent, child) => {
286 let p = child.parentNode
287 while (p && p !== parent) {
288 p = p.parentNode
289 }
290 return p === parent
291}