1
import Vue from 'vue'
2
import { VNode, VNodeDirective } from 'vue/types'
3
import { VuetifyIcon } from 'vuetify/types/services/icons'
4
import { DataTableCompareFunction, SelectItemKey, ItemGroup } from 'vuetify/types'
5

6
export function createSimpleFunctional (
7
  c: string,
8 1
  el = 'div',
9
  name?: string
10 1
) {
11 1
  return Vue.extend({
12 1
    name: name || c.replace(/__/g, '-'),
13

14
    functional: true,
15

16 1
    render (h, { data, children }): VNode {
17 1
      data.staticClass = (`${c} ${data.staticClass || ''}`).trim()
18

19 1
      return h(el, data, children)
20
    },
21
  })
22
}
23

24
export type BindingConfig = Pick<VNodeDirective, 'arg' | 'modifiers' | 'value'>
25 1
export function directiveConfig (binding: BindingConfig, defaults = {}): VNodeDirective {
26 0
  return {
27
    ...defaults,
28
    ...binding.modifiers,
29
    value: binding.arg,
30 1
    ...(binding.value || {}),
31
  }
32
}
33

34
export function addOnceEventListener (
35
  el: EventTarget,
36
  eventName: string,
37
  cb: (event: Event) => void,
38 1
  options: boolean | AddEventListenerOptions = false
39 1
): void {
40 1
  var once = (event: Event) => {
41 1
    cb(event)
42 1
    el.removeEventListener(eventName, once, options)
43
  }
44

45 1
  el.addEventListener(eventName, once, options)
46
}
47

48 1
let passiveSupported = false
49 1
try {
50 1
  if (typeof window !== 'undefined') {
51 1
    const testListenerOpts = Object.defineProperty({}, 'passive', {
52 1
      get: () => {
53 1
        passiveSupported = true
54
      },
55
    })
56

57 1
    window.addEventListener('testListener', testListenerOpts, testListenerOpts)
58 1
    window.removeEventListener('testListener', testListenerOpts, testListenerOpts)
59
  }
60 0
} catch (e) { console.warn(e) }
61
export { passiveSupported }
62

63
export function addPassiveEventListener (
64
  el: EventTarget,
65
  event: string,
66
  cb: EventHandlerNonNull | (() => void),
67
  options: {}
68 1
): void {
69 1
  el.addEventListener(event, cb, passiveSupported ? options : false)
70
}
71

72 1
export function getNestedValue (obj: any, path: (string | number)[], fallback?: any): any {
73 1
  const last = path.length - 1
74

75 1
  if (last < 0) return obj === undefined ? fallback : obj
76

77 1
  for (let i = 0; i < last; i++) {
78 1
    if (obj == null) {
79 1
      return fallback
80
    }
81 1
    obj = obj[path[i]]
82
  }
83

84 1
  if (obj == null) return fallback
85

86 1
  return obj[path[last]] === undefined ? fallback : obj[path[last]]
87
}
88

89 1
export function deepEqual (a: any, b: any): boolean {
90 1
  if (a === b) return true
91

92 1
  if (
93 1
    a instanceof Date &&
94 1
    b instanceof Date &&
95 1
    a.getTime() !== b.getTime()
96
  ) {
97
    // If the values are Date, compare them as timestamps
98 1
    return false
99
  }
100

101 1
  if (a !== Object(a) || b !== Object(b)) {
102
    // If the values aren't objects, they were already checked for equality
103 1
    return false
104
  }
105

106 1
  const props = Object.keys(a)
107

108 1
  if (props.length !== Object.keys(b).length) {
109
    // Different number of props, don't bother to check
110 1
    return false
111
  }
112

113 1
  return props.every(p => deepEqual(a[p], b[p]))
114
}
115

116 1
export function getObjectValueByPath (obj: any, path: string, fallback?: any): any {
117
  // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621
118 1
  if (obj == null || !path || typeof path !== 'string') return fallback
119 1
  if (obj[path] !== undefined) return obj[path]
120 1
  path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties
121 1
  path = path.replace(/^\./, '') // strip a leading dot
122 1
  return getNestedValue(obj, path.split('.'), fallback)
123
}
124

