1 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 1
export function createSimpleFunctional (
7
  c: string,
8 1
  el = 'div',
9 1
  name?: string
10
) {
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 1
export function addOnceEventListener (
35
  el: EventTarget,
36
  eventName: string,
37
  cb: (event: Event) => void,
38 1
  options: boolean | AddEventListenerOptions = false
39
): 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 1
export { passiveSupported }
62

63 1
export function addPassiveEventListener (
64
  el: EventTarget,
65
  event: string,
66
  cb: EventHandlerNonNull | (() => void),
67 1
  options: {}
68
): 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 (a instanceof Date && b instanceof Date) {
93
    // If the values are Date, they were convert to timestamp with getTime and compare it
94 1
    if (a.getTime() !== b.getTime()) return false
95
  }
96

97 1
  if (a !== Object(a) || b !== Object(b)) {
98
    // If the values aren't objects, they were already checked for equality
99 1
    return false
100
  }
101

102 1
  const props = Object.keys(a)
103

104 1
  if (props.length !== Object.keys(b).length) {
105
    // Different number of props, don't bother to check
106 1
    return false
107
  }
108

109 1
  return props.every(p => deepEqual(a[p], b[p]))
110
}
111

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

121 1
export function getPropertyFromItem (
122
  item: object,
123
  property: SelectItemKey,
124 1
  fallback?: any
125
): any {
126 1
  if (property == null) return item === undefined ? fallback : item
127

128 1
  if (item !== Object(item)) return fallback === undefined ? item : fallback
129

130 1
  if (typeof property === 'string') return getObjectValueByPath(item, property, fallback)
131

132 1
  if (Array.isArray(property)) return getNestedValue(item, property, fallback)
133

134 1
  if (typeof property !== 'function') return fallback
135

136 1
  const value = property(item, fallback)
137

138 1
  return typeof value === 'undefined' ? fallback : value
139
}
140

141 1
export function createRange (length: number): number[] {
142 1
  return Array.from({ length }, (v, k) => k)
143
}
144

145 1
export function getZIndex (el?: Element | null): number {
146 1
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0
147

148 1
  const index = +window.getComputedStyle(el).getPropertyValue('z-index')
149

150 1
  if (!index) return getZIndex(el.parentNode as Element)
151 1
  return index
152
}
153

154 1
const tagsToReplace = {
155
  '&': '&amp;',
156
  '<': '&lt;',
157
  '>': '&gt;',
158
} as any
159

160 1
export function escapeHTML (str: string): string {
161 1
  return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag)
162
}
163

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

167 1
  for (let i = 0; i < keys.length; i++) {
168 1
    const key = keys[i]
169 1
    if (typeof obj[key] !== 'undefined') {
170 1
      filtered[key] = obj[key]
171
    }
172
  }
173

174 1
  return filtered
175
}
176

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

187 1
export function kebabCase (str: string): string {
188 1
  return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
189
}
190

191 1
export function isObject (obj: any): obj is object {
192 1
  return obj !== null && typeof obj === 'object'
193
}
194

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

215
// This remaps internal names like '$cancel' or '$vuetify.icons.cancel'
216
// to the current name or component for that icon.
217 1
export function remapInternalIcon (vm: Vue, iconName: string): VuetifyIcon {
218
  // Look for custom component in the configuration
219 1
  const component = getObjectValueByPath(vm, '$vuetify.icons.component', null)
220 1
  const usingCustomComponent = component != null
221

222
  // Look for overrides
223 1
  if (iconName.startsWith('$')) {
224
    // Get the target icon name
225 1
    const iconPath = `$vuetify.icons.values.${iconName.split('$').pop()!.split('.').pop()}`
226

227
    // Now look up icon indirection name,
228
    // e.g. '$vuetify.icons.values.cancel'
229 1
    const override = getObjectValueByPath(vm, iconPath, iconName)
230

231 1
    if (typeof override === 'string') iconName = override
232 1
    else return override
233
  }
234

235 1
  if (!usingCustomComponent) {
236 1
    return iconName
237
  }
238

239 0
  return {
240
    component,
241
    props: {
242
      icon: iconName,
243
    },
244
  }
245
}
246

