Blame view

node_modules/bootstrap-vue/src/utils/dom.js 10.4 KB
4cd4fd28   郭伟龙   feat: 初始化项目
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
import { DOCUMENT, WINDOW } from '../constants/env'
import { Element } from '../constants/safe-types'
import { from as arrayFrom } from './array'
import { isFunction, isNull } from './inspect'
import { toFloat } from './number'
import { toString } from './string'

// --- Constants ---

const ELEMENT_PROTO = Element.prototype

const TABABLE_SELECTOR = [
  'button',
  '[href]:not(.disabled)',
  'input',
  'select',
  'textarea',
  '[tabindex]',
  '[contenteditable]'
]
  .map(s => `${s}:not(:disabled):not([disabled])`)
  .join(', ')

// --- Normalization utils ---

// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill
/* istanbul ignore next */
export const matchesEl =
  ELEMENT_PROTO.matches || ELEMENT_PROTO.msMatchesSelector || ELEMENT_PROTO.webkitMatchesSelector

// See: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
/* istanbul ignore next */
export const closestEl =
  ELEMENT_PROTO.closest ||
  function(sel) {
    let el = this
    do {
      // Use our "patched" matches function
      if (matches(el, sel)) {
        return el
      }
      el = el.parentElement || el.parentNode
    } while (!isNull(el) && el.nodeType === Node.ELEMENT_NODE)
    return null
  }

// `requestAnimationFrame()` convenience method
/* istanbul ignore next: JSDOM always returns the first option */
export const requestAF = (
  WINDOW.requestAnimationFrame ||
  WINDOW.webkitRequestAnimationFrame ||
  WINDOW.mozRequestAnimationFrame ||
  WINDOW.msRequestAnimationFrame ||
  WINDOW.oRequestAnimationFrame ||
  // Fallback, but not a true polyfill
  // Only needed for Opera Mini
  /* istanbul ignore next */
  (cb => setTimeout(cb, 16))
).bind(WINDOW)

export const MutationObs =
  WINDOW.MutationObserver || WINDOW.WebKitMutationObserver || WINDOW.MozMutationObserver || null

// --- Utils ---

// Remove a node from DOM
export const removeNode = el => el && el.parentNode && el.parentNode.removeChild(el)

// Determine if an element is an HTML element
export const isElement = el => !!(el && el.nodeType === Node.ELEMENT_NODE)

// Get the currently active HTML element
export const getActiveElement = (excludes = []) => {
  const { activeElement } = DOCUMENT
  return activeElement && !excludes.some(el => el === activeElement) ? activeElement : null
}

// Returns `true` if a tag's name equals `name`
export const isTag = (tag, name) => toString(tag).toLowerCase() === toString(name).toLowerCase()

// Determine if an HTML element is the currently active element
export const isActiveElement = el => isElement(el) && el === getActiveElement()

// Determine if an HTML element is visible - Faster than CSS check
export const isVisible = el => {
  if (!isElement(el) || !el.parentNode || !contains(DOCUMENT.body, el)) {
    // Note this can fail for shadow dom elements since they
    // are not a direct descendant of document.body
    return false
  }
  if (getStyle(el, 'display') === 'none') {
    // We do this check to help with vue-test-utils when using v-show
    /* istanbul ignore next */
    return false
  }
  // All browsers support getBoundingClientRect(), except JSDOM as it returns all 0's for values :(
  // So any tests that need isVisible will fail in JSDOM
  // Except when we override the getBCR prototype in some tests
  const bcr = getBCR(el)
  return !!(bcr && bcr.height > 0 && bcr.width > 0)
}

// Determine if an element is disabled
export const isDisabled = el =>
  !isElement(el) || el.disabled || hasAttr(el, 'disabled') || hasClass(el, 'disabled')

// Cause/wait-for an element to reflow its content (adjusting its height/width)
export const reflow = el => {
  // Requesting an elements offsetHight will trigger a reflow of the element content
  /* istanbul ignore next: reflow doesn't happen in JSDOM */
  return isElement(el) && el.offsetHeight
}

// Select all elements matching selector. Returns `[]` if none found
export const selectAll = (selector, root) =>
  arrayFrom((isElement(root) ? root : DOCUMENT).querySelectorAll(selector))

// Select a single element, returns `null` if not found
export const select = (selector, root) =>
  (isElement(root) ? root : DOCUMENT).querySelector(selector) || null

// Determine if an element matches a selector
export const matches = (el, selector) => (isElement(el) ? matchesEl.call(el, selector) : false)

// Finds closest element matching selector. Returns `null` if not found
export const closest = (selector, root, includeRoot = false) => {
  if (!isElement(root)) {
    return null
  }
  const el = closestEl.call(root, selector)

  // Native closest behaviour when `includeRoot` is truthy,
  // else emulate jQuery closest and return `null` if match is
  // the passed in root element when `includeRoot` is falsey
  return includeRoot ? el : el === root ? null : el
}

// Returns true if the parent element contains the child element
export const contains = (parent, child) =>
  parent && isFunction(parent.contains) ? parent.contains(child) : false

