vuetify
Version:
Vue Material Component Framework
1,570 lines (1,470 loc) • 1.04 MB
JavaScript
/*!
* Vuetify v3.8.4
* Forged by John Leider
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Vuetify = {}, global.Vue));
})(this, (function (exports, vue) { 'use strict';
// Types
// eslint-disable-line vue/prefer-import-from-vue
/**
* Creates a factory function for props definitions.
* This is used to define props in a composable then override
* default values in an implementing component.
*
* @example Simplified signature
* (props: Props) => (defaults?: Record<keyof props, any>) => Props
*
* @example Usage
* const makeProps = propsFactory({
* foo: String,
* })
*
* defineComponent({
* props: {
* ...makeProps({
* foo: 'a',
* }),
* },
* setup (props) {
* // would be "string | undefined", now "string" because a default has been provided
* props.foo
* },
* }
*/
function propsFactory(props, source) {
return defaults => {
return Object.keys(props).reduce((obj, prop) => {
const isObjectDefinition = typeof props[prop] === 'object' && props[prop] != null && !Array.isArray(props[prop]);
const definition = isObjectDefinition ? props[prop] : {
type: props[prop]
};
if (defaults && prop in defaults) {
obj[prop] = {
...definition,
default: defaults[prop]
};
} else {
obj[prop] = definition;
}
if (source && !obj[prop].source) {
obj[prop].source = source;
}
return obj;
}, {});
};
}
/**
* Like `Partial<T>` but doesn't care what the value is
*/
// Copied from Vue
// Utilities
// Types
// Composables
const makeComponentProps = propsFactory({
class: [String, Array, Object],
style: {
type: [String, Array, Object],
default: null
}
}, 'component');
const IN_BROWSER = typeof window !== 'undefined';
const SUPPORTS_INTERSECTION = IN_BROWSER && 'IntersectionObserver' in window;
const SUPPORTS_TOUCH = IN_BROWSER && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0);
const SUPPORTS_EYE_DROPPER = IN_BROWSER && 'EyeDropper' in window;
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
// Types
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]];
}
function deepEqual(a, b) {
if (a === b) return true;
if (a instanceof Date && b instanceof Date && a.getTime() !== b.getTime()) {
// If the values are Date, compare them as timestamps
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]));
}
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);
}
function getPropertyFromItem(item, property, fallback) {
if (property === true) return item === undefined ? fallback : item;
if (property == null || typeof property === 'boolean') return fallback;
if (item !== Object(item)) {
if (typeof property !== 'function') return fallback;
const value = property(item, fallback);
return typeof value === 'undefined' ? fallback : value;
}
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;
}
function createRange(length) {
let start = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
return Array.from({
length
}, (v, k) => start + k);
}
function convertToUnit(str) {
let unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'px';
if (str == null || str === '') {
return undefined;
}
const num = Number(str);
if (isNaN(num)) {
return String(str);
} else if (!isFinite(num)) {
return undefined;
} else {
return `${num}${unit}`;
}
}
function isObject(obj) {
return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
}
function isPlainObject(obj) {
let proto;
return obj !== null && typeof obj === 'object' && ((proto = Object.getPrototypeOf(obj)) === Object.prototype || proto === null);
}
function refElement(obj) {
if (obj && '$el' in obj) {
const el = obj.$el;
if (el?.nodeType === Node.TEXT_NODE) {
// Multi-root component, use the first element
return el.nextElementSibling;
}
return el;
}
return obj;
}
// KeyboardEvent.keyCode aliases
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,
shift: 16
});
const keyValues = Object.freeze({
enter: 'Enter',
tab: 'Tab',
delete: 'Delete',
esc: 'Escape',
space: 'Space',
up: 'ArrowUp',
down: 'ArrowDown',
left: 'ArrowLeft',
right: 'ArrowRight',
end: 'End',
home: 'Home',
del: 'Delete',
backspace: 'Backspace',
insert: 'Insert',
pageup: 'PageUp',
pagedown: 'PageDown',
shift: 'Shift'
});
function keys(o) {
return Object.keys(o);
}
function has(obj, key) {
return key.every(k => obj.hasOwnProperty(k));
}
// Array of keys
function pick(obj, paths) {
const found = {};
for (const key of paths) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
found[key] = obj[key];
}
}
return found;
}
// Array of keys
// Array of keys or RegExp to test keys against
function pickWithRest(obj, paths, exclude) {
const found = Object.create(null);
const rest = Object.create(null);
for (const key in obj) {
if (paths.some(path => path instanceof RegExp ? path.test(key) : path === key) && true) {
found[key] = obj[key];
} else {
rest[key] = obj[key];
}
}
return [found, rest];
}
function omit(obj, exclude) {
const clone = {
...obj
};
exclude.forEach(prop => delete clone[prop]);
return clone;
}
const onRE = /^on[^a-z]/;
const isOn = key => onRE.test(key);
const bubblingEvents = ['onAfterscriptexecute', 'onAnimationcancel', 'onAnimationend', 'onAnimationiteration', 'onAnimationstart', 'onAuxclick', 'onBeforeinput', 'onBeforescriptexecute', 'onChange', 'onClick', 'onCompositionend', 'onCompositionstart', 'onCompositionupdate', 'onContextmenu', 'onCopy', 'onCut', 'onDblclick', 'onFocusin', 'onFocusout', 'onFullscreenchange', 'onFullscreenerror', 'onGesturechange', 'onGestureend', 'onGesturestart', 'onGotpointercapture', 'onInput', 'onKeydown', 'onKeypress', 'onKeyup', 'onLostpointercapture', 'onMousedown', 'onMousemove', 'onMouseout', 'onMouseover', 'onMouseup', 'onMousewheel', 'onPaste', 'onPointercancel', 'onPointerdown', 'onPointerenter', 'onPointerleave', 'onPointermove', 'onPointerout', 'onPointerover', 'onPointerup', 'onReset', 'onSelect', 'onSubmit', 'onTouchcancel', 'onTouchend', 'onTouchmove', 'onTouchstart', 'onTransitioncancel', 'onTransitionend', 'onTransitionrun', 'onTransitionstart', 'onWheel'];
const compositionIgnoreKeys = ['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft', 'Enter', 'Escape', 'Tab', ' '];
function isComposingIgnoreKey(e) {
return e.isComposing && compositionIgnoreKeys.includes(e.key);
}
/**
* Filter attributes that should be applied to
* the root element of an input component. Remaining
* attributes should be passed to the <input> element inside.
*/
function filterInputAttrs(attrs) {
const [events, props] = pickWithRest(attrs, [onRE]);
const inputEvents = omit(events, bubblingEvents);
const [rootAttrs, inputAttrs] = pickWithRest(props, ['class', 'style', 'id', /^data-/]);
Object.assign(rootAttrs, events);
Object.assign(inputAttrs, inputEvents);
return [rootAttrs, inputAttrs];
}
function wrapInArray(v) {
return v == null ? [] : Array.isArray(v) ? v : [v];
}
function debounce(fn, delay) {
let timeoutId = 0;
const wrap = function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), vue.unref(delay));
};
wrap.clear = () => {
clearTimeout(timeoutId);
};
wrap.immediate = fn;
return wrap;
}
function clamp(value) {
let min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
let max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
return Math.max(min, Math.min(max, value));
}
function getDecimals(value) {
const trimmedStr = value.toString().trim();
return trimmedStr.includes('.') ? trimmedStr.length - trimmedStr.indexOf('.') - 1 : 0;
}
function padEnd(str, length) {
let char = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '0';
return str + char.repeat(Math.max(0, length - str.length));
}
function padStart(str, length) {
let char = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '0';
return char.repeat(Math.max(0, length - str.length)) + str;
}
function chunk(str) {
let size = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
const chunked = [];
let index = 0;
while (index < str.length) {
chunked.push(str.substr(index, size));
index += size;
}
return chunked;
}
function chunkArray(array) {
let size = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return Array.from({
length: Math.ceil(array.length / size)
}, (v, i) => array.slice(i * size, i * size + size));
}
function humanReadableFileSize(bytes) {
let base = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1000;
if (bytes < base) {
return `${bytes} B`;
}
const prefix = base === 1024 ? ['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`;
}
function mergeDeep() {
let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let arrayFn = arguments.length > 2 ? arguments[2] : undefined;
const out = {};
for (const key in source) {
out[key] = source[key];
}
for (const key in target) {
const sourceProperty = source[key];
const targetProperty = target[key];
// Only continue deep merging if
// both properties are plain objects
if (isPlainObject(sourceProperty) && isPlainObject(targetProperty)) {
out[key] = mergeDeep(sourceProperty, targetProperty, arrayFn);
continue;
}
if (arrayFn && Array.isArray(sourceProperty) && Array.isArray(targetProperty)) {
out[key] = arrayFn(sourceProperty, targetProperty);
continue;
}
out[key] = targetProperty;
}
return out;
}
function flattenFragments(nodes) {
return nodes.map(node => {
if (node.type === vue.Fragment) {
return flattenFragments(node.children);
} else {
return node;
}
}).flat();
}
function toKebabCase() {
let str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
if (toKebabCase.cache.has(str)) return toKebabCase.cache.get(str);
const kebab = str.replace(/[^a-z]/gi, '-').replace(/\B([A-Z])/g, '-$1').toLowerCase();
toKebabCase.cache.set(str, kebab);
return kebab;
}
toKebabCase.cache = new Map();
function findChildrenWithProvide(key, vnode) {
if (!vnode || typeof vnode !== 'object') return [];
if (Array.isArray(vnode)) {
return vnode.map(child => findChildrenWithProvide(key, child)).flat(1);
} else if (vnode.suspense) {
return findChildrenWithProvide(key, vnode.ssContent);
} else if (Array.isArray(vnode.children)) {
return vnode.children.map(child => findChildrenWithProvide(key, child)).flat(1);
} else if (vnode.component) {
if (Object.getOwnPropertySymbols(vnode.component.provides).includes(key)) {
return [vnode.component];
} else if (vnode.component.subTree) {
return findChildrenWithProvide(key, vnode.component.subTree).flat(1);
}
}
return [];
}
var _arr = /*#__PURE__*/new WeakMap();
var _pointer = /*#__PURE__*/new WeakMap();
class CircularBuffer {
constructor(size) {
_classPrivateFieldInitSpec(this, _arr, []);
_classPrivateFieldInitSpec(this, _pointer, 0);
this.size = size;
}
get isFull() {
return _classPrivateFieldGet(_arr, this).length === this.size;
}
push(val) {
_classPrivateFieldGet(_arr, this)[_classPrivateFieldGet(_pointer, this)] = val;
_classPrivateFieldSet(_pointer, this, (_classPrivateFieldGet(_pointer, this) + 1) % this.size);
}
values() {
return _classPrivateFieldGet(_arr, this).slice(_classPrivateFieldGet(_pointer, this)).concat(_classPrivateFieldGet(_arr, this).slice(0, _classPrivateFieldGet(_pointer, this)));
}
clear() {
_classPrivateFieldGet(_arr, this).length = 0;
_classPrivateFieldSet(_pointer, this, 0);
}
}
function getEventCoordinates(e) {
if ('touches' in e) {
return {
clientX: e.touches[0].clientX,
clientY: e.touches[0].clientY
};
}
return {
clientX: e.clientX,
clientY: e.clientY
};
}
// Only allow a single return type
/**
* Convert a computed ref to a record of refs.
* The getter function must always return an object with the same keys.
*/
function destructComputed(getter) {
const refs = vue.reactive({});
vue.watchEffect(() => {
const base = getter();
for (const key in base) {
refs[key] = base[key];
}
}, {
flush: 'sync'
});
const obj = {};
for (const key in refs) {
obj[key] = vue.toRef(() => refs[key]);
}
return obj;
}
/** Array.includes but value can be any type */
function includes(arr, val) {
return arr.includes(val);
}
function eventName(propName) {
return propName[2].toLowerCase() + propName.slice(3);
}
// TODO: this should be an array but vue's types don't accept arrays: vuejs/core#8025
const EventProp = () => [Function, Array];
function hasEvent(props, name) {
name = 'on' + vue.capitalize(name);
return !!(props[name] || props[`${name}Once`] || props[`${name}Capture`] || props[`${name}OnceCapture`] || props[`${name}CaptureOnce`]);
}
function callEvent(handler) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
if (Array.isArray(handler)) {
for (const h of handler) {
h(...args);
}
} else if (typeof handler === 'function') {
handler(...args);
}
}
function focusableChildren(el) {
let filterByTabIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
const targets = ['button', '[href]', 'input:not([type="hidden"])', 'select', 'textarea', '[tabindex]'].map(s => `${s}${filterByTabIndex ? ':not([tabindex="-1"])' : ''}:not([disabled])`).join(', ');
return [...el.querySelectorAll(targets)];
}
function getNextElement(elements, location, condition) {
let _el;
let idx = elements.indexOf(document.activeElement);
const inc = location === 'next' ? 1 : -1;
do {
idx += inc;
_el = elements[idx];
} while ((!_el || _el.offsetParent == null || !(condition?.(_el) ?? true)) && idx < elements.length && idx >= 0);
return _el;
}
function focusChild(el, location) {
const focusable = focusableChildren(el);
if (!location) {
if (el === document.activeElement || !el.contains(document.activeElement)) {
focusable[0]?.focus();
}
} else if (location === 'first') {
focusable[0]?.focus();
} else if (location === 'last') {
focusable.at(-1)?.focus();
} else if (typeof location === 'number') {
focusable[location]?.focus();
} else {
const _el = getNextElement(focusable, location);
if (_el) _el.focus();else focusChild(el, location === 'next' ? 'first' : 'last');
}
}
function isEmpty(val) {
return val === null || val === undefined || typeof val === 'string' && val.trim() === '';
}
function noop() {}
/** Returns null if the selector is not supported or we can't check */
function matchesSelector(el, selector) {
const supportsSelector = IN_BROWSER && typeof CSS !== 'undefined' && typeof CSS.supports !== 'undefined' && CSS.supports(`selector(${selector})`);
if (!supportsSelector) return null;
try {
return !!el && el.matches(selector);
} catch (err) {
return null;
}
}
function ensureValidVNode(vnodes) {
return vnodes.some(child => {
if (!vue.isVNode(child)) return true;
if (child.type === vue.Comment) return false;
return child.type !== vue.Fragment || ensureValidVNode(child.children);
}) ? vnodes : null;
}
function defer(timeout, cb) {
if (!IN_BROWSER || timeout === 0) {
cb();
return () => {};
}
const timeoutId = window.setTimeout(cb, timeout);
return () => window.clearTimeout(timeoutId);
}
function isClickInsideElement(event, targetDiv) {
const mouseX = event.clientX;
const mouseY = event.clientY;
const divRect = targetDiv.getBoundingClientRect();
const divLeft = divRect.left;
const divTop = divRect.top;
const divRight = divRect.right;
const divBottom = divRect.bottom;
return mouseX >= divLeft && mouseX <= divRight && mouseY >= divTop && mouseY <= divBottom;
}
function templateRef() {
const el = vue.shallowRef();
const fn = target => {
el.value = target;
};
Object.defineProperty(fn, 'value', {
enumerable: true,
get: () => el.value,
set: val => el.value = val
});
Object.defineProperty(fn, 'el', {
enumerable: true,
get: () => refElement(el.value)
});
return fn;
}
function checkPrintable(e) {
const isPrintableChar = e.key.length === 1;
const noModifier = !e.ctrlKey && !e.metaKey && !e.altKey;
return isPrintableChar && noModifier;
}
function isPrimitive(value) {
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint';
}
// Utilities
const block = ['top', 'bottom'];
const inline = ['start', 'end', 'left', 'right'];
/** Parse a raw anchor string into an object */
function parseAnchor(anchor, isRtl) {
let [side, align] = anchor.split(' ');
if (!align) {
align = includes(block, side) ? 'start' : includes(inline, side) ? 'top' : 'center';
}
return {
side: toPhysical(side, isRtl),
align: toPhysical(align, isRtl)
};
}
function toPhysical(str, isRtl) {
if (str === 'start') return isRtl ? 'right' : 'left';
if (str === 'end') return isRtl ? 'left' : 'right';
return str;
}
function flipSide(anchor) {
return {
side: {
center: 'center',
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
}[anchor.side],
align: anchor.align
};
}
function flipAlign(anchor) {
return {
side: anchor.side,
align: {
center: 'center',
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
}[anchor.align]
};
}
function flipCorner(anchor) {
return {
side: anchor.align,
align: anchor.side
};
}
function getAxis(anchor) {
return includes(block, anchor.side) ? 'y' : 'x';
}
class Box {
constructor(_ref) {
let {
x,
y,
width,
height
} = _ref;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
get top() {
return this.y;
}
get bottom() {
return this.y + this.height;
}
get left() {
return this.x;
}
get right() {
return this.x + this.width;
}
}
function getOverflow(a, b) {
return {
x: {
before: Math.max(0, b.left - a.left),
after: Math.max(0, a.right - b.right)
},
y: {
before: Math.max(0, b.top - a.top),
after: Math.max(0, a.bottom - b.bottom)
}
};
}
function getTargetBox(target) {
if (Array.isArray(target)) {
return new Box({
x: target[0],
y: target[1],
width: 0,
height: 0
});
} else {
return target.getBoundingClientRect();
}
}
// Utilities
/** @see https://stackoverflow.com/a/57876601/2074736 */
function nullifyTransforms(el) {
const rect = el.getBoundingClientRect();
const style = getComputedStyle(el);
const tx = style.transform;
if (tx) {
let ta, sx, sy, dx, dy;
if (tx.startsWith('matrix3d(')) {
ta = tx.slice(9, -1).split(/, /);
sx = Number(ta[0]);
sy = Number(ta[5]);
dx = Number(ta[12]);
dy = Number(ta[13]);
} else if (tx.startsWith('matrix(')) {
ta = tx.slice(7, -1).split(/, /);
sx = Number(ta[0]);
sy = Number(ta[3]);
dx = Number(ta[4]);
dy = Number(ta[5]);
} else {
return new Box(rect);
}
const to = style.transformOrigin;
const x = rect.x - dx - (1 - sx) * parseFloat(to);
const y = rect.y - dy - (1 - sy) * parseFloat(to.slice(to.indexOf(' ') + 1));
const w = sx ? rect.width / sx : el.offsetWidth + 1;
const h = sy ? rect.height / sy : el.offsetHeight + 1;
return new Box({
x,
y,
width: w,
height: h
});
} else {
return new Box(rect);
}
}
function animate(el, keyframes, options) {
if (typeof el.animate === 'undefined') return {
finished: Promise.resolve()
};
let animation;
try {
animation = el.animate(keyframes, options);
} catch (err) {
return {
finished: Promise.resolve()
};
}
if (typeof animation.finished === 'undefined') {
animation.finished = new Promise(resolve => {
animation.onfinish = () => {
resolve(animation);
};
});
}
return animation;
}
// Utilities
const handlers = new WeakMap();
function bindProps(el, props) {
Object.keys(props).forEach(k => {
if (isOn(k)) {
const name = eventName(k);
const handler = handlers.get(el);
if (props[k] == null) {
handler?.forEach(v => {
const [n, fn] = v;
if (n === name) {
el.removeEventListener(name, fn);
handler.delete(v);
}
});
} else if (!handler || ![...handler]?.some(v => v[0] === name && v[1] === props[k])) {
el.addEventListener(name, props[k]);
const _handler = handler || new Set();
_handler.add([name, props[k]]);
if (!handlers.has(el)) handlers.set(el, _handler);
}
} else {
if (props[k] == null) {
el.removeAttribute(k);
} else {
el.setAttribute(k, props[k]);
}
}
});
}
function unbindProps(el, props) {
Object.keys(props).forEach(k => {
if (isOn(k)) {
const name = eventName(k);
const handler = handlers.get(el);
handler?.forEach(v => {
const [n, fn] = v;
if (n === name) {
el.removeEventListener(name, fn);
handler.delete(v);
}
});
} else {
el.removeAttribute(k);
}
});
}
/**
* WCAG 3.0 APCA perceptual contrast algorithm from https://github.com/Myndex/SAPC-APCA
* @licence https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
* @see https://www.w3.org/WAI/GL/task-forces/silver/wiki/Visual_Contrast_of_Text_Subgroup
*/
// Types
// MAGICAL NUMBERS
// sRGB Conversion to Relative Luminance (Y)
// Transfer Curve (aka "Gamma") for sRGB linearization
// Simple power curve vs piecewise described in docs
// Essentially, 2.4 best models actual display
// characteristics in combination with the total method
const mainTRC = 2.4;
const Rco = 0.2126729; // sRGB Red Coefficient (from matrix)
const Gco = 0.7151522; // sRGB Green Coefficient (from matrix)
const Bco = 0.0721750; // sRGB Blue Coefficient (from matrix)
// For Finding Raw SAPC Contrast from Relative Luminance (Y)
// Constants for SAPC Power Curve Exponents
// One pair for normal text, and one for reverse
// These are the "beating heart" of SAPC
const normBG = 0.55;
const normTXT = 0.58;
const revTXT = 0.57;
const revBG = 0.62;
// For Clamping and Scaling Values
const blkThrs = 0.03; // Level that triggers the soft black clamp
const blkClmp = 1.45; // Exponent for the soft black clamp curve
const deltaYmin = 0.0005; // Lint trap
const scaleBoW = 1.25; // Scaling for dark text on light
const scaleWoB = 1.25; // Scaling for light text on dark
const loConThresh = 0.078; // Threshold for new simple offset scale
const loConFactor = 12.82051282051282; // = 1/0.078,
const loConOffset = 0.06; // The simple offset
const loClip = 0.001; // Output clip (lint trap #2)
function APCAcontrast(text, background) {
// Linearize sRGB
const Rtxt = (text.r / 255) ** mainTRC;
const Gtxt = (text.g / 255) ** mainTRC;
const Btxt = (text.b / 255) ** mainTRC;
const Rbg = (background.r / 255) ** mainTRC;
const Gbg = (background.g / 255) ** mainTRC;
const Bbg = (background.b / 255) ** mainTRC;
// Apply the standard coefficients and sum to Y
let Ytxt = Rtxt * Rco + Gtxt * Gco + Btxt * Bco;
let Ybg = Rbg * Rco + Gbg * Gco + Bbg * Bco;
// Soft clamp Y when near black.
// Now clamping all colors to prevent crossover errors
if (Ytxt <= blkThrs) Ytxt += (blkThrs - Ytxt) ** blkClmp;
if (Ybg <= blkThrs) Ybg += (blkThrs - Ybg) ** blkClmp;
// Return 0 Early for extremely low ∆Y (lint trap #1)
if (Math.abs(Ybg - Ytxt) < deltaYmin) return 0.0;
// SAPC CONTRAST
let outputContrast; // For weighted final values
if (Ybg > Ytxt) {
// For normal polarity, black text on white
// Calculate the SAPC contrast value and scale
const SAPC = (Ybg ** normBG - Ytxt ** normTXT) * scaleBoW;
// NEW! SAPC SmoothScale™
// Low Contrast Smooth Scale Rollout to prevent polarity reversal
// and also a low clip for very low contrasts (lint trap #2)
// much of this is for very low contrasts, less than 10
// therefore for most reversing needs, only loConOffset is important
outputContrast = SAPC < loClip ? 0.0 : SAPC < loConThresh ? SAPC - SAPC * loConFactor * loConOffset : SAPC - loConOffset;
} else {
// For reverse polarity, light text on dark
// WoB should always return negative value.
const SAPC = (Ybg ** revBG - Ytxt ** revTXT) * scaleWoB;
outputContrast = SAPC > -1e-3 ? 0.0 : SAPC > -0.078 ? SAPC - SAPC * loConFactor * loConOffset : SAPC + loConOffset;
}
return outputContrast * 100;
}
/* eslint-disable no-console */
function consoleWarn(message) {
vue.warn(`Vuetify: ${message}`);
}
function consoleError(message) {
vue.warn(`Vuetify error: ${message}`);
}
function deprecate(original, replacement) {
replacement = Array.isArray(replacement) ? replacement.slice(0, -1).map(s => `'${s}'`).join(', ') + ` or '${replacement.at(-1)}'` : `'${replacement}'`;
vue.warn(`[Vuetify UPGRADE] '${original}' is deprecated, use ${replacement} instead.`);
}
// Types
const delta = 0.20689655172413793; // 6÷29
const cielabForwardTransform = t => t > delta ** 3 ? Math.cbrt(t) : t / (3 * delta ** 2) + 4 / 29;
const cielabReverseTransform = t => t > delta ? t ** 3 : 3 * delta ** 2 * (t - 4 / 29);
function fromXYZ$1(xyz) {
const transform = cielabForwardTransform;
const transformedY = transform(xyz[1]);
return [116 * transformedY - 16, 500 * (transform(xyz[0] / 0.95047) - transformedY), 200 * (transformedY - transform(xyz[2] / 1.08883))];
}
function toXYZ$1(lab) {
const transform = cielabReverseTransform;
const Ln = (lab[0] + 16) / 116;
return [transform(Ln + lab[1] / 500) * 0.95047, transform(Ln), transform(Ln - lab[2] / 200) * 1.08883];
}
// Utilities
// Types
// For converting XYZ to sRGB
const srgbForwardMatrix = [[3.2406, -1.5372, -0.4986], [-0.9689, 1.8758, 0.0415], [0.0557, -0.204, 1.0570]];
// Forward gamma adjust
const srgbForwardTransform = C => C <= 0.0031308 ? C * 12.92 : 1.055 * C ** (1 / 2.4) - 0.055;
// For converting sRGB to XYZ
const srgbReverseMatrix = [[0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505]];
// Reverse gamma adjust
const srgbReverseTransform = C => C <= 0.04045 ? C / 12.92 : ((C + 0.055) / 1.055) ** 2.4;
function fromXYZ(xyz) {
const rgb = Array(3);
const transform = srgbForwardTransform;
const matrix = srgbForwardMatrix;
// Matrix transform, then gamma adjustment
for (let i = 0; i < 3; ++i) {
// Rescale back to [0, 255]
rgb[i] = Math.round(clamp(transform(matrix[i][0] * xyz[0] + matrix[i][1] * xyz[1] + matrix[i][2] * xyz[2])) * 255);
}
return {
r: rgb[0],
g: rgb[1],
b: rgb[2]
};
}
function toXYZ(_ref) {
let {
r,
g,
b
} = _ref;
const xyz = [0, 0, 0];
const transform = srgbReverseTransform;
const matrix = srgbReverseMatrix;
// Rescale from [0, 255] to [0, 1] then adjust sRGB gamma to linear RGB
r = transform(r / 255);
g = transform(g / 255);
b = transform(b / 255);
// Matrix color space transform
for (let i = 0; i < 3; ++i) {
xyz[i] = matrix[i][0] * r + matrix[i][1] * g + matrix[i][2] * b;
}
return xyz;
}
// Utilities
// Types
function isCssColor(color) {
return !!color && /^(#|var\(--|(rgb|hsl)a?\()/.test(color);
}
function isParsableColor(color) {
return isCssColor(color) && !/^((rgb|hsl)a?\()?var\(--/.test(color);
}
const cssColorRe = /^(?<fn>(?:rgb|hsl)a?)\((?<values>.+)\)/;
const mappers = {
rgb: (r, g, b, a) => ({
r,
g,
b,
a
}),
rgba: (r, g, b, a) => ({
r,
g,
b,
a
}),
hsl: (h, s, l, a) => HSLtoRGB({
h,
s,
l,
a
}),
hsla: (h, s, l, a) => HSLtoRGB({
h,
s,
l,
a
}),
hsv: (h, s, v, a) => HSVtoRGB({
h,
s,
v,
a
}),
hsva: (h, s, v, a) => HSVtoRGB({
h,
s,
v,
a
})
};
function parseColor(color) {
if (typeof color === 'number') {
if (isNaN(color) || color < 0 || color > 0xFFFFFF) {
// int can't have opacity
consoleWarn(`'${color}' is not a valid hex color`);
}
return {
r: (color & 0xFF0000) >> 16,
g: (color & 0xFF00) >> 8,
b: color & 0xFF
};
} else if (typeof color === 'string' && cssColorRe.test(color)) {
const {
groups
} = color.match(cssColorRe);
const {
fn,
values
} = groups;
const realValues = values.split(/,\s*|\s*\/\s*|\s+/).map((v, i) => {
if (v.endsWith('%') ||
// unitless slv are %
i > 0 && i < 3 && ['hsl', 'hsla', 'hsv', 'hsva'].includes(fn)) {
return parseFloat(v) / 100;
} else {
return parseFloat(v);
}
});
return mappers[fn](...realValues);
} else if (typeof color === 'string') {
let hex = color.startsWith('#') ? color.slice(1) : color;
if ([3, 4].includes(hex.length)) {
hex = hex.split('').map(char => char + char).join('');
} else if (![6, 8].includes(hex.length)) {
consoleWarn(`'${color}' is not a valid hex(a) color`);
}
const int = parseInt(hex, 16);
if (isNaN(int) || int < 0 || int > 0xFFFFFFFF) {
consoleWarn(`'${color}' is not a valid hex(a) color`);
}
return HexToRGB(hex);
} else if (typeof color === 'object') {
if (has(color, ['r', 'g', 'b'])) {
return color;
} else if (has(color, ['h', 's', 'l'])) {
return HSVtoRGB(HSLtoHSV(color));
} else if (has(color, ['h', 's', 'v'])) {
return HSVtoRGB(color);
}
}
throw new TypeError(`Invalid color: ${color == null ? color : String(color) || color.constructor.name}\nExpected #hex, #hexa, rgb(), rgba(), hsl(), hsla(), object or number`);
}
/** Converts HSVA to RGBA. Based on formula from https://en.wikipedia.org/wiki/HSL_and_HSV */
function HSVtoRGB(hsva) {
const {
h,
s,
v,
a
} = hsva;
const f = n => {
const k = (n + h / 60) % 6;
return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
};
const rgb = [f(5), f(3), f(1)].map(v => Math.round(v * 255));
return {
r: rgb[0],
g: rgb[1],
b: rgb[2],
a
};
}
function HSLtoRGB(hsla) {
return HSVtoRGB(HSLtoHSV(hsla));
}
/** Converts RGBA to HSVA. Based on formula from https://en.wikipedia.org/wiki/HSL_and_HSV */
function RGBtoHSV(rgba) {
if (!rgba) return {
h: 0,
s: 1,
v: 1,
a: 1
};
const r = rgba.r / 255;
const g = rgba.g / 255;
const b = rgba.b / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
if (max !== min) {
if (max === r) {
h = 60 * (0 + (g - b) / (max - min));
} else if (max === g) {
h = 60 * (2 + (b - r) / (max - min));
} else if (max === b) {
h = 60 * (4 + (r - g) / (max - min));
}
}
if (h < 0) h = h + 360;
const s = max === 0 ? 0 : (max - min) / max;
const hsv = [h, s, max];
return {
h: hsv[0],
s: hsv[1],
v: hsv[2],
a: rgba.a
};
}
function HSVtoHSL(hsva) {
const {
h,
s,
v,
a
} = hsva;
const l = v - v * s / 2;
const sprime = l === 1 || l === 0 ? 0 : (v - l) / Math.min(l, 1 - l);
return {
h,
s: sprime,
l,
a
};
}
function HSLtoHSV(hsl) {
const {
h,
s,
l,
a
} = hsl;
const v = l + s * Math.min(l, 1 - l);
const sprime = v === 0 ? 0 : 2 - 2 * l / v;
return {
h,
s: sprime,
v,
a
};
}
function RGBtoCSS(_ref) {
let {
r,
g,
b,
a
} = _ref;
return a === undefined ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
}
function HSVtoCSS(hsva) {
return RGBtoCSS(HSVtoRGB(hsva));
}
function toHex(v) {
const h = Math.round(v).toString(16);
return ('00'.substr(0, 2 - h.length) + h).toUpperCase();
}
function RGBtoHex(_ref2) {
let {
r,
g,
b,
a
} = _ref2;
return `#${[toHex(r), toHex(g), toHex(b), a !== undefined ? toHex(Math.round(a * 255)) : ''].join('')}`;
}
function HexToRGB(hex) {
hex = parseHex(hex);
let [r, g, b, a] = chunk(hex, 2).map(c => parseInt(c, 16));
a = a === undefined ? a : a / 255;
return {
r,
g,
b,
a
};
}
function HexToHSV(hex) {
const rgb = HexToRGB(hex);
return RGBtoHSV(rgb);
}
function HSVtoHex(hsva) {
return RGBtoHex(HSVtoRGB(hsva));
}
function parseHex(hex) {
if (hex.startsWith('#')) {
hex = hex.slice(1);
}
hex = hex.replace(/([^0-9a-f])/gi, 'F');
if (hex.length === 3 || hex.length === 4) {
hex = hex.split('').map(x => x + x).join('');
}
if (hex.length !== 6) {
hex = padEnd(padEnd(hex, 6), 8, 'F');
}
return hex;
}
function lighten(value, amount) {
const lab = fromXYZ$1(toXYZ(value));
lab[0] = lab[0] + amount * 10;
return fromXYZ(toXYZ$1(lab));
}
function darken(value, amount) {
const lab = fromXYZ$1(toXYZ(value));
lab[0] = lab[0] - amount * 10;
return fromXYZ(toXYZ$1(lab));
}
/**
* Calculate the relative luminance of a given color
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
*/
function getLuma(color) {
const rgb = parseColor(color);
return toXYZ(rgb)[1];
}
/**
* Returns the contrast ratio (1-21) between two colors.
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
*/
function getContrast(first, second) {
const l1 = getLuma(first);
const l2 = getLuma(second);
const light = Math.max(l1, l2);
const dark = Math.min(l1, l2);
return (light + 0.05) / (dark + 0.05);
}
function getForeground(color) {
const blackContrast = Math.abs(APCAcontrast(parseColor(0), parseColor(color)));
const whiteContrast = Math.abs(APCAcontrast(parseColor(0xffffff), parseColor(color)));
// TODO: warn about poor color selections
// const contrastAsText = Math.abs(APCAcontrast(colorVal, colorToInt(theme.colors.background)))
// const minContrast = Math.max(blackContrast, whiteContrast)
// if (minContrast < 60) {
// consoleInfo(`${key} theme color ${color} has poor contrast (${minContrast.toFixed()}%)`)
// } else if (contrastAsText < 60 && !['background', 'surface'].includes(color)) {
// consoleInfo(`${key} theme color ${color} has poor contrast as text (${contrastAsText.toFixed()}%)`)
// }
// Prefer white text if both have an acceptable contrast ratio
return whiteContrast > Math.min(blackContrast, 50) ? '#fff' : '#000';
}
// Utilities
function getCurrentInstance(name, message) {
const vm = vue.getCurrentInstance();
if (!vm) {
throw new Error(`[Vuetify] ${name} ${'must be called from inside a setup function'}`);
}
return vm;
}
function getCurrentInstanceName() {
let name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'composables';
const vm = getCurrentInstance(name).type;
return toKebabCase(vm?.aliasName || vm?.name);
}
// Utilities
// Types
function injectSelf(key) {
let vm = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : getCurrentInstance('injectSelf');
const {
provides
} = vm;
if (provides && key in provides) {
// TS doesn't allow symbol as index type
return provides[key];
}
return undefined;
}
// Utilities
// Types
const DefaultsSymbol = Symbol.for('vuetify:defaults');
function createDefaults(options) {
return vue.ref(options);
}
function injectDefaults() {
const defaults = vue.inject(DefaultsSymbol);
if (!defaults) throw new Error('[Vuetify] Could not find defaults instance');
return defaults;
}
function provideDefaults(defaults, options) {
const injectedDefaults = injectDefaults();
const providedDefaults = vue.ref(defaults);
const newDefaults = vue.computed(() => {
const disabled = vue.unref(options?.disabled);
if (disabled) return injectedDefaults.value;
const scoped = vue.unref(options?.scoped);
const reset = vue.unref(options?.reset);
const root = vue.unref(options?.root);
if (providedDefaults.value == null && !(scoped || reset || root)) return injectedDefaults.value;
let properties = mergeDeep(providedDefaults.value, {
prev: injectedDefaults.value
});
if (scoped) return properties;
if (reset || root) {
const len = Number(reset || Infinity);
for (let i = 0; i <= len; i++) {
if (!properties || !('prev' in properties)) {
break;
}
properties = properties.prev;
}
if (properties && typeof root === 'string' && root in properties) {
properties = mergeDeep(mergeDeep(properties, {
prev: properties
}), properties[root]);
}
return properties;
}
return properties.prev ? mergeDeep(properties.prev, properties) : properties;
});
vue.provide(DefaultsSymbol, newDefaults);
return newDefaults;
}
function propIsDefined(vnode, prop) {
return vnode.props && (typeof vnode.props[prop] !== 'undefined' || typeof vnode.props[toKebabCase(prop)] !== 'undefined');
}
function internalUseDefaults() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let name = arguments.length > 1 ? arguments[1] : undefined;
let defaults = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : injectDefaults();
const vm = getCurrentInstance('useDefaults');
name = name ?? vm.type.name ?? vm.type.__name;
if (!name) {
throw new Error('[Vuetify] Could not determine component name');
}
const componentDefaults = vue.computed(() => defaults.value?.[props._as ?? name]);
const _props = new Proxy(props, {
get(target, prop) {
const propValue = Reflect.get(target, prop);
if (prop === 'class' || prop === 'style') {
return [componentDefaults.value?.[prop], propValue].filter(v => v != null);
}
if (propIsDefined(vm.vnode, prop)) return propValue;
const _componentDefault = componentDefaults.value?.[prop];
if (_componentDefault !== undefined) return _componentDefault;
const _globalDefault = defaults.value?.global?.[prop];
if (_globalDefault !== undefined) return _globalDefault;
return propValue;
}
});
const _subcomponentDefaults = vue.shallowRef();
vue.watchEffect(() => {
if (componentDefaults.value) {
const subComponents = Object.entries(componentDefaults.value).filter(_ref => {
let [key] = _ref;
return key.startsWith(key[0].toUpperCase());
});
_subcomponentDefaults.value = subComponents.length ? Object.fromEntries(subComponents) : undefined;
} else {
_subcomponentDefaults.value = undefined;
}
});
function provideSubDefaults() {
const injected = injectSelf(DefaultsSymbol, vm);
vue.provide(DefaultsSymbol, vue.computed(() => {
return _subcomponentDefaults.value ? mergeDeep(injected?.value ?? {}, _subcomponentDefaults.value) : injected?.value;
}));
}
return {
props: _props,
provideSubDefaults
};
}
function useDefaults() {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let name = arguments.length > 1 ? arguments[1] : undefined;
const {
props: _props,
provideSubDefaults
} = internalUseDefaults(props, name);
provideSubDefaults();
return _props;
}
// Composables
// Types
// No props
// Object Props
// Implementation
function defineComponent(options) {
options._setup = options._setup ?? options.setup;
if (!options.name) {
consoleWarn('The component is missing an explicit name, unable to generate default prop value');
return options;
}
if (options._setup) {
options.props = propsFactory(options.props ?? {}, options.name)();
const propKeys = Object.keys(options.props).filter(key => key !== 'class' && key !== 'style');
options.filterProps = function filterProps(props) {
return pick(props, propKeys);
};
options.props._as = String;
options.setup = function setup(props, ctx) {
const defaults = injectDefaults();
// Skip props proxy if defaults are not provided
if (!defaults.value) return options._setup(props, ctx);
const {
props: _props,
provideSubDefaults
} = internalUseDefaults(props, props._as ?? options.name, defaults);
const setupBindings = options._setup(_props, ctx);
provideSubDefaults();
return setupBindings;
};
}
return options;
}
// No argument - simple default slot
// Generic constructor argument - generic props and slots
// Slots argument - simple slots
// Implementation
function genericComponent() {
let exposeDefaults = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
return options => (exposeDefaults ? defineComponent : vue.defineComponent)(options);
}
function defineFunctionalComponent(props, render) {
render.props = props;
return render;
}
// Adds a filterProps method to the component options
// https://github.com/vuejs/core/pull/10557
// not a vue Component
// Composables
function createSimpleFunctional(klass) {
let tag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'div';
let name = arguments.length > 2 ? arguments[2] : undefined;
return genericComponent()({
name: name ?? vue.capitalize(vue.camelize(klass.replace(/__/g, '-'))),
props: {
tag: {
type: String,
default: tag
},
...makeComponentProps()
},
setup(props, _ref) {
let {
slots
} = _ref;
return () => {
return vue.h(props.tag, {
class: [klass, props.class],
style: props.style
}, slots.default?.());
};
}
});
}
/**
* Returns:
* - 'null' if the node is not attached to the DOM
* - the root node (HTMLDocument | ShadowRoot) otherwise
*/
function attachedRoot(node) {
/* istanbul ignore next */
if (typeof node.getRootNode !== 'function') {
// Shadow DOM not supported (IE11), lets find the root of this node
while (node.parentNode) node = node.parentNode;
// The root parent is the document if the node is attached to the DOM
if (node !== document) return null;
return document;
}
const root = node.getRootNode();
// The composed root node is the document if the node is attached to the DOM
if (root !== document && root.getRootNode({
composed: true
}) !== document) return null;
return root;
}
const standardEasing = 'cubic-bezier(0.4, 0, 0.2, 1)';
const deceleratedEasing = 'cubic-bezier(0.0, 0, 0.2, 1)'; // Entering
const acceleratedEasing = 'cubic-bezier(0.4, 0, 1, 1)'; // Leaving
// Utilities
function getPrefixedEventHandlers(attrs, suffix, getData) {
return Object.keys(attrs).filter(key => isOn(key) && key.endsWith(suffix)).reduce((acc, key) => {
acc[key.slice(0, -suffix.length)] = event => attrs[key](event, getData(event));
return acc;
}, {});
}
function getScrollParent(el) {
let includeHidden = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
while (el) {
if (includeHidden ? isPotentiallyScrollable(el) : hasScrollbar(el)) return el;
el = el.parentElement;
}
return document.scrollingElement;
}
function getScrollParents(el, stopAt) {
const elements = [];
if (stopAt && el && !stopAt.contains(el)) return elements;
while (el) {
if (hasScrollbar(el)) elements.push(el);
if (el === stopAt) break;
el = el.parentElement;
}
return elements;
}
function hasScrollbar(el) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;
const style = window.getComputedStyle(el);
return style.overflowY === 'scroll' || style.overflowY === 'auto' && el.scrollHeight > el.clientHeight;
}
function isPotentiallyScrollable(el) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;
const style = window.getComputedStyle(el);
return ['scroll', 'auto'].includes(style.overflowY);
}
function isFixedPosition(el) {
while (el) {
if (window.getComputedStyle(el).position === 'fixed') {
return true;
}