247 1
export function keys<O> (o: O) {
248 1
  return Object.keys(o) as (keyof O)[]
249
}
250

251
/**
252
 * Camelize a hyphen-delimited string.
253
 */
254 1
const camelizeRE = /-(\w)/g
255 1
export const camelize = (str: string): string => {
256 1
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
257
}
258

259
/**
260
 * Returns the set difference of B and A, i.e. the set of elements in B but not in A
261
 */
262 1
export function arrayDiff (a: any[], b: any[]): any[] {
263 1
  const diff: any[] = []
264 1
  for (let i = 0; i < b.length; i++) {
265 1
    if (a.indexOf(b[i]) < 0) diff.push(b[i])
266
  }
267 1
  return diff
268
}
269

270
/**
271
 * Makes the first character of a string uppercase
272
 */
273 1
export function upperFirst (str: string): string {
274 1
  return str.charAt(0).toUpperCase() + str.slice(1)
275
}
276

277 1
export function groupItems<T extends any = any> (
278
  items: T[],
279
  groupBy: string[],
280 1
  groupDesc: boolean[]
281
): ItemGroup<T>[] {
282 1
  const key = groupBy[0]
283 1
  const groups: ItemGroup<T>[] = []
284 1
  let current = null
285 1
  for (var i = 0; i < items.length; i++) {
286 1
    const item = items[i]
287 1
    const val = getObjectValueByPath(item, key)
288 1
    if (current !== val) {
289 1
      current = val
290 1
      groups.push({
291
        name: val,
292
        items: [],
293
      })
294
    }
295 1
    groups[groups.length - 1].items.push(item)
296
  }
297 1
  return groups
298
}
299

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

302 1
export function sortItems<T extends any = any> (
303
  items: T[],
304
  sortBy: string[],
305
  sortDesc: boolean[],
306
  locale: string,
307 1
  customSorters?: Record<string, DataTableCompareFunction<T>>
308
): T[] {
309 1
  if (sortBy === null || !sortBy.length) return items
310 1
  const stringCollator = new Intl.Collator(locale, { sensitivity: 'accent', usage: 'sort' })
311

312 1
  return items.sort((a, b) => {
313 1
    for (let i = 0; i < sortBy.length; i++) {
314 1
      const sortKey = sortBy[i]
315

316 1
      let sortA = getObjectValueByPath(a, sortKey)
317 1
      let sortB = getObjectValueByPath(b, sortKey)
318

319 1
      if (sortDesc[i]) {
320 1
        [sortA, sortB] = [sortB, sortA]
321
      }
322

323 1
      if (customSorters && customSorters[sortKey]) {
324 1
        const customResult = customSorters[sortKey](sortA, sortB)
325

326 1
        if (!customResult) continue
327

328 1
        return customResult
329
      }
330

331
      // Check if both cannot be evaluated
332 1
      if (sortA === null && sortB === null) {
333 0
        continue
334
      }
335

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

338 1
      if (sortA !== sortB) {
339 1
        if (!isNaN(sortA) && !isNaN(sortB)) return Number(sortA) - Number(sortB)
340 1
        return stringCollator.compare(sortA, sortB)
341
      }
342
    }
343

344 1
    return 0
345
  })
346
}
347

348 1
export function defaultFilter (value: any, search: string | null, item: any) {
349 1
  return value != null &&
350 1
    search != null &&
351 1
    typeof value !== 'boolean' &&
352 1
    value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1
353
}
354

355 1
export function searchItems<T extends any = any> (items: T[], search: string): T[] {
356 1
  if (!search) return items
357 1
  search = search.toString().toLowerCase()
358 1
  if (search.trim() === '') return items
359

360 1
  return items.filter((item: any) => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search, item)))
361
}
362

363
/**
364
 * Returns:
365
 *  - 'normal' for old style slots - `<template slot="default">`
366
 *  - 'scoped' for old style scoped slots (`<template slot="default" slot-scope="data">`) or bound v-slot (`#default="data"`)
367
 *  - 'v-slot' for unbound v-slot (`#default`) - only if the third param is true, otherwise counts as scoped
368
 */