// Get an element given an ID
export const getById = id => DOCUMENT.getElementById(/^#/.test(id) ? id.slice(1) : id) || null

// Add a class to an element
export const addClass = (el, className) => {
  // We are checking for `el.classList` existence here since IE 11
  // returns `undefined` for some elements (e.g. SVG elements)
  // See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
  if (className && isElement(el) && el.classList) {
    el.classList.add(className)
  }
}

// Remove a class from an element
export const removeClass = (el, className) => {
  // We are checking for `el.classList` existence here since IE 11
  // returns `undefined` for some elements (e.g. SVG elements)
  // See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
  if (className && isElement(el) && el.classList) {
    el.classList.remove(className)
  }
}

// Test if an element has a class
export const hasClass = (el, className) => {
  // We are checking for `el.classList` existence here since IE 11
  // returns `undefined` for some elements (e.g. SVG elements)
  // See https://github.com/bootstrap-vue/bootstrap-vue/issues/2713
  if (className && isElement(el) && el.classList) {
    return el.classList.contains(className)
  }
  return false
}

// Set an attribute on an element
export const setAttr = (el, attr, value) => {
  if (attr && isElement(el)) {
    el.setAttribute(attr, value)
  }
}

// Remove an attribute from an element
export const removeAttr = (el, attr) => {
  if (attr && isElement(el)) {
    el.removeAttribute(attr)
  }
}

// Get an attribute value from an element
// Returns `null` if not found
export const getAttr = (el, attr) => (attr && isElement(el) ? el.getAttribute(attr) : null)

// Determine if an attribute exists on an element
// Returns `true` or `false`, or `null` if element not found
export const hasAttr = (el, attr) => (attr && isElement(el) ? el.hasAttribute(attr) : null)

// Set an style property on an element
export const setStyle = (el, prop, value) => {
  if (prop && isElement(el)) {
    el.style[prop] = value
  }
}

// Remove an style property from an element
export const removeStyle = (el, prop) => {
  if (prop && isElement(el)) {
    el.style[prop] = ''
  }
}

// Get an style property value from an element
// Returns `null` if not found
export const getStyle = (el, prop) => (prop && isElement(el) ? el.style[prop] || null : null)

// Return the Bounding Client Rect of an element
// Returns `null` if not an element
/* istanbul ignore next: getBoundingClientRect() doesn't work in JSDOM */
export const getBCR = el => (isElement(el) ? el.getBoundingClientRect() : null)

// Get computed style object for an element
/* istanbul ignore next: getComputedStyle() doesn't work in JSDOM */
export const getCS = el => {
  const { getComputedStyle } = WINDOW
  return getComputedStyle && isElement(el) ? getComputedStyle(el) : {}
}

// Returns a `Selection` object representing the range of text selected
// Returns `null` if no window support is given
/* istanbul ignore next: getSelection() doesn't work in JSDOM */
export const getSel = () => {
  const { getSelection } = WINDOW
  return getSelection ? WINDOW.getSelection() : null
}

// Return an element's offset with respect to document element
// https://j11y.io/jquery/#v=git&fn=jQuery.fn.offset
export const offset = el => /* istanbul ignore next: getBoundingClientRect(), getClientRects() doesn't work in JSDOM */ {
  const _offset = { top: 0, left: 0 }
  if (!isElement(el) || el.getClientRects().length === 0) {
    return _offset
  }
  const bcr = getBCR(el)
  if (bcr) {
    const win = el.ownerDocument.defaultView
    _offset.top = bcr.top + win.pageYOffset
    _offset.left = bcr.left + win.pageXOffset
  }
  return _offset
}

// Return an element's offset with respect to to its offsetParent
// https://j11y.io/jquery/#v=git&fn=jQuery.fn.position
export const position = el => /* istanbul ignore next: getBoundingClientRect() doesn't work in JSDOM */ {
  let _offset = { top: 0, left: 0 }
  if (!isElement(el)) {
    return _offset
  }
  let parentOffset = { top: 0, left: 0 }
  const elStyles = getCS(el)
  if (elStyles.position === 'fixed') {
    _offset = getBCR(el) || _offset
  } else {
    _offset = offset(el)
    const doc = el.ownerDocument
    let offsetParent = el.offsetParent || doc.documentElement
    while (
      offsetParent &&
      (offsetParent === doc.body || offsetParent === doc.documentElement) &&
      getCS(offsetParent).position === 'static'
    ) {
      offsetParent = offsetParent.parentNode
    }
    if (offsetParent && offsetParent !== el && offsetParent.nodeType === Node.ELEMENT_NODE) {
      parentOffset = offset(offsetParent)
      const offsetParentStyles = getCS(offsetParent)
      parentOffset.top += toFloat(offsetParentStyles.borderTopWidth, 0)
      parentOffset.left += toFloat(offsetParentStyles.borderLeftWidth, 0)
    }
  }
  return {
    top: _offset.top - parentOffset.top - toFloat(elStyles.marginTop, 0),
    left: _offset.left - parentOffset.left - toFloat(elStyles.marginLeft, 0)
  }
}

// Find all tabable elements in the given element
// Assumes users have not used `tabindex` > `0` on elements
export const getTabables = (rootEl = document) =>
  selectAll(TABABLE_SELECTOR, rootEl)
    .filter(isVisible)
    .filter(el => el.tabIndex > -1 && !el.disabled)

// Attempt to focus an element, and return `true` if successful
export const attemptFocus = (el, options = {}) => {
  try {
    el.focus(options)
  } catch {}
  return isActiveElement(el)
}

// Attempt to blur an element, and return `true` if successful
export const attemptBlur = el => {
  try {
    el.blur()
  } catch {}
  return !isActiveElement(el)
}