|
1 |
| -import {AnimationFrame, DOCUMENT} from '../types/safeTypes' |
| 1 | +import {Comment, Slot, VNode} from 'vue' |
| 2 | +import {DOCUMENT} from '../constants/env' |
| 3 | +import {AnimationFrame} from '../types/safeTypes' |
2 | 4 | import {HAS_WINDOW_SUPPORT} from './env'
|
3 | 5 | import {toString} from './stringUtils'
|
4 |
| -import {Comment, Slot, VNode} from 'vue' |
| 6 | + |
| 7 | +const ELEMENT_PROTO = Element.prototype |
| 8 | + |
| 9 | +// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill |
| 10 | +/* istanbul ignore next */ |
| 11 | +export const matchesEl = |
| 12 | + ELEMENT_PROTO.matches || |
| 13 | + (ELEMENT_PROTO as any).msMatchesSelector || |
| 14 | + ELEMENT_PROTO.webkitMatchesSelector |
5 | 15 |
|
6 | 16 | /**
|
7 | 17 | * @param el
|
8 | 18 | * @returns
|
9 | 19 | */
|
10 |
| -export const isElement = (el: HTMLElement): boolean => !!(el && el.nodeType === Node.ELEMENT_NODE) |
| 20 | +export const isElement = (el: HTMLElement | Element): boolean => |
| 21 | + !!(el && el.nodeType === Node.ELEMENT_NODE) |
11 | 22 |
|
12 | 23 | /**
|
13 | 24 | * @param el
|
@@ -148,9 +159,13 @@ export const selectAll = (selector: any, root: any) =>
|
148 | 159 | * @param attr
|
149 | 160 | * @returns
|
150 | 161 | */
|
151 |
| -export const getAttr = (el: HTMLElement, attr: string): string | null => |
| 162 | +export const getAttr = (el: HTMLElement | Element, attr: string): string | null => |
152 | 163 | attr && isElement(el) ? el.getAttribute(attr) : null
|
153 | 164 |
|
| 165 | +// Get an element given an ID |
| 166 | +export const getById = (id: string) => |
| 167 | + DOCUMENT.getElementById(/^#/.test(id) ? id.slice(1) : id) || null |
| 168 | + |
154 | 169 | /**
|
155 | 170 | * @param el
|
156 | 171 | * @param attr
|
@@ -192,3 +207,37 @@ export const requestAF: AnimationFrame = HAS_WINDOW_SUPPORT
|
192 | 207 | // Only needed for Opera Mini
|
193 | 208 | ((cb) => setTimeout(cb, 16))
|
194 | 209 | : (cb) => setTimeout(cb, 0)
|
| 210 | + |
| 211 | +// Determine if an element matches a selector |
| 212 | +export const matches = (el: Element, selector: string) => |
| 213 | + isElement(el) ? matchesEl.call(el, selector) : false |
| 214 | + |
| 215 | +// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest |
| 216 | +/* istanbul ignore next */ |
| 217 | +export const closestEl = |
| 218 | + ELEMENT_PROTO.closest || |
| 219 | + function (this: Element, sel: string) { |
| 220 | + let el = this |
| 221 | + if (!el) return null |
| 222 | + do { |
| 223 | + // Use our "patched" matches function |
| 224 | + if (matches(el, sel)) { |
| 225 | + return el |
| 226 | + } |
| 227 | + el = el.parentElement || (el.parentNode as any) |
| 228 | + } while (el !== null && el.nodeType === Node.ELEMENT_NODE) |
| 229 | + return null |
| 230 | + } |
| 231 | + |
| 232 | +// Finds closest element matching selector. Returns `null` if not found |
| 233 | +export const closest = (selector: string, root: Element, includeRoot = false) => { |
| 234 | + if (!isElement(root)) { |
| 235 | + return null |
| 236 | + } |
| 237 | + const el = closestEl.call(root, selector) |
| 238 | + |
| 239 | + // Native closest behaviour when `includeRoot` is truthy, |
| 240 | + // else emulate jQuery closest and return `null` if match is |
| 241 | + // the passed in root element when `includeRoot` is falsey |
| 242 | + return includeRoot ? el : el === root ? null : el |
| 243 | +} |
0 commit comments