125
export function getPropertyFromItem (
126
  item: object,
127
  property: SelectItemKey,
128
  fallback?: any
129 1
): any {
130 1
  if (property == null) return item === undefined ? fallback : item
131

132 1
  if (item !== Object(item)) return fallback === undefined ? item : fallback
133

134 1
  if (typeof property === 'string') return getObjectValueByPath(item, property, fallback)
135

136 1
  if (Array.isArray(property)) return getNestedValue(item, property, fallback)
137

138 1
  if (typeof property !== 'function') return fallback
139

140 1
  const value = property(item, fallback)
141

142 1
  return typeof value === 'undefined' ? fallback : value
143
}
144

145 1
export function createRange (length: number): number[] {
146 1
  return Array.from({ length }, (v, k) => k)
147
}
148

149 1
export function getZIndex (el?: Element | null): number {
150 1
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0
151

152 1
  const index = +window.getComputedStyle(el).getPropertyValue('z-index')
153

154 1
  if (!index) return getZIndex(el.parentNode as Element)
155 1
  return index
156
}
157

158 1
const tagsToReplace = {
159
  '&': '&amp;',
160
  '<': '&lt;',
161
  '>': '&gt;',
162
} as any
163

164 1
export function escapeHTML (str: string): string {
165 1
  return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag)
166
}
167

168 1
export function filterObjectOnKeys<T, K extends keyof T> (obj: T, keys: K[]): { [N in K]: T[N] } {
169 1
  const filtered = {} as { [N in K]: T[N] }
170

171 1
  for (let i = 0; i < keys.length; i++) {
172 1
    const key = keys[i]
173 1
    if (typeof obj[key] !== 'undefined') {
174 1
      filtered[key] = obj[key]
175
    }
176
  }
177

178 1
  return filtered
179
}
180

181 1
export function convertToUnit (str: string | number | null | undefined, unit = 'px'): string | undefined {
182 1
  if (str == null || str === '') {
183 1
    return undefined
184 1
  } else if (isNaN(+str!)) {
185 1
    return String(str)
186
  } else {
187 1
    return `${Number(str)}${unit}`
188
  }
189
}
190

191 1
export function kebabCase (str: string): string {
192 1
  return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
193
}
194

195 1
export function isObject (obj: any): obj is object {
196 1
  return obj !== null && typeof obj === 'object'
197
}
198

199
// KeyboardEvent.keyCode aliases
200 1
export const keyCodes = Object.freeze({
201
  enter: 13,
202
  tab: 9,
203
  delete: 46,
204
  esc: 27,
205
  space: 32,
206
  up: 38,
207
  down: 40,
208
  left: 37,
209
  right: 39,
210
  end: 35,
211
  home: 36,
212
  del: 46,
213
  backspace: 8,
214
  insert: 45,
215
  pageup: 33,
216
  pagedown: 34,
217
})
218

219
// This remaps internal names like '$cancel' or '$vuetify.icons.cancel'
220
// to the current name or component for that icon.
221 1
export function remapInternalIcon (vm: Vue, iconName: string): VuetifyIcon {
222 1
  if (!iconName.startsWith('$')) {
223 1
    return iconName
224
  }
225

226
  // Get the target icon name
227 1
  const iconPath = `$vuetify.icons.values.${iconName.split('$').pop()!.split('.').pop()}`
228

229
  // Now look up icon indirection name,
230
  // e.g. '$vuetify.icons.values.cancel'
231 1
  return getObjectValueByPath(vm, iconPath, iconName)
232
}
233

234 1
export function keys<O> (o: O) {
235 1
  return Object.keys(o) as (keyof O)[]
236
}
237

238
/**
239
 * Camelize a hyphen-delimited string.
240
 */
241 1
const camelizeRE = /-(\w)/g
242 1
export const camelize = (str: string): string => {
243 1
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
244
}
245

246
/**
247
 * Returns the set difference of B and A, i.e. the set of elements in B but not in A
248
 */
