import Vue from 'vue'; export function createSimpleFunctional(c, el = 'div', name) { return Vue.extend({ name: name || c.replace(/__/g, '-'), functional: true, render(h, { data, children }) { data.staticClass = `${c} ${data.staticClass || ''}`.trim(); return h(el, data, children); } }); } export function directiveConfig(binding, defaults = {}) { return { ...defaults, ...binding.modifiers, value: binding.arg, ...(binding.value || {}) }; } export function addOnceEventListener(el, eventName, cb, options = false) { var once = event => { cb(event); el.removeEventListener(eventName, once, options); }; el.addEventListener(eventName, once, options); } let passiveSupported = false; try { if (typeof window !== 'undefined') { const testListenerOpts = Object.defineProperty({}, 'passive', { get: () => { passiveSupported = true; } }); window.addEventListener('testListener', testListenerOpts, testListenerOpts); window.removeEventListener('testListener', testListenerOpts, testListenerOpts); } } catch (e) { console.warn(e); } export { passiveSupported }; export function addPassiveEventListener(el, event, cb, options) { el.addEventListener(event, cb, passiveSupported ? options : false); } export function getNestedValue(obj, path, fallback) { const last = path.length - 1; if (last < 0) return obj === undefined ? fallback : obj; for (let i = 0; i < last; i++) { if (obj == null) { return fallback; } obj = obj[path[i]]; } if (obj == null) return fallback; return obj[path[last]] === undefined ? fallback : obj[path[last]]; } export function deepEqual(a, b) { if (a === b) return true; if (a instanceof Date && b instanceof Date) { // If the values are Date, they were convert to timestamp with getTime and compare it if (a.getTime() !== b.getTime()) return false; } if (a !== Object(a) || b !== Object(b)) { // If the values aren't objects, they were already checked for equality return false; } const props = Object.keys(a); if (props.length !== Object.keys(b).length) { // Different number of props, don't bother to check return false; } return props.every(p => deepEqual(a[p], b[p])); } export function getObjectValueByPath(obj, path, fallback) { // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621 if (obj == null || !path || typeof path !== 'string') return fallback; if (obj[path] !== undefined) return obj[path]; path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties path = path.replace(/^\./, ''); // strip a leading dot return getNestedValue(obj, path.split('.'), fallback); } export function getPropertyFromItem(item, property, fallback) { if (property == null) return item === undefined ? fallback : item; if (item !== Object(item)) return fallback === undefined ? item : fallback; if (typeof property === 'string') return getObjectValueByPath(item, property, fallback); if (Array.isArray(property)) return getNestedValue(item, property, fallback); if (typeof property !== 'function') return fallback; const value = property(item, fallback); return typeof value === 'undefined' ? fallback : value; } export function createRange(length) { return Array.from({ length }, (v, k) => k); } export function getZIndex(el) { if (!el || el.nodeType !== Node.ELEMENT_NODE) return 0; const index = +window.getComputedStyle(el).getPropertyValue('z-index'); if (!index) return getZIndex(el.parentNode); return index; } const tagsToReplace = { '&': '&', '<': '<', '>': '>' }; export function escapeHTML(str) { return str.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag); } export function filterObjectOnKeys(obj, keys) { const filtered = {}; for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (typeof obj[key] !== 'undefined') { filtered[key] = obj[key]; } } return filtered; } export function convertToUnit(str, unit = 'px') { if (str == null || str === '') { return undefined; } else if (isNaN(+str)) { return String(str); } else { return `${Number(str)}${unit}`; } } export function kebabCase(str) { return (str || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } export function isObject(obj) { return obj !== null && typeof obj === 'object'; } // KeyboardEvent.keyCode aliases export const keyCodes = Object.freeze({ enter: 13, tab: 9, delete: 46, esc: 27, space: 32, up: 38, down: 40, left: 37, right: 39, end: 35, home: 36, del: 46, backspace: 8, insert: 45, pageup: 33, pagedown: 34 }); // This remaps internal names like '$cancel' or '$vuetify.icons.cancel' // to the current name or component for that icon. export function remapInternalIcon(vm, iconName) { if (!iconName.startsWith('$')) { return iconName; } // Get the target icon name const iconPath = `$vuetify.icons.values.${iconName.split('$').pop().split('.').pop()}`; // Now look up icon indirection name, // e.g. '$vuetify.icons.values.cancel' return getObjectValueByPath(vm, iconPath, iconName); } export function keys(o) { return Object.keys(o); } /** * Camelize a hyphen-delimited string. */ const camelizeRE = /-(\w)/g; export const camelize = str => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ''); }; /** * Returns the set difference of B and A, i.e. the set of elements in B but not in A */ export function arrayDiff(a, b) { const diff = []; for (let i = 0; i < b.length; i++) { if (a.indexOf(b[i]) < 0) diff.push(b[i]); } return diff; } /** * Makes the first character of a string uppercase */ export function upperFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } export function groupItems(items, groupBy, groupDesc) { const key = groupBy[0]; const groups = []; let current = null; for (var i = 0; i < items.length; i++) { const item = items[i]; const val = getObjectValueByPath(item, key); if (current !== val) { current = val; groups.push({ name: val, items: [] }); } groups[groups.length - 1].items.push(item); } return groups; } export function wrapInArray(v) { return v != null ? Array.isArray(v) ? v : [v] : []; } export function sortItems(items, sortBy, sortDesc, locale, customSorters) { if (sortBy === null || !sortBy.length) return items; const stringCollator = new Intl.Collator(locale, { sensitivity: 'accent', usage: 'sort' }); return items.sort((a, b) => { for (let i = 0; i < sortBy.length; i++) { const sortKey = sortBy[i]; let sortA = getObjectValueByPath(a, sortKey); let sortB = getObjectValueByPath(b, sortKey); if (sortDesc[i]) { [sortA, sortB] = [sortB, sortA]; } if (customSorters && customSorters[sortKey]) { const customResult = customSorters[sortKey](sortA, sortB); if (!customResult) continue; return customResult; } // Check if both cannot be evaluated if (sortA === null && sortB === null) { continue; } [sortA, sortB] = [sortA, sortB].map(s => (s || '').toString().toLocaleLowerCase()); if (sortA !== sortB) { if (!isNaN(sortA) && !isNaN(sortB)) return Number(sortA) - Number(sortB); return stringCollator.compare(sortA, sortB); } } return 0; }); } export function defaultFilter(value, search, item) { return value != null && search != null && typeof value !== 'boolean' && value.toString().toLocaleLowerCase().indexOf(search.toLocaleLowerCase()) !== -1; } export function searchItems(items, search) { if (!search) return items; search = search.toString().toLowerCase(); if (search.trim() === '') return items; return items.filter(item => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search, item))); } /** * Returns: * - 'normal' for old style slots - `