397 lines
11 KiB
JavaScript
397 lines
11 KiB
JavaScript
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 - `<template slot="default">`
|
|
* - 'scoped' for old style scoped slots (`<template slot="default" slot-scope="data">`) or bound v-slot (`#default="data"`)
|
|
* - 'v-slot' for unbound v-slot (`#default`) - only if the third param is true, otherwise counts as scoped
|
|
*/
|
|
|
|
export function getSlotType(vm, name, split) {
|
|
if (vm.$slots[name] && vm.$scopedSlots[name] && vm.$scopedSlots[name].name) {
|
|
return split ? 'v-slot' : 'scoped';
|
|
}
|
|
|
|
if (vm.$slots[name]) return 'normal';
|
|
if (vm.$scopedSlots[name]) return 'scoped';
|
|
}
|
|
export function debounce(fn, delay) {
|
|
let timeoutId = 0;
|
|
return (...args) => {
|
|
clearTimeout(timeoutId);
|
|
timeoutId = setTimeout(() => fn(...args), delay);
|
|
};
|
|
}
|
|
export function throttle(fn, limit) {
|
|
let throttling = false;
|
|
return (...args) => {
|
|
if (!throttling) {
|
|
throttling = true;
|
|
setTimeout(() => throttling = false, limit);
|
|
return fn(...args);
|
|
}
|
|
};
|
|
}
|
|
export function getPrefixedScopedSlots(prefix, scopedSlots) {
|
|
return Object.keys(scopedSlots).filter(k => k.startsWith(prefix)).reduce((obj, k) => {
|
|
obj[k.replace(prefix, '')] = scopedSlots[k];
|
|
return obj;
|
|
}, {});
|
|
}
|
|
export function getSlot(vm, name = 'default', data, optional = false) {
|
|
if (vm.$scopedSlots[name]) {
|
|
return vm.$scopedSlots[name](data instanceof Function ? data() : data);
|
|
} else if (vm.$slots[name] && (!data || optional)) {
|
|
return vm.$slots[name];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
export function clamp(value, min = 0, max = 1) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
export function padEnd(str, length, char = '0') {
|
|
return str + char.repeat(Math.max(0, length - str.length));
|
|
}
|
|
export function chunk(str, size = 1) {
|
|
const chunked = [];
|
|
let index = 0;
|
|
|
|
while (index < str.length) {
|
|
chunked.push(str.substr(index, size));
|
|
index += size;
|
|
}
|
|
|
|
return chunked;
|
|
}
|
|
export function humanReadableFileSize(bytes, binary = false) {
|
|
const base = binary ? 1024 : 1000;
|
|
|
|
if (bytes < base) {
|
|
return `${bytes} B`;
|
|
}
|
|
|
|
const prefix = binary ? ['Ki', 'Mi', 'Gi'] : ['k', 'M', 'G'];
|
|
let unit = -1;
|
|
|
|
while (Math.abs(bytes) >= base && unit < prefix.length - 1) {
|
|
bytes /= base;
|
|
++unit;
|
|
}
|
|
|
|
return `${bytes.toFixed(1)} ${prefix[unit]}B`;
|
|
}
|
|
export function camelizeObjectKeys(obj) {
|
|
if (!obj) return {};
|
|
return Object.keys(obj).reduce((o, key) => {
|
|
o[camelize(key)] = obj[key];
|
|
return o;
|
|
}, {});
|
|
}
|
|
export function mergeDeep(source = {}, target = {}) {
|
|
for (const key in target) {
|
|
const sourceProperty = source[key];
|
|
const targetProperty = target[key]; // Only continue deep merging if
|
|
// both properties are objects
|
|
|
|
if (isObject(sourceProperty) && isObject(targetProperty)) {
|
|
source[key] = mergeDeep(sourceProperty, targetProperty);
|
|
continue;
|
|
}
|
|
|
|
source[key] = targetProperty;
|
|
}
|
|
|
|
return source;
|
|
}
|
|
export function fillArray(length, obj) {
|
|
return Array(length).fill(obj);
|
|
}
|
|
//# sourceMappingURL=helpers.js.map
|