249 1
export function arrayDiff (a: any[], b: any[]): any[] {
250 1
  const diff: any[] = []
251 1
  for (let i = 0; i < b.length; i++) {
252 1
    if (a.indexOf(b[i]) < 0) diff.push(b[i])
253
  }
254 1
  return diff
255
}
256

257
/**
258
 * Makes the first character of a string uppercase
259
 */
260 1
export function upperFirst (str: string): string {
261 1
  return str.charAt(0).toUpperCase() + str.slice(1)
262
}
263

264
export function groupItems<T extends any = any> (
265
  items: T[],
266
  groupBy: string[],
267
  groupDesc: boolean[]
268 1
): ItemGroup<T>[] {
269 1
  const key = groupBy[0]
270 1
  const groups: ItemGroup<T>[] = []
271 1
  let current = null
272 1
  for (var i = 0; i < items.length; i++) {
273 1
    const item = items[i]
274 1
    const val = getObjectValueByPath(item, key)
275 1
    if (current !== val) {
276 1
      current = val
277 1
      groups.push({
278
        name: val,
279
        items: [],
280
      })
281
    }
282 1
    groups[groups.length - 1].items.push(item)
283
  }
284 1
  return groups
285
}
286

287 1
export function wrapInArray<T> (v: T | T[] | null | undefined): T[] { return v != null ? Array.isArray(v) ? v : [v] : [] }
288

289
export function sortItems<T extends any = any> (
290
  items: T[],
291
  sortBy: string[],
292
  sortDesc: boolean[],
293
  locale: string,
294
  customSorters?: Record<string, DataTableCompareFunction<T>>
295 1
): T[] {
296 1
  if (sortBy === null || !sortBy.length) return items
297 1
  const stringCollator = new Intl.Collator(locale, { sensitivity: 'accent', usage: 'sort' })
298

299 1
  return items.sort((a, b) => {
300 1
    for (let i = 0; i < sortBy.length; i++) {
301 1
      const sortKey = sortBy[i]
302

303 1
      let sortA = getObjectValueByPath(a, sortKey)
304 1
      let sortB = getObjectValueByPath(b, sortKey)
305

306 1
      if (sortDesc[i]) {
307 1
        [sortA, sortB] = [sortB, sortA]
308
      }
309

310 1
      if (customSorters && customSorters[sortKey]) {
311 1
        const customResult = customSorters[sortKey](sortA, sortB)
312

313 1
        if (!customResult) continue
314

315 1
        return customResult
316
      }
317

318
      // Check if both cannot be evaluated
319 1
      if (sortA === null && sortB === null) {
320 0
        continue
321
      }
322

323 1
      [sortA, sortB] = [sortA, sortB].map(s => (s || '').toString().toLocaleLowerCase())
324

325 1
      if (sortA !== sortB) {
326 1
        if (!isNaN(sortA) && !isNaN(sortB)) return Number(sortA) - Number(sortB)
327 1
        return stringCollator.compare(sortA, sortB)
328
      }
329
    }
330

331 1
    return 0
332
  })
333
}
334

335 1
export function defaultFilter (value: any, search: string | null, item: any) {
336 1
  return value != null &&
337 1
    search != null &&
338 1
    typeof value !== 'boolean' &&
339 1
    value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1
340
}
341

342 1
export function searchItems<T extends any = any> (items: T[], search: string): T[] {
343 1
  if (!search) return items
344 1
  search = search.toString().toLowerCase()
345 1
  if (search.trim() === '') return items
346

347 1
  return items.filter((item: any) => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search, item)))
348
}
349

350
/**
351
 * Returns:
352
 *  - 'normal' for old style slots - `<template slot="default">`
353
 *  - 'scoped' for old style scoped slots (`<template slot="default" slot-scope="data">`) or bound v-slot (`#default="data"`)
354
 *  - 'v-slot' for unbound v-slot (`#default`) - only if the third param is true, otherwise counts as scoped
355
 */