369 1
export function getSlotType<T extends boolean = false> (vm: Vue, name: string, split?: T): (T extends true ? 'v-slot' : never) | 'normal' | 'scoped' | void {
370 1
  if (vm.$slots[name] && vm.$scopedSlots[name] && (vm.$scopedSlots[name] as any).name) {
371 1
    return split ? 'v-slot' as any : 'scoped'
372
  }
373 1
  if (vm.$slots[name]) return 'normal'
374 1
  if (vm.$scopedSlots[name]) return 'scoped'
375
}
376

377 1
export function debounce (fn: Function, delay: number) {
378 1
  let timeoutId = 0 as any
379 1
  return (...args: any[]) => {
380 0
    clearTimeout(timeoutId)
381 0
    timeoutId = setTimeout(() => fn(...args), delay)
382
  }
383
}
384

385 1
export function throttle<T extends (...args: any[]) => any> (fn: T, limit: number) {
386 1
  let throttling = false
387 1
  return (...args: Parameters<T>): void | ReturnType<T> => {
388 1
    if (!throttling) {
389 1
      throttling = true
390 1
      setTimeout(() => throttling = false, limit)
391 1
      return fn(...args)
392
    }
393
  }
394
}
395

396 1
export function getPrefixedScopedSlots (prefix: string, scopedSlots: any) {
397 1
  return Object.keys(scopedSlots).filter(k => k.startsWith(prefix)).reduce((obj: any, k: string) => {
398 1
    obj[k.replace(prefix, '')] = scopedSlots[k]
399 1
    return obj
400
  }, {})
401
}
402

403 1
export function getSlot (vm: Vue, name = 'default', data?: object | (() => object), optional = false) {
404 1
  if (vm.$scopedSlots[name]) {
405 1
    return vm.$scopedSlots[name]!(data instanceof Function ? data() : data)
406 1
  } else if (vm.$slots[name] && (!data || optional)) {
407 0
    return vm.$slots[name]
408
  }
409 1
  return undefined
410
}
411

412 1
export function clamp (value: number, min = 0, max = 1) {
413 1
  return Math.max(min, Math.min(max, value))
414
}
415

416 1
export function padEnd (str: string, length: number, char = '0') {
417 1
  return str + char.repeat(Math.max(0, length - str.length))
418
}
419

420 1
export function chunk (str: string, size = 1) {
421 1
  const chunked: string[] = []
422 1
  let index = 0
423 1
  while (index < str.length) {
424 1
    chunked.push(str.substr(index, size))
425 1
    index += size
426
  }
427 1
  return chunked
428
}
429

430 1
export function humanReadableFileSize (bytes: number, binary = false): string {
431 1
  const base = binary ? 1024 : 1000
432 1
  if (bytes < base) {
433 1
    return `${bytes} B`
434
  }
435

436 1
  const prefix = binary ? ['Ki', 'Mi', 'Gi'] : ['k', 'M', 'G']
437 1
  let unit = -1
438 1
  while (Math.abs(bytes) >= base && unit < prefix.length - 1) {
439 1
    bytes /= base
440 1
    ++unit
441
  }
442 1
  return `${bytes.toFixed(1)} ${prefix[unit]}B`
443
}
444

445 1
export function camelizeObjectKeys (obj: Record<string, any> | null | undefined) {
446 1
  if (!obj) return {}
447

448 1
  return Object.keys(obj).reduce((o: any, key: string) => {
449 1
    o[camelize(key)] = obj[key]
450 1
    return o
451
  }, {})
452
}
453

454 1
export function mergeDeep (
455 1
  source: Dictionary<any> = {},
456 1
  target: Dictionary<any> = {}
457
) {
458 1
  for (const key in target) {
459 1
    const sourceProperty = source[key]
460 1
    const targetProperty = target[key]
461

462
    // Only continue deep merging if
463
    // both properties are objects
464 1
    if (
465 1
      isObject(sourceProperty) &&
466 1
      isObject(targetProperty)
467
    ) {
468 1
      source[key] = mergeDeep(sourceProperty, targetProperty)
469

470 1
      continue
471
    }
472

473 1
    source[key] = targetProperty
474
  }
475

476 1
  return source
477
}
478

479 1
export function fillArray<T> (length: number, obj: T) {
480 1
  return Array(length).fill(obj)
481
}

Read our documentation on viewing source code .

Loading