356 1
export function getSlotType<T extends boolean = false> (vm: Vue, name: string, split?: T): (T extends true ? 'v-slot' : never) | 'normal' | 'scoped' | void {
357 1
  if (vm.$slots[name] && vm.$scopedSlots[name] && (vm.$scopedSlots[name] as any).name) {
358 1
    return split ? 'v-slot' as any : 'scoped'
359
  }
360 1
  if (vm.$slots[name]) return 'normal'
361 1
  if (vm.$scopedSlots[name]) return 'scoped'
362
}
363

364 1
export function debounce (fn: Function, delay: number) {
365 1
  let timeoutId = 0 as any
366 1
  return (...args: any[]) => {
367 0
    clearTimeout(timeoutId)
368 0
    timeoutId = setTimeout(() => fn(...args), delay)
369
  }
370
}
371

372 1
export function throttle<T extends (...args: any[]) => any> (fn: T, limit: number) {
373 1
  let throttling = false
374 1
  return (...args: Parameters<T>): void | ReturnType<T> => {
375 1
    if (!throttling) {
376 1
      throttling = true
377 1
      setTimeout(() => throttling = false, limit)
378 1
      return fn(...args)
379
    }
380
  }
381
}
382

383 1
export function getPrefixedScopedSlots (prefix: string, scopedSlots: any) {
384 1
  return Object.keys(scopedSlots).filter(k => k.startsWith(prefix)).reduce((obj: any, k: string) => {
385 1
    obj[k.replace(prefix, '')] = scopedSlots[k]
386 1
    return obj
387
  }, {})
388
}
389

390 1
export function getSlot (vm: Vue, name = 'default', data?: object | (() => object), optional = false) {
391 1
  if (vm.$scopedSlots[name]) {
392 1
    return vm.$scopedSlots[name]!(data instanceof Function ? data() : data)
393 1
  } else if (vm.$slots[name] && (!data || optional)) {
394 0
    return vm.$slots[name]
395
  }
396 1
  return undefined
397
}
398

399 1
export function clamp (value: number, min = 0, max = 1) {
400 1
  return Math.max(min, Math.min(max, value))
401
}
402

403 1
export function padEnd (str: string, length: number, char = '0') {
404 1
  return str + char.repeat(Math.max(0, length - str.length))
405
}
406

407 1
export function chunk (str: string, size = 1) {
408 1
  const chunked: string[] = []
409 1
  let index = 0
410 1
  while (index < str.length) {
411 1
    chunked.push(str.substr(index, size))
412 1
    index += size
413
  }
414 1
  return chunked
415
}
416

417 1
export function humanReadableFileSize (bytes: number, binary = false): string {
418 1
  const base = binary ? 1024 : 1000
419 1
  if (bytes < base) {
420 1
    return `${bytes} B`
421
  }
422

423 1
  const prefix = binary ? ['Ki', 'Mi', 'Gi'] : ['k', 'M', 'G']
424 1
  let unit = -1
425 1
  while (Math.abs(bytes) >= base && unit < prefix.length - 1) {
426 1
    bytes /= base
427 1
    ++unit
428
  }
429 1
  return `${bytes.toFixed(1)} ${prefix[unit]}B`
430
}
431

432 1
export function camelizeObjectKeys (obj: Record<string, any> | null | undefined) {
433 1
  if (!obj) return {}
434

435 1
  return Object.keys(obj).reduce((o: any, key: string) => {
436 1
    o[camelize(key)] = obj[key]
437 1
    return o
438
  }, {})
439
}
440

441
export function mergeDeep (
442 1
  source: Dictionary<any> = {},
443 1
  target: Dictionary<any> = {}
444 1
) {
445 1
  for (const key in target) {
446 1
    const sourceProperty = source[key]
447 1
    const targetProperty = target[key]
448

449
    // Only continue deep merging if
450
    // both properties are objects
451 1
    if (
452 1
      isObject(sourceProperty) &&
453 1
      isObject(targetProperty)
454
    ) {
455 1
      source[key] = mergeDeep(sourceProperty, targetProperty)
456

457 1
      continue
458
    }
459

460 1
    source[key] = targetProperty
461
  }
462

463 1
  return source
464
}
465

466 1
export function fillArray<T> (length: number, obj: T) {
467 1
  return Array(length).fill(obj)
468
}

Read our documentation on viewing source code .

Loading