40949 lines
1.1 MiB
40949 lines
1.1 MiB
/*!
|
|
* Quasar Framework v2.12.5
|
|
* (c) 2015-present Razvan Stoenescu
|
|
* Released under the MIT License.
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) :
|
|
typeof define === 'function' && define.amd ? define(['vue'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Quasar = factory(global.Vue));
|
|
})(this, (function (vue) { 'use strict';
|
|
|
|
function injectProp (target, propName, get, set) {
|
|
Object.defineProperty(target, propName, {
|
|
get,
|
|
set,
|
|
enumerable: true
|
|
});
|
|
return target
|
|
}
|
|
|
|
function injectMultipleProps (target, props) {
|
|
for (const key in props) {
|
|
injectProp(target, key, props[ key ]);
|
|
}
|
|
return target
|
|
}
|
|
|
|
/* eslint-disable no-useless-escape */
|
|
|
|
/**
|
|
* __ QUASAR_SSR __ -> runs on SSR on client or server
|
|
* __ QUASAR_SSR_SERVER __ -> runs on SSR on server
|
|
* __ QUASAR_SSR_CLIENT __ -> runs on SSR on client
|
|
* __ QUASAR_SSR_PWA __ -> built with SSR+PWA; may run on SSR on client or on PWA client
|
|
* (needs runtime detection)
|
|
*/
|
|
|
|
const isRuntimeSsrPreHydration = vue.ref(
|
|
false
|
|
);
|
|
let iosCorrection;
|
|
|
|
function getMatch (userAgent, platformMatch) {
|
|
const match = /(edg|edge|edga|edgios)\/([\w.]+)/.exec(userAgent)
|
|
|| /(opr)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(vivaldi)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(chrome|crios)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(version)(applewebkit)[\/]([\w.]+).*(safari)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(webkit)[\/]([\w.]+).*(version)[\/]([\w.]+).*(safari)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(firefox|fxios)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(webkit)[\/]([\w.]+)/.exec(userAgent)
|
|
|| /(opera)(?:.*version|)[\/]([\w.]+)/.exec(userAgent)
|
|
|| [];
|
|
|
|
return {
|
|
browser: match[ 5 ] || match[ 3 ] || match[ 1 ] || '',
|
|
version: match[ 2 ] || match[ 4 ] || '0',
|
|
versionNumber: match[ 4 ] || match[ 2 ] || '0',
|
|
platform: platformMatch[ 0 ] || ''
|
|
}
|
|
}
|
|
|
|
function getPlatformMatch (userAgent) {
|
|
return /(ipad)/.exec(userAgent)
|
|
|| /(ipod)/.exec(userAgent)
|
|
|| /(windows phone)/.exec(userAgent)
|
|
|| /(iphone)/.exec(userAgent)
|
|
|| /(kindle)/.exec(userAgent)
|
|
|| /(silk)/.exec(userAgent)
|
|
|| /(android)/.exec(userAgent)
|
|
|| /(win)/.exec(userAgent)
|
|
|| /(mac)/.exec(userAgent)
|
|
|| /(linux)/.exec(userAgent)
|
|
|| /(cros)/.exec(userAgent)
|
|
// TODO: Remove BlackBerry detection. BlackBerry OS, BlackBerry 10, and BlackBerry PlayBook OS
|
|
// is officially dead as of January 4, 2022 (https://www.blackberry.com/us/en/support/devices/end-of-life)
|
|
|| /(playbook)/.exec(userAgent)
|
|
|| /(bb)/.exec(userAgent)
|
|
|| /(blackberry)/.exec(userAgent)
|
|
|| []
|
|
}
|
|
|
|
const hasTouch = 'ontouchstart' in window || window.navigator.maxTouchPoints > 0;
|
|
|
|
function applyIosCorrection (is) {
|
|
iosCorrection = { is: { ...is } };
|
|
|
|
delete is.mac;
|
|
delete is.desktop;
|
|
|
|
const platform = Math.min(window.innerHeight, window.innerWidth) > 414
|
|
? 'ipad'
|
|
: 'iphone';
|
|
|
|
Object.assign(is, {
|
|
mobile: true,
|
|
ios: true,
|
|
platform,
|
|
[ platform ]: true
|
|
});
|
|
}
|
|
|
|
function getPlatform (UA) {
|
|
const
|
|
userAgent = UA.toLowerCase(),
|
|
platformMatch = getPlatformMatch(userAgent),
|
|
matched = getMatch(userAgent, platformMatch),
|
|
browser = {};
|
|
|
|
if (matched.browser) {
|
|
browser[ matched.browser ] = true;
|
|
browser.version = matched.version;
|
|
browser.versionNumber = parseInt(matched.versionNumber, 10);
|
|
}
|
|
|
|
if (matched.platform) {
|
|
browser[ matched.platform ] = true;
|
|
}
|
|
|
|
const knownMobiles = browser.android
|
|
|| browser.ios
|
|
|| browser.bb
|
|
|| browser.blackberry
|
|
|| browser.ipad
|
|
|| browser.iphone
|
|
|| browser.ipod
|
|
|| browser.kindle
|
|
|| browser.playbook
|
|
|| browser.silk
|
|
|| browser[ 'windows phone' ];
|
|
|
|
// These are all considered mobile platforms, meaning they run a mobile browser
|
|
if (knownMobiles === true || userAgent.indexOf('mobile') > -1) {
|
|
browser.mobile = true;
|
|
|
|
if (browser.edga || browser.edgios) {
|
|
browser.edge = true;
|
|
matched.browser = 'edge';
|
|
}
|
|
else if (browser.crios) {
|
|
browser.chrome = true;
|
|
matched.browser = 'chrome';
|
|
}
|
|
else if (browser.fxios) {
|
|
browser.firefox = true;
|
|
matched.browser = 'firefox';
|
|
}
|
|
}
|
|
// If it's not mobile we should consider it's desktop platform, meaning it runs a desktop browser
|
|
// It's a workaround for anonymized user agents
|
|
// (browser.cros || browser.mac || browser.linux || browser.win)
|
|
else {
|
|
browser.desktop = true;
|
|
}
|
|
|
|
// Set iOS if on iPod, iPad or iPhone
|
|
if (browser.ipod || browser.ipad || browser.iphone) {
|
|
browser.ios = true;
|
|
}
|
|
|
|
if (browser[ 'windows phone' ]) {
|
|
browser.winphone = true;
|
|
delete browser[ 'windows phone' ];
|
|
}
|
|
|
|
// TODO: The assumption about WebKit based browsers below is not completely accurate.
|
|
// Google released Blink(a fork of WebKit) engine on April 3, 2013, which is really different than WebKit today.
|
|
// Today, one might want to check for WebKit to deal with its bugs, which is used on all browsers on iOS, and Safari browser on all platforms.
|
|
|
|
// Chrome, Opera 15+, Vivaldi and Safari are webkit based browsers
|
|
if (
|
|
browser.chrome
|
|
|| browser.opr
|
|
|| browser.safari
|
|
|| browser.vivaldi
|
|
// we expect unknown, non iOS mobile browsers to be webkit based
|
|
|| (
|
|
browser.mobile === true
|
|
&& browser.ios !== true
|
|
&& knownMobiles !== true
|
|
)
|
|
) {
|
|
browser.webkit = true;
|
|
}
|
|
|
|
// TODO: (Qv3) rename the terms 'edge' to 'edge legacy'(or remove it) then 'edge chromium' to 'edge' to match with the known up-to-date terms
|
|
// Microsoft Edge is the new Chromium-based browser. Microsoft Edge Legacy is the old EdgeHTML-based browser (EOL: March 9, 2021).
|
|
if (browser.edg) {
|
|
matched.browser = 'edgechromium';
|
|
browser.edgeChromium = true;
|
|
}
|
|
|
|
// Blackberry browsers are marked as Safari on BlackBerry
|
|
if ((browser.safari && browser.blackberry) || browser.bb) {
|
|
matched.browser = 'blackberry';
|
|
browser.blackberry = true;
|
|
}
|
|
|
|
// Playbook browsers are marked as Safari on Playbook
|
|
if (browser.safari && browser.playbook) {
|
|
matched.browser = 'playbook';
|
|
browser.playbook = true;
|
|
}
|
|
|
|
// Opera 15+ are identified as opr
|
|
if (browser.opr) {
|
|
matched.browser = 'opera';
|
|
browser.opera = true;
|
|
}
|
|
|
|
// Stock Android browsers are marked as Safari on Android.
|
|
if (browser.safari && browser.android) {
|
|
matched.browser = 'android';
|
|
browser.android = true;
|
|
}
|
|
|
|
// Kindle browsers are marked as Safari on Kindle
|
|
if (browser.safari && browser.kindle) {
|
|
matched.browser = 'kindle';
|
|
browser.kindle = true;
|
|
}
|
|
|
|
// Kindle Silk browsers are marked as Safari on Kindle
|
|
if (browser.safari && browser.silk) {
|
|
matched.browser = 'silk';
|
|
browser.silk = true;
|
|
}
|
|
|
|
if (browser.vivaldi) {
|
|
matched.browser = 'vivaldi';
|
|
browser.vivaldi = true;
|
|
}
|
|
|
|
// Assign the name and platform variable
|
|
browser.name = matched.browser;
|
|
browser.platform = matched.platform;
|
|
|
|
{
|
|
if (userAgent.indexOf('electron') > -1) {
|
|
browser.electron = true;
|
|
}
|
|
else if (document.location.href.indexOf('-extension://') > -1) {
|
|
browser.bex = true;
|
|
}
|
|
else {
|
|
if (window.Capacitor !== void 0) {
|
|
browser.capacitor = true;
|
|
browser.nativeMobile = true;
|
|
browser.nativeMobileWrapper = 'capacitor';
|
|
}
|
|
else if (window._cordovaNative !== void 0 || window.cordova !== void 0) {
|
|
browser.cordova = true;
|
|
browser.nativeMobile = true;
|
|
browser.nativeMobileWrapper = 'cordova';
|
|
}
|
|
|
|
if (
|
|
hasTouch === true
|
|
&& browser.mac === true
|
|
&& (
|
|
(browser.desktop === true && browser.safari === true)
|
|
|| (
|
|
browser.nativeMobile === true
|
|
&& browser.android !== true
|
|
&& browser.ios !== true
|
|
&& browser.ipad !== true
|
|
)
|
|
)
|
|
) {
|
|
/*
|
|
* Correction needed for iOS since the default
|
|
* setting on iPad is to request desktop view; if we have
|
|
* touch support and the user agent says it's a
|
|
* desktop, we infer that it's an iPhone/iPad with desktop view
|
|
* so we must fix the false positives
|
|
*/
|
|
applyIosCorrection(browser);
|
|
}
|
|
}
|
|
}
|
|
|
|
return browser
|
|
}
|
|
|
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
|
|
const ssrClient = {
|
|
has: {
|
|
touch: false,
|
|
webStorage: false
|
|
},
|
|
within: { iframe: false }
|
|
};
|
|
|
|
// We export "client" for hydration error-free parts,
|
|
// like touch directives who do not (and must NOT) wait
|
|
// for the client takeover;
|
|
// Do NOT import this directly in your app, unless you really know
|
|
// what you are doing.
|
|
const client = {
|
|
userAgent,
|
|
is: getPlatform(userAgent),
|
|
has: {
|
|
touch: hasTouch
|
|
},
|
|
within: {
|
|
iframe: window.self !== window.top
|
|
}
|
|
};
|
|
|
|
const Platform = {
|
|
install (opts) {
|
|
const { $q } = opts;
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
// takeover should increase accuracy for
|
|
// the rest of the props; we also avoid
|
|
// hydration errors
|
|
opts.onSSRHydrated.push(() => {
|
|
Object.assign($q.platform, client);
|
|
isRuntimeSsrPreHydration.value = false;
|
|
iosCorrection = void 0;
|
|
});
|
|
|
|
// we need to make platform reactive
|
|
// for the takeover phase
|
|
$q.platform = vue.reactive(this);
|
|
}
|
|
else {
|
|
$q.platform = this;
|
|
}
|
|
}
|
|
};
|
|
|
|
{
|
|
// do not access window.localStorage without
|
|
// devland actually using it as this will get
|
|
// reported under "Cookies" in Google Chrome
|
|
let hasWebStorage;
|
|
|
|
injectProp(client.has, 'webStorage', () => {
|
|
if (hasWebStorage !== void 0) {
|
|
return hasWebStorage
|
|
}
|
|
|
|
try {
|
|
if (window.localStorage) {
|
|
hasWebStorage = true;
|
|
return true
|
|
}
|
|
}
|
|
catch (e) {}
|
|
|
|
hasWebStorage = false;
|
|
return false
|
|
});
|
|
|
|
client.is.ios === true
|
|
&& window.navigator.vendor.toLowerCase().indexOf('apple') === -1;
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
// must match with server-side before
|
|
// client taking over in order to prevent
|
|
// hydration errors
|
|
Object.assign(Platform, client, iosCorrection, ssrClient);
|
|
}
|
|
else {
|
|
Object.assign(Platform, client);
|
|
}
|
|
}
|
|
|
|
var defineReactivePlugin = (state, plugin) => {
|
|
const reactiveState = vue.reactive(state);
|
|
|
|
for (const name in state) {
|
|
injectProp(
|
|
plugin,
|
|
name,
|
|
() => reactiveState[ name ],
|
|
val => { reactiveState[ name ] = val; }
|
|
);
|
|
}
|
|
|
|
return plugin
|
|
};
|
|
|
|
const listenOpts = {
|
|
hasPassive: false,
|
|
passiveCapture: true,
|
|
notPassiveCapture: true
|
|
};
|
|
|
|
try {
|
|
const opts = Object.defineProperty({}, 'passive', {
|
|
get () {
|
|
Object.assign(listenOpts, {
|
|
hasPassive: true,
|
|
passive: { passive: true },
|
|
notPassive: { passive: false },
|
|
passiveCapture: { passive: true, capture: true },
|
|
notPassiveCapture: { passive: false, capture: true }
|
|
});
|
|
}
|
|
});
|
|
window.addEventListener('qtest', null, opts);
|
|
window.removeEventListener('qtest', null, opts);
|
|
}
|
|
catch (e) {}
|
|
|
|
function noop () {}
|
|
|
|
function leftClick (e) {
|
|
return e.button === 0
|
|
}
|
|
|
|
function middleClick (e) {
|
|
return e.button === 1
|
|
}
|
|
|
|
function rightClick (e) {
|
|
return e.button === 2
|
|
}
|
|
|
|
function position (e) {
|
|
if (e.touches && e.touches[ 0 ]) {
|
|
e = e.touches[ 0 ];
|
|
}
|
|
else if (e.changedTouches && e.changedTouches[ 0 ]) {
|
|
e = e.changedTouches[ 0 ];
|
|
}
|
|
else if (e.targetTouches && e.targetTouches[ 0 ]) {
|
|
e = e.targetTouches[ 0 ];
|
|
}
|
|
|
|
return {
|
|
top: e.clientY,
|
|
left: e.clientX
|
|
}
|
|
}
|
|
|
|
function getEventPath (e) {
|
|
if (e.path) {
|
|
return e.path
|
|
}
|
|
if (e.composedPath) {
|
|
return e.composedPath()
|
|
}
|
|
|
|
const path = [];
|
|
let el = e.target;
|
|
|
|
while (el) {
|
|
path.push(el);
|
|
|
|
if (el.tagName === 'HTML') {
|
|
path.push(document);
|
|
path.push(window);
|
|
return path
|
|
}
|
|
|
|
el = el.parentElement;
|
|
}
|
|
}
|
|
|
|
// Reasonable defaults
|
|
const
|
|
LINE_HEIGHT = 40,
|
|
PAGE_HEIGHT = 800;
|
|
|
|
function getMouseWheelDistance (e) {
|
|
let x = e.deltaX, y = e.deltaY;
|
|
|
|
if ((x || y) && e.deltaMode) {
|
|
const multiplier = e.deltaMode === 1 ? LINE_HEIGHT : PAGE_HEIGHT;
|
|
x *= multiplier;
|
|
y *= multiplier;
|
|
}
|
|
|
|
if (e.shiftKey && !x) {
|
|
[ y, x ] = [ x, y ];
|
|
}
|
|
|
|
return { x, y }
|
|
}
|
|
|
|
function stop (e) {
|
|
e.stopPropagation();
|
|
}
|
|
|
|
function prevent (e) {
|
|
e.cancelable !== false && e.preventDefault();
|
|
}
|
|
|
|
function stopAndPrevent (e) {
|
|
e.cancelable !== false && e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
function preventDraggable (el, status) {
|
|
if (el === void 0 || (status === true && el.__dragPrevented === true)) {
|
|
return
|
|
}
|
|
|
|
const fn = status === true
|
|
? el => {
|
|
el.__dragPrevented = true;
|
|
el.addEventListener('dragstart', prevent, listenOpts.notPassiveCapture);
|
|
}
|
|
: el => {
|
|
delete el.__dragPrevented;
|
|
el.removeEventListener('dragstart', prevent, listenOpts.notPassiveCapture);
|
|
};
|
|
|
|
el.querySelectorAll('a, img').forEach(fn);
|
|
}
|
|
|
|
function addEvt (ctx, targetName, events) {
|
|
const name = `__q_${ targetName }_evt`;
|
|
|
|
ctx[ name ] = ctx[ name ] !== void 0
|
|
? ctx[ name ].concat(events)
|
|
: events;
|
|
|
|
events.forEach(evt => {
|
|
evt[ 0 ].addEventListener(evt[ 1 ], ctx[ evt[ 2 ] ], listenOpts[ evt[ 3 ] ]);
|
|
});
|
|
}
|
|
|
|
function cleanEvt (ctx, targetName) {
|
|
const name = `__q_${ targetName }_evt`;
|
|
|
|
if (ctx[ name ] !== void 0) {
|
|
ctx[ name ].forEach(evt => {
|
|
evt[ 0 ].removeEventListener(evt[ 1 ], ctx[ evt[ 2 ] ], listenOpts[ evt[ 3 ] ]);
|
|
});
|
|
ctx[ name ] = void 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* also update /types/utils/event.d.ts
|
|
*/
|
|
|
|
var event = {
|
|
listenOpts,
|
|
leftClick,
|
|
middleClick,
|
|
rightClick,
|
|
position,
|
|
getEventPath,
|
|
getMouseWheelDistance,
|
|
stop,
|
|
prevent,
|
|
stopAndPrevent,
|
|
preventDraggable
|
|
};
|
|
|
|
function debounce (fn, wait = 250, immediate) {
|
|
let timer = null;
|
|
|
|
function debounced (/* ...args */) {
|
|
const args = arguments;
|
|
|
|
const later = () => {
|
|
timer = null;
|
|
if (immediate !== true) {
|
|
fn.apply(this, args);
|
|
}
|
|
};
|
|
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
}
|
|
else if (immediate === true) {
|
|
fn.apply(this, args);
|
|
}
|
|
|
|
timer = setTimeout(later, wait);
|
|
}
|
|
|
|
debounced.cancel = () => {
|
|
timer !== null && clearTimeout(timer);
|
|
};
|
|
|
|
return debounced
|
|
}
|
|
|
|
const SIZE_LIST = [ 'sm', 'md', 'lg', 'xl' ];
|
|
const { passive: passive$4 } = listenOpts;
|
|
|
|
var Screen = defineReactivePlugin({
|
|
width: 0,
|
|
height: 0,
|
|
name: 'xs',
|
|
|
|
sizes: {
|
|
sm: 600,
|
|
md: 1024,
|
|
lg: 1440,
|
|
xl: 1920
|
|
},
|
|
|
|
lt: {
|
|
sm: true,
|
|
md: true,
|
|
lg: true,
|
|
xl: true
|
|
},
|
|
gt: {
|
|
xs: false,
|
|
sm: false,
|
|
md: false,
|
|
lg: false
|
|
},
|
|
|
|
xs: true,
|
|
sm: false,
|
|
md: false,
|
|
lg: false,
|
|
xl: false
|
|
}, {
|
|
setSizes: noop,
|
|
setDebounce: noop,
|
|
|
|
install ({ $q, onSSRHydrated }) {
|
|
$q.screen = this;
|
|
|
|
if (this.__installed === true) {
|
|
if ($q.config.screen !== void 0) {
|
|
if ($q.config.screen.bodyClasses === false) {
|
|
document.body.classList.remove(`screen--${ this.name }`);
|
|
}
|
|
else {
|
|
this.__update(true);
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
const { visualViewport } = window;
|
|
const target = visualViewport || window;
|
|
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
const getSize = visualViewport === void 0 || client.is.mobile === true
|
|
? () => [
|
|
Math.max(window.innerWidth, scrollingElement.clientWidth),
|
|
Math.max(window.innerHeight, scrollingElement.clientHeight)
|
|
]
|
|
: () => [
|
|
visualViewport.width * visualViewport.scale + window.innerWidth - scrollingElement.clientWidth,
|
|
visualViewport.height * visualViewport.scale + window.innerHeight - scrollingElement.clientHeight
|
|
];
|
|
|
|
const classes = $q.config.screen !== void 0 && $q.config.screen.bodyClasses === true;
|
|
|
|
this.__update = force => {
|
|
const [ w, h ] = getSize();
|
|
|
|
if (h !== this.height) {
|
|
this.height = h;
|
|
}
|
|
|
|
if (w !== this.width) {
|
|
this.width = w;
|
|
}
|
|
else if (force !== true) {
|
|
return
|
|
}
|
|
|
|
let s = this.sizes;
|
|
|
|
this.gt.xs = w >= s.sm;
|
|
this.gt.sm = w >= s.md;
|
|
this.gt.md = w >= s.lg;
|
|
this.gt.lg = w >= s.xl;
|
|
this.lt.sm = w < s.sm;
|
|
this.lt.md = w < s.md;
|
|
this.lt.lg = w < s.lg;
|
|
this.lt.xl = w < s.xl;
|
|
this.xs = this.lt.sm;
|
|
this.sm = this.gt.xs === true && this.lt.md === true;
|
|
this.md = this.gt.sm === true && this.lt.lg === true;
|
|
this.lg = this.gt.md === true && this.lt.xl === true;
|
|
this.xl = this.gt.lg;
|
|
|
|
s = (this.xs === true && 'xs')
|
|
|| (this.sm === true && 'sm')
|
|
|| (this.md === true && 'md')
|
|
|| (this.lg === true && 'lg')
|
|
|| 'xl';
|
|
|
|
if (s !== this.name) {
|
|
if (classes === true) {
|
|
document.body.classList.remove(`screen--${ this.name }`);
|
|
document.body.classList.add(`screen--${ s }`);
|
|
}
|
|
this.name = s;
|
|
}
|
|
};
|
|
|
|
let updateEvt, updateSizes = {}, updateDebounce = 16;
|
|
|
|
this.setSizes = sizes => {
|
|
SIZE_LIST.forEach(name => {
|
|
if (sizes[ name ] !== void 0) {
|
|
updateSizes[ name ] = sizes[ name ];
|
|
}
|
|
});
|
|
};
|
|
this.setDebounce = deb => {
|
|
updateDebounce = deb;
|
|
};
|
|
|
|
const start = () => {
|
|
const style = getComputedStyle(document.body);
|
|
|
|
// if css props available
|
|
if (style.getPropertyValue('--q-size-sm')) {
|
|
SIZE_LIST.forEach(name => {
|
|
this.sizes[ name ] = parseInt(style.getPropertyValue(`--q-size-${ name }`), 10);
|
|
});
|
|
}
|
|
|
|
this.setSizes = sizes => {
|
|
SIZE_LIST.forEach(name => {
|
|
if (sizes[ name ]) {
|
|
this.sizes[ name ] = sizes[ name ];
|
|
}
|
|
});
|
|
this.__update(true);
|
|
};
|
|
|
|
this.setDebounce = delay => {
|
|
updateEvt !== void 0 && target.removeEventListener('resize', updateEvt, passive$4);
|
|
updateEvt = delay > 0
|
|
? debounce(this.__update, delay)
|
|
: this.__update;
|
|
target.addEventListener('resize', updateEvt, passive$4);
|
|
};
|
|
|
|
this.setDebounce(updateDebounce);
|
|
|
|
if (Object.keys(updateSizes).length !== 0) {
|
|
this.setSizes(updateSizes);
|
|
updateSizes = void 0; // free up memory
|
|
}
|
|
else {
|
|
this.__update();
|
|
}
|
|
|
|
// due to optimizations, this would be left out otherwise
|
|
classes === true && this.name === 'xs'
|
|
&& document.body.classList.add('screen--xs');
|
|
};
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
onSSRHydrated.push(start);
|
|
}
|
|
else {
|
|
start();
|
|
}
|
|
}
|
|
});
|
|
|
|
const Plugin$9 = defineReactivePlugin({
|
|
isActive: false,
|
|
mode: false
|
|
}, {
|
|
__media: void 0,
|
|
|
|
set (val) {
|
|
|
|
Plugin$9.mode = val;
|
|
|
|
if (val === 'auto') {
|
|
if (Plugin$9.__media === void 0) {
|
|
Plugin$9.__media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
Plugin$9.__updateMedia = () => { Plugin$9.set('auto'); };
|
|
Plugin$9.__media.addListener(Plugin$9.__updateMedia);
|
|
}
|
|
|
|
val = Plugin$9.__media.matches;
|
|
}
|
|
else if (Plugin$9.__media !== void 0) {
|
|
Plugin$9.__media.removeListener(Plugin$9.__updateMedia);
|
|
Plugin$9.__media = void 0;
|
|
}
|
|
|
|
Plugin$9.isActive = val === true;
|
|
|
|
document.body.classList.remove(`body--${ val === true ? 'light' : 'dark' }`);
|
|
document.body.classList.add(`body--${ val === true ? 'dark' : 'light' }`);
|
|
},
|
|
|
|
toggle () {
|
|
{
|
|
Plugin$9.set(Plugin$9.isActive === false);
|
|
}
|
|
},
|
|
|
|
install ({ $q, onSSRHydrated, ssrContext }) {
|
|
const { dark } = $q.config;
|
|
|
|
$q.dark = this;
|
|
|
|
if (this.__installed === true && dark === void 0) {
|
|
return
|
|
}
|
|
|
|
this.isActive = dark === true;
|
|
|
|
const initialVal = dark !== void 0 ? dark : false;
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
const ssrSet = val => {
|
|
this.__fromSSR = val;
|
|
};
|
|
|
|
const originalSet = this.set;
|
|
|
|
this.set = ssrSet;
|
|
ssrSet(initialVal);
|
|
|
|
onSSRHydrated.push(() => {
|
|
this.set = originalSet;
|
|
this.set(this.__fromSSR);
|
|
});
|
|
}
|
|
else {
|
|
this.set(initialVal);
|
|
}
|
|
}
|
|
});
|
|
|
|
const getTrue = () => true;
|
|
|
|
function filterInvalidPath (path) {
|
|
return typeof path === 'string'
|
|
&& path !== ''
|
|
&& path !== '/'
|
|
&& path !== '#/'
|
|
}
|
|
|
|
function normalizeExitPath (path) {
|
|
path.startsWith('#') === true && (path = path.substring(1));
|
|
path.startsWith('/') === false && (path = '/' + path);
|
|
path.endsWith('/') === true && (path = path.substring(0, path.length - 1));
|
|
return '#' + path
|
|
}
|
|
|
|
function getShouldExitFn (cfg) {
|
|
if (cfg.backButtonExit === false) {
|
|
return () => false
|
|
}
|
|
|
|
if (cfg.backButtonExit === '*') {
|
|
return getTrue
|
|
}
|
|
|
|
// Add default root path
|
|
const exitPaths = [ '#/' ];
|
|
|
|
// Add custom exit paths
|
|
Array.isArray(cfg.backButtonExit) === true && exitPaths.push(
|
|
...cfg.backButtonExit.filter(filterInvalidPath).map(normalizeExitPath)
|
|
);
|
|
|
|
return () => exitPaths.includes(window.location.hash)
|
|
}
|
|
|
|
var History = {
|
|
__history: [],
|
|
add: noop,
|
|
remove: noop,
|
|
|
|
install ({ $q }) {
|
|
if (this.__installed === true) { return }
|
|
|
|
const { cordova, capacitor } = client.is;
|
|
|
|
if (cordova !== true && capacitor !== true) {
|
|
return
|
|
}
|
|
|
|
const qConf = $q.config[ cordova === true ? 'cordova' : 'capacitor' ];
|
|
|
|
if (qConf !== void 0 && qConf.backButton === false) {
|
|
return
|
|
}
|
|
|
|
// if the '@capacitor/app' plugin is not installed
|
|
// then we got nothing to do
|
|
if (
|
|
// if we're on Capacitor mode
|
|
capacitor === true
|
|
// and it's also not in Capacitor's main instance
|
|
&& (window.Capacitor === void 0 || window.Capacitor.Plugins.App === void 0)
|
|
) {
|
|
return
|
|
}
|
|
|
|
this.add = entry => {
|
|
if (entry.condition === void 0) {
|
|
entry.condition = getTrue;
|
|
}
|
|
this.__history.push(entry);
|
|
};
|
|
|
|
this.remove = entry => {
|
|
const index = this.__history.indexOf(entry);
|
|
if (index >= 0) {
|
|
this.__history.splice(index, 1);
|
|
}
|
|
};
|
|
|
|
const shouldExit = getShouldExitFn(
|
|
Object.assign(
|
|
{ backButtonExit: true },
|
|
qConf
|
|
)
|
|
);
|
|
|
|
const backHandler = () => {
|
|
if (this.__history.length) {
|
|
const entry = this.__history[ this.__history.length - 1 ];
|
|
|
|
if (entry.condition() === true) {
|
|
this.__history.pop();
|
|
entry.handler();
|
|
}
|
|
}
|
|
else if (shouldExit() === true) {
|
|
navigator.app.exitApp();
|
|
}
|
|
else {
|
|
window.history.back();
|
|
}
|
|
};
|
|
|
|
if (cordova === true) {
|
|
document.addEventListener('deviceready', () => {
|
|
document.addEventListener('backbutton', backHandler, false);
|
|
});
|
|
}
|
|
else {
|
|
window.Capacitor.Plugins.App.addListener('backButton', backHandler);
|
|
}
|
|
}
|
|
};
|
|
|
|
var defaultLang = {
|
|
isoName: 'en-US',
|
|
nativeName: 'English (US)',
|
|
label: {
|
|
clear: 'Clear',
|
|
ok: 'OK',
|
|
cancel: 'Cancel',
|
|
close: 'Close',
|
|
set: 'Set',
|
|
select: 'Select',
|
|
reset: 'Reset',
|
|
remove: 'Remove',
|
|
update: 'Update',
|
|
create: 'Create',
|
|
search: 'Search',
|
|
filter: 'Filter',
|
|
refresh: 'Refresh',
|
|
expand: label => (label ? `Expand "${ label }"` : 'Expand'),
|
|
collapse: label => (label ? `Collapse "${ label }"` : 'Collapse')
|
|
},
|
|
date: {
|
|
days: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
|
|
daysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
|
|
months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
|
|
monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
|
|
firstDayOfWeek: 0, // 0-6, 0 - Sunday, 1 Monday, ...
|
|
format24h: false,
|
|
pluralDay: 'days'
|
|
},
|
|
table: {
|
|
noData: 'No data available',
|
|
noResults: 'No matching records found',
|
|
loading: 'Loading...',
|
|
selectedRecords: rows => (
|
|
rows === 1
|
|
? '1 record selected.'
|
|
: (rows === 0 ? 'No' : rows) + ' records selected.'
|
|
),
|
|
recordsPerPage: 'Records per page:',
|
|
allRows: 'All',
|
|
pagination: (start, end, total) => start + '-' + end + ' of ' + total,
|
|
columns: 'Columns'
|
|
},
|
|
editor: {
|
|
url: 'URL',
|
|
bold: 'Bold',
|
|
italic: 'Italic',
|
|
strikethrough: 'Strikethrough',
|
|
underline: 'Underline',
|
|
unorderedList: 'Unordered List',
|
|
orderedList: 'Ordered List',
|
|
subscript: 'Subscript',
|
|
superscript: 'Superscript',
|
|
hyperlink: 'Hyperlink',
|
|
toggleFullscreen: 'Toggle Fullscreen',
|
|
quote: 'Quote',
|
|
left: 'Left align',
|
|
center: 'Center align',
|
|
right: 'Right align',
|
|
justify: 'Justify align',
|
|
print: 'Print',
|
|
outdent: 'Decrease indentation',
|
|
indent: 'Increase indentation',
|
|
removeFormat: 'Remove formatting',
|
|
formatting: 'Formatting',
|
|
fontSize: 'Font Size',
|
|
align: 'Align',
|
|
hr: 'Insert Horizontal Rule',
|
|
undo: 'Undo',
|
|
redo: 'Redo',
|
|
heading1: 'Heading 1',
|
|
heading2: 'Heading 2',
|
|
heading3: 'Heading 3',
|
|
heading4: 'Heading 4',
|
|
heading5: 'Heading 5',
|
|
heading6: 'Heading 6',
|
|
paragraph: 'Paragraph',
|
|
code: 'Code',
|
|
size1: 'Very small',
|
|
size2: 'A bit small',
|
|
size3: 'Normal',
|
|
size4: 'Medium-large',
|
|
size5: 'Big',
|
|
size6: 'Very big',
|
|
size7: 'Maximum',
|
|
defaultFont: 'Default Font',
|
|
viewSource: 'View Source'
|
|
},
|
|
tree: {
|
|
noNodes: 'No nodes available',
|
|
noResults: 'No matching nodes found'
|
|
}
|
|
};
|
|
|
|
function getLocale () {
|
|
|
|
const val = Array.isArray(navigator.languages) === true && navigator.languages.length !== 0
|
|
? navigator.languages[ 0 ]
|
|
: navigator.language;
|
|
|
|
if (typeof val === 'string') {
|
|
return val.split(/[-_]/).map((v, i) => (
|
|
i === 0
|
|
? v.toLowerCase()
|
|
: (
|
|
i > 1 || v.length < 4
|
|
? v.toUpperCase()
|
|
: (v[ 0 ].toUpperCase() + v.slice(1).toLowerCase())
|
|
)
|
|
)).join('-')
|
|
}
|
|
}
|
|
|
|
const Plugin$8 = defineReactivePlugin({
|
|
__langPack: {}
|
|
}, {
|
|
getLocale,
|
|
|
|
set (langObject = defaultLang, ssrContext) {
|
|
const lang = {
|
|
...langObject,
|
|
rtl: langObject.rtl === true,
|
|
getLocale
|
|
};
|
|
|
|
{
|
|
lang.set = Plugin$8.set;
|
|
|
|
if (Plugin$8.__langConfig === void 0 || Plugin$8.__langConfig.noHtmlAttrs !== true) {
|
|
const el = document.documentElement;
|
|
el.setAttribute('dir', lang.rtl === true ? 'rtl' : 'ltr');
|
|
el.setAttribute('lang', lang.isoName);
|
|
}
|
|
|
|
Object.assign(Plugin$8.__langPack, lang);
|
|
|
|
Plugin$8.props = lang;
|
|
Plugin$8.isoName = lang.isoName;
|
|
Plugin$8.nativeName = lang.nativeName;
|
|
}
|
|
},
|
|
|
|
install ({ $q, lang, ssrContext }) {
|
|
{
|
|
$q.lang = Plugin$8.__langPack;
|
|
Plugin$8.__langConfig = $q.config.lang;
|
|
|
|
if (this.__installed === true) {
|
|
lang !== void 0 && this.set(lang);
|
|
}
|
|
else {
|
|
this.set(lang || defaultLang);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function setCssVar (propName, value, element = document.body) {
|
|
if (typeof propName !== 'string') {
|
|
throw new TypeError('Expected a string as propName')
|
|
}
|
|
if (typeof value !== 'string') {
|
|
throw new TypeError('Expected a string as value')
|
|
}
|
|
if (!(element instanceof Element)) {
|
|
throw new TypeError('Expected a DOM element')
|
|
}
|
|
|
|
element.style.setProperty(`--q-${ propName }`, value);
|
|
}
|
|
|
|
let lastKeyCompositionStatus = false;
|
|
|
|
function onKeyDownComposition (evt) {
|
|
lastKeyCompositionStatus = evt.isComposing === true;
|
|
}
|
|
|
|
function shouldIgnoreKey (evt) {
|
|
return lastKeyCompositionStatus === true
|
|
|| evt !== Object(evt)
|
|
|| evt.isComposing === true
|
|
|| evt.qKeyEvent === true
|
|
}
|
|
|
|
function isKeyCode (evt, keyCodes) {
|
|
return shouldIgnoreKey(evt) === true
|
|
? false
|
|
: [].concat(keyCodes).includes(evt.keyCode)
|
|
}
|
|
|
|
function getMobilePlatform (is) {
|
|
if (is.ios === true) return 'ios'
|
|
if (is.android === true) return 'android'
|
|
}
|
|
|
|
function getBodyClasses ({ is, has, within }, cfg) {
|
|
const cls = [
|
|
is.desktop === true ? 'desktop' : 'mobile',
|
|
`${ has.touch === false ? 'no-' : '' }touch`
|
|
];
|
|
|
|
if (is.mobile === true) {
|
|
const mobile = getMobilePlatform(is);
|
|
mobile !== void 0 && cls.push('platform-' + mobile);
|
|
}
|
|
|
|
if (is.nativeMobile === true) {
|
|
const type = is.nativeMobileWrapper;
|
|
|
|
cls.push(type);
|
|
cls.push('native-mobile');
|
|
|
|
if (
|
|
is.ios === true
|
|
&& (cfg[ type ] === void 0 || cfg[ type ].iosStatusBarPadding !== false)
|
|
) {
|
|
cls.push('q-ios-padding');
|
|
}
|
|
}
|
|
else if (is.electron === true) {
|
|
cls.push('electron');
|
|
}
|
|
else if (is.bex === true) {
|
|
cls.push('bex');
|
|
}
|
|
|
|
within.iframe === true && cls.push('within-iframe');
|
|
|
|
return cls
|
|
}
|
|
|
|
function applyClientSsrCorrections () {
|
|
const { is } = client;
|
|
const classes = document.body.className;
|
|
|
|
const classList = new Set(classes.replace(/ {2}/g, ' ').split(' '));
|
|
|
|
if (iosCorrection !== void 0) {
|
|
classList.delete('desktop');
|
|
classList.add('platform-ios');
|
|
classList.add('mobile');
|
|
}
|
|
// else: is it SSG?
|
|
else if (is.nativeMobile !== true && is.electron !== true && is.bex !== true) {
|
|
if (is.desktop === true) {
|
|
classList.delete('mobile');
|
|
classList.delete('platform-ios');
|
|
classList.delete('platform-android');
|
|
classList.add('desktop');
|
|
}
|
|
else if (is.mobile === true) {
|
|
classList.delete('desktop');
|
|
classList.add('mobile');
|
|
|
|
const mobile = getMobilePlatform(is);
|
|
if (mobile !== void 0) {
|
|
classList.add(`platform-${ mobile }`);
|
|
classList.delete(`platform-${ mobile === 'ios' ? 'android' : 'ios' }`);
|
|
}
|
|
else {
|
|
classList.delete('platform-ios');
|
|
classList.delete('platform-android');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client.has.touch === true) {
|
|
classList.delete('no-touch');
|
|
classList.add('touch');
|
|
}
|
|
|
|
if (client.within.iframe === true) {
|
|
classList.add('within-iframe');
|
|
}
|
|
|
|
const newCls = Array.from(classList).join(' ');
|
|
|
|
if (classes !== newCls) {
|
|
document.body.className = newCls;
|
|
}
|
|
}
|
|
|
|
function setColors (brand) {
|
|
for (const color in brand) {
|
|
setCssVar(color, brand[ color ]);
|
|
}
|
|
}
|
|
|
|
var Body = {
|
|
install (opts) {
|
|
|
|
if (this.__installed === true) { return }
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
applyClientSsrCorrections();
|
|
}
|
|
else {
|
|
const { $q } = opts;
|
|
|
|
$q.config.brand !== void 0 && setColors($q.config.brand);
|
|
|
|
const cls = getBodyClasses(client, $q.config);
|
|
document.body.classList.add.apply(document.body.classList, cls);
|
|
}
|
|
|
|
if (client.is.ios === true) {
|
|
// needed for iOS button active state
|
|
document.body.addEventListener('touchstart', noop);
|
|
}
|
|
|
|
window.addEventListener('keydown', onKeyDownComposition, true);
|
|
}
|
|
};
|
|
|
|
var materialIcons = {
|
|
name: 'material-icons',
|
|
type: {
|
|
positive: 'check_circle',
|
|
negative: 'warning',
|
|
info: 'info',
|
|
warning: 'priority_high'
|
|
},
|
|
arrow: {
|
|
up: 'arrow_upward',
|
|
right: 'arrow_forward',
|
|
down: 'arrow_downward',
|
|
left: 'arrow_back',
|
|
dropdown: 'arrow_drop_down'
|
|
},
|
|
chevron: {
|
|
left: 'chevron_left',
|
|
right: 'chevron_right'
|
|
},
|
|
colorPicker: {
|
|
spectrum: 'gradient',
|
|
tune: 'tune',
|
|
palette: 'style'
|
|
},
|
|
pullToRefresh: {
|
|
icon: 'refresh'
|
|
},
|
|
carousel: {
|
|
left: 'chevron_left',
|
|
right: 'chevron_right',
|
|
up: 'keyboard_arrow_up',
|
|
down: 'keyboard_arrow_down',
|
|
navigationIcon: 'lens'
|
|
},
|
|
chip: {
|
|
remove: 'cancel',
|
|
selected: 'check'
|
|
},
|
|
datetime: {
|
|
arrowLeft: 'chevron_left',
|
|
arrowRight: 'chevron_right',
|
|
now: 'access_time',
|
|
today: 'today'
|
|
},
|
|
editor: {
|
|
bold: 'format_bold',
|
|
italic: 'format_italic',
|
|
strikethrough: 'strikethrough_s',
|
|
underline: 'format_underlined',
|
|
unorderedList: 'format_list_bulleted',
|
|
orderedList: 'format_list_numbered',
|
|
subscript: 'vertical_align_bottom',
|
|
superscript: 'vertical_align_top',
|
|
hyperlink: 'link',
|
|
toggleFullscreen: 'fullscreen',
|
|
quote: 'format_quote',
|
|
left: 'format_align_left',
|
|
center: 'format_align_center',
|
|
right: 'format_align_right',
|
|
justify: 'format_align_justify',
|
|
print: 'print',
|
|
outdent: 'format_indent_decrease',
|
|
indent: 'format_indent_increase',
|
|
removeFormat: 'format_clear',
|
|
formatting: 'text_format',
|
|
fontSize: 'format_size',
|
|
align: 'format_align_left',
|
|
hr: 'remove',
|
|
undo: 'undo',
|
|
redo: 'redo',
|
|
heading: 'format_size',
|
|
code: 'code',
|
|
size: 'format_size',
|
|
font: 'font_download',
|
|
viewSource: 'code'
|
|
},
|
|
expansionItem: {
|
|
icon: 'keyboard_arrow_down',
|
|
denseIcon: 'arrow_drop_down'
|
|
},
|
|
fab: {
|
|
icon: 'add',
|
|
activeIcon: 'close'
|
|
},
|
|
field: {
|
|
clear: 'cancel',
|
|
error: 'error'
|
|
},
|
|
pagination: {
|
|
first: 'first_page',
|
|
prev: 'keyboard_arrow_left',
|
|
next: 'keyboard_arrow_right',
|
|
last: 'last_page'
|
|
},
|
|
rating: {
|
|
icon: 'grade'
|
|
},
|
|
stepper: {
|
|
done: 'check',
|
|
active: 'edit',
|
|
error: 'warning'
|
|
},
|
|
tabs: {
|
|
left: 'chevron_left',
|
|
right: 'chevron_right',
|
|
up: 'keyboard_arrow_up',
|
|
down: 'keyboard_arrow_down'
|
|
},
|
|
table: {
|
|
arrowUp: 'arrow_upward',
|
|
warning: 'warning',
|
|
firstPage: 'first_page',
|
|
prevPage: 'chevron_left',
|
|
nextPage: 'chevron_right',
|
|
lastPage: 'last_page'
|
|
},
|
|
tree: {
|
|
icon: 'play_arrow'
|
|
},
|
|
uploader: {
|
|
done: 'done',
|
|
clear: 'clear',
|
|
add: 'add_box',
|
|
upload: 'cloud_upload',
|
|
removeQueue: 'clear_all',
|
|
removeUploaded: 'done_all'
|
|
}
|
|
};
|
|
|
|
const Plugin$7 = defineReactivePlugin({
|
|
iconMapFn: null,
|
|
__icons: {}
|
|
}, {
|
|
set (setObject, ssrContext) {
|
|
const def = { ...setObject, rtl: setObject.rtl === true };
|
|
|
|
{
|
|
def.set = Plugin$7.set;
|
|
Object.assign(Plugin$7.__icons, def);
|
|
}
|
|
},
|
|
|
|
install ({ $q, iconSet, ssrContext }) {
|
|
{
|
|
if ($q.config.iconMapFn !== void 0) {
|
|
this.iconMapFn = $q.config.iconMapFn;
|
|
}
|
|
|
|
$q.iconSet = this.__icons;
|
|
|
|
injectProp($q, 'iconMapFn', () => this.iconMapFn, val => { this.iconMapFn = val; });
|
|
|
|
if (this.__installed === true) {
|
|
iconSet !== void 0 && this.set(iconSet);
|
|
}
|
|
else {
|
|
this.set(iconSet || materialIcons);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const quasarKey = '_q_';
|
|
const timelineKey = '_q_t_';
|
|
const stepperKey = '_q_s_';
|
|
const layoutKey = '_q_l_';
|
|
const pageContainerKey = '_q_pc_';
|
|
const fabKey = '_q_f_';
|
|
const formKey = '_q_fo_';
|
|
const tabsKey = '_q_tabs_';
|
|
const uploaderKey = '_q_u_';
|
|
|
|
const emptyRenderFn = () => {};
|
|
|
|
const globalConfig = {};
|
|
let globalConfigIsFrozen = false;
|
|
|
|
function freezeGlobalConfig () {
|
|
globalConfigIsFrozen = true;
|
|
}
|
|
|
|
function isDeepEqual (a, b) {
|
|
if (a === b) {
|
|
return true
|
|
}
|
|
|
|
if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') {
|
|
if (a.constructor !== b.constructor) {
|
|
return false
|
|
}
|
|
|
|
let length, i;
|
|
|
|
if (a.constructor === Array) {
|
|
length = a.length;
|
|
|
|
if (length !== b.length) {
|
|
return false
|
|
}
|
|
|
|
for (i = length; i-- !== 0;) {
|
|
if (isDeepEqual(a[ i ], b[ i ]) !== true) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if (a.constructor === Map) {
|
|
if (a.size !== b.size) {
|
|
return false
|
|
}
|
|
|
|
let iter = a.entries();
|
|
|
|
i = iter.next();
|
|
while (i.done !== true) {
|
|
if (b.has(i.value[ 0 ]) !== true) {
|
|
return false
|
|
}
|
|
i = iter.next();
|
|
}
|
|
|
|
iter = a.entries();
|
|
i = iter.next();
|
|
while (i.done !== true) {
|
|
if (isDeepEqual(i.value[ 1 ], b.get(i.value[ 0 ])) !== true) {
|
|
return false
|
|
}
|
|
i = iter.next();
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if (a.constructor === Set) {
|
|
if (a.size !== b.size) {
|
|
return false
|
|
}
|
|
|
|
const iter = a.entries();
|
|
|
|
i = iter.next();
|
|
while (i.done !== true) {
|
|
if (b.has(i.value[ 0 ]) !== true) {
|
|
return false
|
|
}
|
|
i = iter.next();
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if (a.buffer != null && a.buffer.constructor === ArrayBuffer) {
|
|
length = a.length;
|
|
|
|
if (length !== b.length) {
|
|
return false
|
|
}
|
|
|
|
for (i = length; i-- !== 0;) {
|
|
if (a[ i ] !== b[ i ]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if (a.constructor === RegExp) {
|
|
return a.source === b.source && a.flags === b.flags
|
|
}
|
|
|
|
if (a.valueOf !== Object.prototype.valueOf) {
|
|
return a.valueOf() === b.valueOf()
|
|
}
|
|
|
|
if (a.toString !== Object.prototype.toString) {
|
|
return a.toString() === b.toString()
|
|
}
|
|
|
|
const keys = Object.keys(a).filter(key => a[ key ] !== void 0);
|
|
length = keys.length;
|
|
|
|
if (length !== Object.keys(b).filter(key => b[ key ] !== void 0).length) {
|
|
return false
|
|
}
|
|
|
|
for (i = length; i-- !== 0;) {
|
|
const key = keys[ i ];
|
|
if (isDeepEqual(a[ key ], b[ key ]) !== true) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// true if both NaN, false otherwise
|
|
return a !== a && b !== b // eslint-disable-line no-self-compare
|
|
}
|
|
|
|
// not perfect, but what we ARE interested is for Arrays not to slip in
|
|
// as spread operator will mess things up in various areas
|
|
function isObject (v) {
|
|
return v !== null && typeof v === 'object' && Array.isArray(v) !== true
|
|
}
|
|
|
|
function isDate (v) {
|
|
return Object.prototype.toString.call(v) === '[object Date]'
|
|
}
|
|
|
|
function isRegexp (v) {
|
|
return Object.prototype.toString.call(v) === '[object RegExp]'
|
|
}
|
|
|
|
function isNumber (v) {
|
|
return typeof v === 'number' && isFinite(v)
|
|
}
|
|
|
|
var is = {
|
|
deepEqual: isDeepEqual,
|
|
object: isObject,
|
|
date: isDate,
|
|
regexp: isRegexp,
|
|
number: isNumber
|
|
};
|
|
|
|
const autoInstalledPlugins = [
|
|
Platform,
|
|
Body,
|
|
Plugin$9,
|
|
Screen,
|
|
History,
|
|
Plugin$8,
|
|
Plugin$7
|
|
];
|
|
|
|
function createChildApp (appCfg, parentApp) {
|
|
const app = vue.createApp(appCfg);
|
|
|
|
app.config.globalProperties = parentApp.config.globalProperties;
|
|
|
|
const { reload, ...appContext } = parentApp._context;
|
|
Object.assign(app._context, appContext);
|
|
|
|
return app
|
|
}
|
|
|
|
function installPlugins (pluginOpts, pluginList) {
|
|
pluginList.forEach(Plugin => {
|
|
Plugin.install(pluginOpts);
|
|
Plugin.__installed = true;
|
|
});
|
|
}
|
|
|
|
function prepareApp (app, uiOpts, pluginOpts) {
|
|
app.config.globalProperties.$q = pluginOpts.$q;
|
|
app.provide(quasarKey, pluginOpts.$q);
|
|
|
|
installPlugins(pluginOpts, autoInstalledPlugins);
|
|
|
|
uiOpts.components !== void 0 && Object.values(uiOpts.components).forEach(c => {
|
|
if (isObject(c) === true && c.name !== void 0) {
|
|
app.component(c.name, c);
|
|
}
|
|
});
|
|
|
|
uiOpts.directives !== void 0 && Object.values(uiOpts.directives).forEach(d => {
|
|
if (isObject(d) === true && d.name !== void 0) {
|
|
app.directive(d.name, d);
|
|
}
|
|
});
|
|
|
|
uiOpts.plugins !== void 0 && installPlugins(
|
|
pluginOpts,
|
|
Object.values(uiOpts.plugins).filter(
|
|
p => typeof p.install === 'function' && autoInstalledPlugins.includes(p) === false
|
|
)
|
|
);
|
|
|
|
if (isRuntimeSsrPreHydration.value === true) {
|
|
pluginOpts.$q.onSSRHydrated = () => {
|
|
pluginOpts.onSSRHydrated.forEach(fn => { fn(); });
|
|
pluginOpts.$q.onSSRHydrated = () => {};
|
|
};
|
|
}
|
|
}
|
|
|
|
var installQuasar = function (parentApp, opts = {}) {
|
|
const $q = { version: '2.12.5' };
|
|
|
|
if (globalConfigIsFrozen === false) {
|
|
if (opts.config !== void 0) {
|
|
Object.assign(globalConfig, opts.config);
|
|
}
|
|
|
|
$q.config = { ...globalConfig };
|
|
freezeGlobalConfig();
|
|
}
|
|
else {
|
|
$q.config = opts.config || {};
|
|
}
|
|
|
|
prepareApp(parentApp, opts, {
|
|
parentApp,
|
|
$q,
|
|
lang: opts.lang,
|
|
iconSet: opts.iconSet,
|
|
onSSRHydrated: []
|
|
});
|
|
};
|
|
|
|
const createComponent = raw => vue.markRaw(vue.defineComponent(raw));
|
|
const createDirective = raw => vue.markRaw(raw);
|
|
|
|
const units = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB' ];
|
|
|
|
function humanStorageSize (bytes) {
|
|
let u = 0;
|
|
|
|
while (parseInt(bytes, 10) >= 1024 && u < units.length - 1) {
|
|
bytes /= 1024;
|
|
++u;
|
|
}
|
|
|
|
return `${ bytes.toFixed(1) }${ units[ u ] }`
|
|
}
|
|
|
|
function capitalize (str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
}
|
|
|
|
function between (v, min, max) {
|
|
return max <= min
|
|
? min
|
|
: Math.min(max, Math.max(min, v))
|
|
}
|
|
|
|
function normalizeToInterval (v, min, max) {
|
|
if (max <= min) {
|
|
return min
|
|
}
|
|
|
|
const size = (max - min + 1);
|
|
|
|
let index = min + (v - min) % size;
|
|
if (index < min) {
|
|
index = size + index;
|
|
}
|
|
|
|
return index === 0 ? 0 : index // fix for (-a % a) => -0
|
|
}
|
|
|
|
function pad (v, length = 2, char = '0') {
|
|
if (v === void 0 || v === null) {
|
|
return v
|
|
}
|
|
|
|
const val = '' + v;
|
|
return val.length >= length
|
|
? val
|
|
: new Array(length - val.length + 1).join(char) + val
|
|
}
|
|
|
|
var format = {
|
|
humanStorageSize,
|
|
capitalize,
|
|
between,
|
|
normalizeToInterval,
|
|
pad
|
|
};
|
|
|
|
const
|
|
xhr = XMLHttpRequest,
|
|
open = xhr.prototype.open,
|
|
positionValues = [ 'top', 'right', 'bottom', 'left' ];
|
|
|
|
let stack = [];
|
|
let highjackCount = 0;
|
|
|
|
function translate ({ p, pos, active, horiz, reverse, dir }) {
|
|
let x = 1, y = 1;
|
|
|
|
if (horiz === true) {
|
|
if (reverse === true) { x = -1; }
|
|
if (pos === 'bottom') { y = -1; }
|
|
return { transform: `translate3d(${ x * (p - 100) }%,${ active ? 0 : y * -200 }%,0)` }
|
|
}
|
|
|
|
if (reverse === true) { y = -1; }
|
|
if (pos === 'right') { x = -1; }
|
|
return { transform: `translate3d(${ active ? 0 : dir * x * -200 }%,${ y * (p - 100) }%,0)` }
|
|
}
|
|
|
|
function inc (p, amount) {
|
|
if (typeof amount !== 'number') {
|
|
if (p < 25) {
|
|
amount = Math.random() * 3 + 3;
|
|
}
|
|
else if (p < 65) {
|
|
amount = Math.random() * 3;
|
|
}
|
|
else if (p < 85) {
|
|
amount = Math.random() * 2;
|
|
}
|
|
else if (p < 99) {
|
|
amount = 0.6;
|
|
}
|
|
else {
|
|
amount = 0;
|
|
}
|
|
}
|
|
return between(p + amount, 0, 100)
|
|
}
|
|
|
|
function highjackAjax (stackEntry) {
|
|
highjackCount++;
|
|
|
|
stack.push(stackEntry);
|
|
|
|
if (highjackCount > 1) { return }
|
|
|
|
xhr.prototype.open = function (_, url) {
|
|
const stopStack = [];
|
|
|
|
const loadStart = () => {
|
|
stack.forEach(entry => {
|
|
if (
|
|
entry.hijackFilter.value === null
|
|
|| (entry.hijackFilter.value(url) === true)
|
|
) {
|
|
entry.start();
|
|
stopStack.push(entry.stop);
|
|
}
|
|
});
|
|
};
|
|
|
|
const loadEnd = () => {
|
|
stopStack.forEach(stop => { stop(); });
|
|
};
|
|
|
|
this.addEventListener('loadstart', loadStart, { once: true });
|
|
this.addEventListener('loadend', loadEnd, { once: true });
|
|
|
|
open.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
function restoreAjax (start) {
|
|
stack = stack.filter(entry => entry.start !== start);
|
|
|
|
highjackCount = Math.max(0, highjackCount - 1);
|
|
if (highjackCount === 0) {
|
|
xhr.prototype.open = open;
|
|
}
|
|
}
|
|
|
|
var QAjaxBar = createComponent({
|
|
name: 'QAjaxBar',
|
|
|
|
props: {
|
|
position: {
|
|
type: String,
|
|
default: 'top',
|
|
validator: val => positionValues.includes(val)
|
|
},
|
|
|
|
size: {
|
|
type: String,
|
|
default: '2px'
|
|
},
|
|
|
|
color: String,
|
|
skipHijack: Boolean,
|
|
reverse: Boolean,
|
|
|
|
hijackFilter: Function
|
|
},
|
|
|
|
emits: [ 'start', 'stop' ],
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const progress = vue.ref(0);
|
|
const onScreen = vue.ref(false);
|
|
const animate = vue.ref(true);
|
|
|
|
let sessions = 0, timer = null, speed;
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-loading-bar q-loading-bar--${ props.position }`
|
|
+ (props.color !== void 0 ? ` bg-${ props.color }` : '')
|
|
+ (animate.value === true ? '' : ' no-transition')
|
|
);
|
|
|
|
const horizontal = vue.computed(() => props.position === 'top' || props.position === 'bottom');
|
|
const sizeProp = vue.computed(() => (horizontal.value === true ? 'height' : 'width'));
|
|
|
|
const style = vue.computed(() => {
|
|
const active = onScreen.value;
|
|
|
|
const obj = translate({
|
|
p: progress.value,
|
|
pos: props.position,
|
|
active,
|
|
horiz: horizontal.value,
|
|
reverse: proxy.$q.lang.rtl === true && [ 'top', 'bottom' ].includes(props.position)
|
|
? props.reverse === false
|
|
: props.reverse,
|
|
dir: proxy.$q.lang.rtl === true ? -1 : 1
|
|
});
|
|
|
|
obj[ sizeProp.value ] = props.size;
|
|
obj.opacity = active ? 1 : 0;
|
|
|
|
return obj
|
|
});
|
|
|
|
const attributes = vue.computed(() => (
|
|
onScreen.value === true
|
|
? {
|
|
role: 'progressbar',
|
|
'aria-valuemin': 0,
|
|
'aria-valuemax': 100,
|
|
'aria-valuenow': progress.value
|
|
}
|
|
: { 'aria-hidden': 'true' }
|
|
));
|
|
|
|
function start (newSpeed = 300) {
|
|
const oldSpeed = speed;
|
|
speed = Math.max(0, newSpeed) || 0;
|
|
|
|
sessions++;
|
|
|
|
if (sessions > 1) {
|
|
if (oldSpeed === 0 && newSpeed > 0) {
|
|
planNextStep();
|
|
}
|
|
else if (timer !== null && oldSpeed > 0 && newSpeed <= 0) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
return sessions
|
|
}
|
|
|
|
timer !== null && clearTimeout(timer);
|
|
emit('start');
|
|
|
|
progress.value = 0;
|
|
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
animate.value = true;
|
|
newSpeed > 0 && planNextStep();
|
|
}, onScreen.value === true ? 500 : 1);
|
|
|
|
if (onScreen.value !== true) {
|
|
onScreen.value = true;
|
|
animate.value = false;
|
|
}
|
|
|
|
return sessions
|
|
}
|
|
|
|
function increment (amount) {
|
|
if (sessions > 0) {
|
|
progress.value = inc(progress.value, amount);
|
|
}
|
|
|
|
return sessions
|
|
}
|
|
|
|
function stop () {
|
|
sessions = Math.max(0, sessions - 1);
|
|
if (sessions > 0) {
|
|
return sessions
|
|
}
|
|
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
emit('stop');
|
|
|
|
const end = () => {
|
|
animate.value = true;
|
|
progress.value = 100;
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
onScreen.value = false;
|
|
}, 1000);
|
|
};
|
|
|
|
if (progress.value === 0) {
|
|
timer = setTimeout(end, 1);
|
|
}
|
|
else {
|
|
end();
|
|
}
|
|
|
|
return sessions
|
|
}
|
|
|
|
function planNextStep () {
|
|
if (progress.value < 100) {
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
increment();
|
|
planNextStep();
|
|
}, speed);
|
|
}
|
|
}
|
|
|
|
let hijacked;
|
|
|
|
vue.onMounted(() => {
|
|
if (props.skipHijack !== true) {
|
|
hijacked = true;
|
|
highjackAjax({
|
|
start,
|
|
stop,
|
|
hijackFilter: vue.computed(() => props.hijackFilter || null)
|
|
});
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
timer !== null && clearTimeout(timer);
|
|
hijacked === true && restoreAjax(start);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { start, stop, increment });
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
...attributes.value
|
|
})
|
|
}
|
|
});
|
|
|
|
const useSizeDefaults = {
|
|
xs: 18,
|
|
sm: 24,
|
|
md: 32,
|
|
lg: 38,
|
|
xl: 46
|
|
};
|
|
|
|
const useSizeProps = {
|
|
size: String
|
|
};
|
|
|
|
function useSize (props, sizes = useSizeDefaults) {
|
|
// return sizeStyle
|
|
return vue.computed(() => (
|
|
props.size !== void 0
|
|
? { fontSize: props.size in sizes ? `${ sizes[ props.size ] }px` : props.size }
|
|
: null
|
|
))
|
|
}
|
|
|
|
function hSlot (slot, otherwise) {
|
|
return slot !== void 0
|
|
? slot() || otherwise
|
|
: otherwise
|
|
}
|
|
|
|
function hUniqueSlot (slot, otherwise) {
|
|
if (slot !== void 0) {
|
|
const vnode = slot();
|
|
if (vnode !== void 0 && vnode !== null) {
|
|
return vnode.slice()
|
|
}
|
|
}
|
|
|
|
return otherwise
|
|
}
|
|
|
|
/**
|
|
* Source definitely exists,
|
|
* so it's merged with the possible slot
|
|
*/
|
|
function hMergeSlot (slot, source) {
|
|
return slot !== void 0
|
|
? source.concat(slot())
|
|
: source
|
|
}
|
|
|
|
/**
|
|
* Merge with possible slot,
|
|
* even if source might not exist
|
|
*/
|
|
function hMergeSlotSafely (slot, source) {
|
|
if (slot === void 0) {
|
|
return source
|
|
}
|
|
|
|
return source !== void 0
|
|
? source.concat(slot())
|
|
: slot()
|
|
}
|
|
|
|
/*
|
|
* (String) key - unique vnode key
|
|
* (Boolean) condition - should change ONLY when adding/removing directive
|
|
*/
|
|
function hDir (
|
|
tag,
|
|
data,
|
|
children,
|
|
key,
|
|
condition,
|
|
getDirsFn
|
|
) {
|
|
data.key = key + condition;
|
|
|
|
const vnode = vue.h(tag, data, children);
|
|
|
|
return condition === true
|
|
? vue.withDirectives(vnode, getDirsFn())
|
|
: vnode
|
|
}
|
|
|
|
const defaultViewBox = '0 0 24 24';
|
|
|
|
const sameFn = i => i;
|
|
const ionFn = i => `ionicons ${ i }`;
|
|
|
|
const libMap = {
|
|
'mdi-': i => `mdi ${ i }`,
|
|
'icon-': sameFn, // fontawesome equiv
|
|
'bt-': i => `bt ${ i }`,
|
|
'eva-': i => `eva ${ i }`,
|
|
'ion-md': ionFn,
|
|
'ion-ios': ionFn,
|
|
'ion-logo': ionFn,
|
|
'iconfont ': sameFn,
|
|
'ti-': i => `themify-icon ${ i }`,
|
|
'bi-': i => `bootstrap-icons ${ i }`
|
|
};
|
|
|
|
const matMap = {
|
|
o_: '-outlined',
|
|
r_: '-round',
|
|
s_: '-sharp'
|
|
};
|
|
|
|
const symMap = {
|
|
sym_o_: '-outlined',
|
|
sym_r_: '-rounded',
|
|
sym_s_: '-sharp'
|
|
};
|
|
|
|
const libRE = new RegExp('^(' + Object.keys(libMap).join('|') + ')');
|
|
const matRE = new RegExp('^(' + Object.keys(matMap).join('|') + ')');
|
|
const symRE = new RegExp('^(' + Object.keys(symMap).join('|') + ')');
|
|
const mRE = /^[Mm]\s?[-+]?\.?\d/;
|
|
const imgRE = /^img:/;
|
|
const svgUseRE = /^svguse:/;
|
|
const ionRE = /^ion-/;
|
|
const faRE = /^(fa-(sharp|solid|regular|light|brands|duotone|thin)|[lf]a[srlbdk]?) /;
|
|
|
|
var QIcon = createComponent({
|
|
name: 'QIcon',
|
|
|
|
props: {
|
|
...useSizeProps,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'i'
|
|
},
|
|
|
|
name: String,
|
|
color: String,
|
|
left: Boolean,
|
|
right: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const sizeStyle = useSize(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-icon'
|
|
+ (props.left === true ? ' on-left' : '') // TODO Qv3: drop this
|
|
+ (props.right === true ? ' on-right' : '')
|
|
+ (props.color !== void 0 ? ` text-${ props.color }` : '')
|
|
);
|
|
|
|
const type = vue.computed(() => {
|
|
let cls;
|
|
let icon = props.name;
|
|
|
|
if (icon === 'none' || !icon) {
|
|
return { none: true }
|
|
}
|
|
|
|
if ($q.iconMapFn !== null) {
|
|
const res = $q.iconMapFn(icon);
|
|
if (res !== void 0) {
|
|
if (res.icon !== void 0) {
|
|
icon = res.icon;
|
|
if (icon === 'none' || !icon) {
|
|
return { none: true }
|
|
}
|
|
}
|
|
else {
|
|
return {
|
|
cls: res.cls,
|
|
content: res.content !== void 0
|
|
? res.content
|
|
: ' '
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mRE.test(icon) === true) {
|
|
const [ def, viewBox = defaultViewBox ] = icon.split('|');
|
|
|
|
return {
|
|
svg: true,
|
|
viewBox,
|
|
nodes: def.split('&&').map(path => {
|
|
const [ d, style, transform ] = path.split('@@');
|
|
return vue.h('path', { style, d, transform })
|
|
})
|
|
}
|
|
}
|
|
|
|
if (imgRE.test(icon) === true) {
|
|
return {
|
|
img: true,
|
|
src: icon.substring(4)
|
|
}
|
|
}
|
|
|
|
if (svgUseRE.test(icon) === true) {
|
|
const [ def, viewBox = defaultViewBox ] = icon.split('|');
|
|
|
|
return {
|
|
svguse: true,
|
|
src: def.substring(7),
|
|
viewBox
|
|
}
|
|
}
|
|
|
|
let content = ' ';
|
|
const matches = icon.match(libRE);
|
|
|
|
if (matches !== null) {
|
|
cls = libMap[ matches[ 1 ] ](icon);
|
|
}
|
|
else if (faRE.test(icon) === true) {
|
|
cls = icon;
|
|
}
|
|
else if (ionRE.test(icon) === true) {
|
|
cls = `ionicons ion-${ $q.platform.is.ios === true ? 'ios' : 'md' }${ icon.substring(3) }`;
|
|
}
|
|
else if (symRE.test(icon) === true) {
|
|
// "notranslate" class is for Google Translate
|
|
// to avoid tampering with Material Symbols ligature font
|
|
//
|
|
// Caution: To be able to add suffix to the class name,
|
|
// keep the 'material-symbols' at the end of the string.
|
|
cls = 'notranslate material-symbols';
|
|
|
|
const matches = icon.match(symRE);
|
|
if (matches !== null) {
|
|
icon = icon.substring(6);
|
|
cls += symMap[ matches[ 1 ] ];
|
|
}
|
|
|
|
content = icon;
|
|
}
|
|
else {
|
|
// "notranslate" class is for Google Translate
|
|
// to avoid tampering with Material Icons ligature font
|
|
//
|
|
// Caution: To be able to add suffix to the class name,
|
|
// keep the 'material-icons' at the end of the string.
|
|
cls = 'notranslate material-icons';
|
|
|
|
const matches = icon.match(matRE);
|
|
if (matches !== null) {
|
|
icon = icon.substring(2);
|
|
cls += matMap[ matches[ 1 ] ];
|
|
}
|
|
|
|
content = icon;
|
|
}
|
|
|
|
return {
|
|
cls,
|
|
content
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
const data = {
|
|
class: classes.value,
|
|
style: sizeStyle.value,
|
|
'aria-hidden': 'true',
|
|
role: 'presentation'
|
|
};
|
|
|
|
if (type.value.none === true) {
|
|
return vue.h(props.tag, data, hSlot(slots.default))
|
|
}
|
|
|
|
if (type.value.img === true) {
|
|
return vue.h('span', data, hMergeSlot(slots.default, [
|
|
vue.h('img', { src: type.value.src })
|
|
]))
|
|
}
|
|
|
|
if (type.value.svg === true) {
|
|
return vue.h('span', data, hMergeSlot(slots.default, [
|
|
vue.h('svg', {
|
|
viewBox: type.value.viewBox || '0 0 24 24'
|
|
}, type.value.nodes)
|
|
]))
|
|
}
|
|
|
|
if (type.value.svguse === true) {
|
|
return vue.h('span', data, hMergeSlot(slots.default, [
|
|
vue.h('svg', {
|
|
viewBox: type.value.viewBox
|
|
}, [
|
|
vue.h('use', { 'xlink:href': type.value.src })
|
|
])
|
|
]))
|
|
}
|
|
|
|
if (type.value.cls !== void 0) {
|
|
data.class += ' ' + type.value.cls;
|
|
}
|
|
|
|
return vue.h(props.tag, data, hMergeSlot(slots.default, [
|
|
type.value.content
|
|
]))
|
|
}
|
|
}
|
|
});
|
|
|
|
var QAvatar = createComponent({
|
|
name: 'QAvatar',
|
|
|
|
props: {
|
|
...useSizeProps,
|
|
|
|
fontSize: String,
|
|
|
|
color: String,
|
|
textColor: String,
|
|
|
|
icon: String,
|
|
square: Boolean,
|
|
rounded: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const sizeStyle = useSize(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-avatar'
|
|
+ (props.color ? ` bg-${ props.color }` : '')
|
|
+ (props.textColor ? ` text-${ props.textColor } q-chip--colored` : '')
|
|
+ (
|
|
props.square === true
|
|
? ' q-avatar--square'
|
|
: (props.rounded === true ? ' rounded-borders' : '')
|
|
)
|
|
);
|
|
|
|
const contentStyle = vue.computed(() => (
|
|
props.fontSize
|
|
? { fontSize: props.fontSize }
|
|
: null
|
|
));
|
|
|
|
return () => {
|
|
const icon = props.icon !== void 0
|
|
? [ vue.h(QIcon, { name: props.icon }) ]
|
|
: void 0;
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: sizeStyle.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-avatar__content row flex-center overflow-hidden',
|
|
style: contentStyle.value
|
|
}, hMergeSlotSafely(slots.default, icon))
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
const alignValues$3 = [ 'top', 'middle', 'bottom' ];
|
|
|
|
var QBadge = createComponent({
|
|
name: 'QBadge',
|
|
|
|
props: {
|
|
color: String,
|
|
textColor: String,
|
|
|
|
floating: Boolean,
|
|
transparent: Boolean,
|
|
multiLine: Boolean,
|
|
outline: Boolean,
|
|
rounded: Boolean,
|
|
|
|
label: [ Number, String ],
|
|
|
|
align: {
|
|
type: String,
|
|
validator: v => alignValues$3.includes(v)
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const style = vue.computed(() => {
|
|
return props.align !== void 0
|
|
? { verticalAlign: props.align }
|
|
: null
|
|
});
|
|
|
|
const classes = vue.computed(() => {
|
|
const text = props.outline === true
|
|
? props.color || props.textColor
|
|
: props.textColor;
|
|
|
|
return 'q-badge flex inline items-center no-wrap'
|
|
+ ` q-badge--${ props.multiLine === true ? 'multi' : 'single' }-line`
|
|
+ (props.outline === true
|
|
? ' q-badge--outline'
|
|
: (props.color !== void 0 ? ` bg-${ props.color }` : '')
|
|
)
|
|
+ (text !== void 0 ? ` text-${ text }` : '')
|
|
+ (props.floating === true ? ' q-badge--floating' : '')
|
|
+ (props.rounded === true ? ' q-badge--rounded' : '')
|
|
+ (props.transparent === true ? ' q-badge--transparent' : '')
|
|
});
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
role: 'status',
|
|
'aria-label': props.label
|
|
}, hMergeSlot(slots.default, props.label !== void 0 ? [ props.label ] : []))
|
|
}
|
|
});
|
|
|
|
const useDarkProps = {
|
|
dark: {
|
|
type: Boolean,
|
|
default: null
|
|
}
|
|
};
|
|
|
|
function useDark (props, $q) {
|
|
// return isDark
|
|
return vue.computed(() => (
|
|
props.dark === null
|
|
? $q.dark.isActive
|
|
: props.dark
|
|
))
|
|
}
|
|
|
|
var QBanner = createComponent({
|
|
name: 'QBanner',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
inlineActions: Boolean,
|
|
dense: Boolean,
|
|
rounded: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-banner row items-center'
|
|
+ (props.dense === true ? ' q-banner--dense' : '')
|
|
+ (isDark.value === true ? ' q-banner--dark q-dark' : '')
|
|
+ (props.rounded === true ? ' rounded-borders' : '')
|
|
);
|
|
|
|
const actionClass = vue.computed(() =>
|
|
'q-banner__actions row items-center justify-end'
|
|
+ ` col-${ props.inlineActions === true ? 'auto' : 'all' }`
|
|
);
|
|
|
|
return () => {
|
|
const child = [
|
|
vue.h('div', {
|
|
class: 'q-banner__avatar col-auto row items-center self-start'
|
|
}, hSlot(slots.avatar)),
|
|
|
|
vue.h('div', {
|
|
class: 'q-banner__content col text-body2'
|
|
}, hSlot(slots.default))
|
|
];
|
|
|
|
const actions = hSlot(slots.action);
|
|
actions !== void 0 && child.push(
|
|
vue.h('div', { class: actionClass.value }, actions)
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value
|
|
+ (props.inlineActions === false && actions !== void 0 ? ' q-banner--top-padding' : ''),
|
|
role: 'alert'
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QBar = createComponent({
|
|
name: 'QBar',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
dense: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-bar row no-wrap items-center'
|
|
+ ` q-bar--${ props.dense === true ? 'dense' : 'standard' } `
|
|
+ ` q-bar--${ isDark.value === true ? 'dark' : 'light' }`
|
|
);
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
role: 'toolbar'
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
const alignMap = {
|
|
left: 'start',
|
|
center: 'center',
|
|
right: 'end',
|
|
between: 'between',
|
|
around: 'around',
|
|
evenly: 'evenly',
|
|
stretch: 'stretch'
|
|
};
|
|
|
|
const alignValues$2 = Object.keys(alignMap);
|
|
|
|
const useAlignProps = {
|
|
align: {
|
|
type: String,
|
|
validator: v => alignValues$2.includes(v)
|
|
}
|
|
};
|
|
|
|
function useAlign (props) {
|
|
// return alignClass
|
|
return vue.computed(() => {
|
|
const align = props.align === void 0
|
|
? props.vertical === true ? 'stretch' : 'left'
|
|
: props.align;
|
|
|
|
return `${ props.vertical === true ? 'items' : 'justify' }-${ alignMap[ align ] }`
|
|
})
|
|
}
|
|
|
|
// copied to docs too
|
|
function getParentProxy (proxy) {
|
|
if (Object(proxy.$parent) === proxy.$parent) {
|
|
return proxy.$parent
|
|
}
|
|
|
|
let { parent } = proxy.$;
|
|
|
|
while (Object(parent) === parent) {
|
|
if (Object(parent.proxy) === parent.proxy) {
|
|
return parent.proxy
|
|
}
|
|
|
|
parent = parent.parent;
|
|
}
|
|
}
|
|
|
|
function fillNormalizedVNodes (children, vnode) {
|
|
if (typeof vnode.type === 'symbol') {
|
|
if (Array.isArray(vnode.children) === true) {
|
|
vnode.children.forEach(child => {
|
|
fillNormalizedVNodes(children, child);
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
children.add(vnode);
|
|
}
|
|
}
|
|
|
|
// vnodes from rendered in advanced slots
|
|
function getNormalizedVNodes (vnodes) {
|
|
const children = new Set();
|
|
|
|
vnodes.forEach(vnode => {
|
|
fillNormalizedVNodes(children, vnode);
|
|
});
|
|
|
|
return Array.from(children)
|
|
}
|
|
|
|
function vmHasRouter (vm) {
|
|
return vm.appContext.config.globalProperties.$router !== void 0
|
|
}
|
|
|
|
function vmIsDestroyed (vm) {
|
|
return vm.isUnmounted === true || vm.isDeactivated === true
|
|
}
|
|
|
|
const disabledValues = [ '', true ];
|
|
|
|
var QBreadcrumbs = createComponent({
|
|
name: 'QBreadcrumbs',
|
|
|
|
props: {
|
|
...useAlignProps,
|
|
|
|
separator: {
|
|
type: String,
|
|
default: '/'
|
|
},
|
|
separatorColor: String,
|
|
|
|
activeColor: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
|
|
gutter: {
|
|
type: String,
|
|
validator: v => [ 'none', 'xs', 'sm', 'md', 'lg', 'xl' ].includes(v),
|
|
default: 'sm'
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const alignClass = useAlign(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
`flex items-center ${ alignClass.value }${ props.gutter === 'none' ? '' : ` q-gutter-${ props.gutter }` }`
|
|
);
|
|
|
|
const sepClass = vue.computed(() => (props.separatorColor ? ` text-${ props.separatorColor }` : ''));
|
|
const activeClass = vue.computed(() => ` text-${ props.activeColor }`);
|
|
|
|
return () => {
|
|
const vnodes = getNormalizedVNodes(
|
|
hSlot(slots.default)
|
|
);
|
|
|
|
if (vnodes.length === 0) { return }
|
|
|
|
let els = 1;
|
|
|
|
const
|
|
child = [],
|
|
len = vnodes.filter(c => c.type !== void 0 && c.type.name === 'QBreadcrumbsEl').length,
|
|
separator = slots.separator !== void 0
|
|
? slots.separator
|
|
: () => props.separator;
|
|
|
|
vnodes.forEach(comp => {
|
|
if (comp.type !== void 0 && comp.type.name === 'QBreadcrumbsEl') {
|
|
const middle = els < len;
|
|
const disabled = comp.props !== null && disabledValues.includes(comp.props.disable);
|
|
const cls = (middle === true ? '' : ' q-breadcrumbs--last')
|
|
+ (disabled !== true && middle === true ? activeClass.value : '');
|
|
|
|
els++;
|
|
|
|
child.push(
|
|
vue.h('div', {
|
|
class: `flex items-center${ cls }`
|
|
}, [ comp ])
|
|
);
|
|
|
|
if (middle === true) {
|
|
child.push(
|
|
vue.h('div', {
|
|
class: 'q-breadcrumbs__separator' + sepClass.value
|
|
}, separator())
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
child.push(comp);
|
|
}
|
|
});
|
|
|
|
return vue.h('div', {
|
|
class: 'q-breadcrumbs'
|
|
}, [
|
|
vue.h('div', { class: classes.value }, child)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Inspired by RouterLink from Vue Router
|
|
* --> API should match!
|
|
*/
|
|
|
|
// Get the original path value of a record by following its aliasOf
|
|
function getOriginalPath (record) {
|
|
return record
|
|
? (
|
|
record.aliasOf
|
|
? record.aliasOf.path
|
|
: record.path
|
|
) : ''
|
|
}
|
|
|
|
function isSameRouteRecord (a, b) {
|
|
// since the original record has an undefined value for aliasOf
|
|
// but all aliases point to the original record, this will always compare
|
|
// the original record
|
|
return (a.aliasOf || a) === (b.aliasOf || b)
|
|
}
|
|
|
|
function includesParams (outer, inner) {
|
|
for (const key in inner) {
|
|
const
|
|
innerValue = inner[ key ],
|
|
outerValue = outer[ key ];
|
|
|
|
if (typeof innerValue === 'string') {
|
|
if (innerValue !== outerValue) {
|
|
return false
|
|
}
|
|
}
|
|
else if (
|
|
Array.isArray(outerValue) === false
|
|
|| outerValue.length !== innerValue.length
|
|
|| innerValue.some((value, i) => value !== outerValue[ i ])
|
|
) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
function isEquivalentArray (a, b) {
|
|
return Array.isArray(b) === true
|
|
? a.length === b.length && a.every((value, i) => value === b[ i ])
|
|
: a.length === 1 && a[ 0 ] === b
|
|
}
|
|
|
|
function isSameRouteLocationParamsValue (a, b) {
|
|
return Array.isArray(a) === true
|
|
? isEquivalentArray(a, b)
|
|
: (
|
|
Array.isArray(b) === true
|
|
? isEquivalentArray(b, a)
|
|
: a === b
|
|
)
|
|
}
|
|
|
|
function isSameRouteLocationParams (a, b) {
|
|
if (Object.keys(a).length !== Object.keys(b).length) {
|
|
return false
|
|
}
|
|
|
|
for (const key in a) {
|
|
if (isSameRouteLocationParamsValue(a[ key ], b[ key ]) === false) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
const useRouterLinkProps = {
|
|
// router-link
|
|
to: [ String, Object ],
|
|
replace: Boolean,
|
|
exact: Boolean,
|
|
activeClass: {
|
|
type: String,
|
|
default: 'q-router-link--active'
|
|
},
|
|
exactActiveClass: {
|
|
type: String,
|
|
default: 'q-router-link--exact-active'
|
|
},
|
|
|
|
// regular <a> link
|
|
href: String,
|
|
target: String,
|
|
|
|
// state
|
|
disable: Boolean
|
|
};
|
|
|
|
// external props: type, tag
|
|
|
|
function useRouterLink ({ fallbackTag, useDisableForRouterLinkProps = true } = {}) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { props, proxy, emit } = vm;
|
|
|
|
const hasRouter = vmHasRouter(vm);
|
|
const hasHrefLink = vue.computed(() => props.disable !== true && props.href !== void 0);
|
|
|
|
// for perf reasons, we use minimum amount of runtime work
|
|
const hasRouterLinkProps = useDisableForRouterLinkProps === true
|
|
? vue.computed(() =>
|
|
hasRouter === true
|
|
&& props.disable !== true
|
|
&& hasHrefLink.value !== true
|
|
&& props.to !== void 0 && props.to !== null && props.to !== ''
|
|
)
|
|
: vue.computed(() =>
|
|
hasRouter === true
|
|
&& hasHrefLink.value !== true
|
|
&& props.to !== void 0 && props.to !== null && props.to !== ''
|
|
);
|
|
|
|
const resolvedLink = vue.computed(() => (
|
|
hasRouterLinkProps.value === true
|
|
? getLink(props.to)
|
|
: null
|
|
));
|
|
|
|
const hasRouterLink = vue.computed(() => resolvedLink.value !== null);
|
|
const hasLink = vue.computed(() => hasHrefLink.value === true || hasRouterLink.value === true);
|
|
|
|
const linkTag = vue.computed(() => (
|
|
props.type === 'a' || hasLink.value === true
|
|
? 'a'
|
|
: (props.tag || fallbackTag || 'div')
|
|
));
|
|
|
|
const linkAttrs = vue.computed(() => (
|
|
hasHrefLink.value === true
|
|
? {
|
|
href: props.href,
|
|
target: props.target
|
|
}
|
|
: (
|
|
hasRouterLink.value === true
|
|
? {
|
|
href: resolvedLink.value.href,
|
|
target: props.target
|
|
}
|
|
: {}
|
|
)
|
|
));
|
|
|
|
const linkActiveIndex = vue.computed(() => {
|
|
if (hasRouterLink.value === false) {
|
|
return -1
|
|
}
|
|
|
|
const
|
|
{ matched } = resolvedLink.value,
|
|
{ length } = matched,
|
|
routeMatched = matched[ length - 1 ];
|
|
|
|
if (routeMatched === void 0) {
|
|
return -1
|
|
}
|
|
|
|
const currentMatched = proxy.$route.matched;
|
|
|
|
if (currentMatched.length === 0) {
|
|
return -1
|
|
}
|
|
|
|
const index = currentMatched.findIndex(
|
|
isSameRouteRecord.bind(null, routeMatched)
|
|
);
|
|
|
|
if (index > -1) {
|
|
return index
|
|
}
|
|
|
|
// possible parent record
|
|
const parentRecordPath = getOriginalPath(matched[ length - 2 ]);
|
|
|
|
return (
|
|
// we are dealing with nested routes
|
|
length > 1
|
|
// if the parent and matched route have the same path, this link is
|
|
// referring to the empty child. Or we currently are on a different
|
|
// child of the same parent
|
|
&& getOriginalPath(routeMatched) === parentRecordPath
|
|
// avoid comparing the child with its parent
|
|
&& currentMatched[ currentMatched.length - 1 ].path !== parentRecordPath
|
|
? currentMatched.findIndex(
|
|
isSameRouteRecord.bind(null, matched[ length - 2 ])
|
|
)
|
|
: index
|
|
)
|
|
});
|
|
|
|
const linkIsActive = vue.computed(() =>
|
|
hasRouterLink.value === true
|
|
&& linkActiveIndex.value !== -1
|
|
&& includesParams(proxy.$route.params, resolvedLink.value.params)
|
|
);
|
|
|
|
const linkIsExactActive = vue.computed(() =>
|
|
linkIsActive.value === true
|
|
&& linkActiveIndex.value === proxy.$route.matched.length - 1
|
|
&& isSameRouteLocationParams(proxy.$route.params, resolvedLink.value.params)
|
|
);
|
|
|
|
const linkClass = vue.computed(() => (
|
|
hasRouterLink.value === true
|
|
? (
|
|
linkIsExactActive.value === true
|
|
? ` ${ props.exactActiveClass } ${ props.activeClass }`
|
|
: (
|
|
props.exact === true
|
|
? ''
|
|
: (linkIsActive.value === true ? ` ${ props.activeClass }` : '')
|
|
)
|
|
)
|
|
: ''
|
|
));
|
|
|
|
function getLink (to) {
|
|
try { return proxy.$router.resolve(to) }
|
|
catch (_) {}
|
|
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* @returns Promise<RouterError | false | undefined>
|
|
*/
|
|
function navigateToRouterLink (
|
|
e,
|
|
{ returnRouterError, to = props.to, replace = props.replace } = {}
|
|
) {
|
|
if (props.disable === true) {
|
|
// ensure native navigation is prevented in all cases,
|
|
// like when useDisableForRouterLinkProps === false (QRouteTab)
|
|
e.preventDefault();
|
|
return Promise.resolve(false)
|
|
}
|
|
|
|
if (
|
|
// don't redirect with control keys;
|
|
// should match RouterLink from Vue Router
|
|
e.metaKey || e.altKey || e.ctrlKey || e.shiftKey
|
|
|
|
// don't redirect on right click
|
|
|| (e.button !== void 0 && e.button !== 0)
|
|
|
|
// don't redirect if it should open in a new window
|
|
|| props.target === '_blank'
|
|
) {
|
|
return Promise.resolve(false)
|
|
}
|
|
|
|
// hinder the native navigation
|
|
e.preventDefault();
|
|
|
|
// then() can also return a "soft" router error (Vue Router behavior)
|
|
const promise = proxy.$router[ replace === true ? 'replace' : 'push' ](to);
|
|
|
|
return returnRouterError === true
|
|
? promise
|
|
// else catching hard errors and also "soft" ones - then(err => ...)
|
|
: promise.then(() => {}).catch(() => {})
|
|
}
|
|
|
|
// warning! ensure that the component using it has 'click' included in its 'emits' definition prop
|
|
function navigateOnClick (e) {
|
|
if (hasRouterLink.value === true) {
|
|
const go = opts => navigateToRouterLink(e, opts);
|
|
|
|
emit('click', e, go);
|
|
e.defaultPrevented !== true && go();
|
|
}
|
|
else {
|
|
emit('click', e);
|
|
}
|
|
}
|
|
|
|
return {
|
|
hasRouterLink,
|
|
hasHrefLink,
|
|
hasLink,
|
|
|
|
linkTag,
|
|
resolvedLink,
|
|
linkIsActive,
|
|
linkIsExactActive,
|
|
linkClass,
|
|
linkAttrs,
|
|
|
|
getLink,
|
|
navigateToRouterLink,
|
|
navigateOnClick
|
|
}
|
|
}
|
|
|
|
var QBreadcrumbsEl = createComponent({
|
|
name: 'QBreadcrumbsEl',
|
|
|
|
props: {
|
|
...useRouterLinkProps,
|
|
|
|
label: String,
|
|
icon: String,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'span'
|
|
}
|
|
},
|
|
|
|
emits: [ 'click' ],
|
|
|
|
setup (props, { slots }) {
|
|
const { linkTag, linkAttrs, linkClass, navigateOnClick } = useRouterLink();
|
|
|
|
const data = vue.computed(() => {
|
|
return {
|
|
class: 'q-breadcrumbs__el q-link '
|
|
+ 'flex inline items-center relative-position '
|
|
+ (props.disable !== true ? 'q-link--focusable' + linkClass.value : 'q-breadcrumbs__el--disable'),
|
|
...linkAttrs.value,
|
|
onClick: navigateOnClick
|
|
}
|
|
});
|
|
|
|
const iconClass = vue.computed(() =>
|
|
'q-breadcrumbs__el-icon'
|
|
+ (props.label !== void 0 ? ' q-breadcrumbs__el-icon--with-label' : '')
|
|
);
|
|
|
|
return () => {
|
|
const child = [];
|
|
|
|
props.icon !== void 0 && child.push(
|
|
vue.h(QIcon, {
|
|
class: iconClass.value,
|
|
name: props.icon
|
|
})
|
|
);
|
|
|
|
props.label !== void 0 && child.push(props.label);
|
|
|
|
return vue.h(
|
|
linkTag.value,
|
|
{ ...data.value },
|
|
hMergeSlot(slots.default, child)
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
const useSpinnerProps = {
|
|
size: {
|
|
type: [ Number, String ],
|
|
default: '1em'
|
|
},
|
|
color: String
|
|
};
|
|
|
|
function useSpinner (props) {
|
|
return {
|
|
cSize: vue.computed(() => (
|
|
props.size in useSizeDefaults
|
|
? `${ useSizeDefaults[ props.size ] }px`
|
|
: props.size
|
|
)),
|
|
|
|
classes: vue.computed(() =>
|
|
'q-spinner' + (props.color ? ` text-${ props.color }` : '')
|
|
)
|
|
}
|
|
}
|
|
|
|
var QSpinner = createComponent({
|
|
name: 'QSpinner',
|
|
|
|
props: {
|
|
...useSpinnerProps,
|
|
|
|
thickness: {
|
|
type: Number,
|
|
default: 5
|
|
}
|
|
},
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value + ' q-spinner-mat',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '25 25 50 50'
|
|
}, [
|
|
vue.h('circle', {
|
|
class: 'path',
|
|
cx: '50',
|
|
cy: '50',
|
|
r: '20',
|
|
fill: 'none',
|
|
stroke: 'currentColor',
|
|
'stroke-width': props.thickness,
|
|
'stroke-miterlimit': '10'
|
|
})
|
|
])
|
|
}
|
|
});
|
|
|
|
function offset (el) {
|
|
if (el === window) {
|
|
return { top: 0, left: 0 }
|
|
}
|
|
const { top, left } = el.getBoundingClientRect();
|
|
return { top, left }
|
|
}
|
|
|
|
function style (el, property) {
|
|
return window.getComputedStyle(el).getPropertyValue(property)
|
|
}
|
|
|
|
function height (el) {
|
|
return el === window
|
|
? window.innerHeight
|
|
: el.getBoundingClientRect().height
|
|
}
|
|
|
|
function width$1 (el) {
|
|
return el === window
|
|
? window.innerWidth
|
|
: el.getBoundingClientRect().width
|
|
}
|
|
|
|
function css (element, css) {
|
|
const style = element.style;
|
|
|
|
for (const prop in css) {
|
|
style[ prop ] = css[ prop ];
|
|
}
|
|
}
|
|
|
|
function cssBatch (elements, style) {
|
|
elements.forEach(el => css(el, style));
|
|
}
|
|
|
|
function ready (fn) {
|
|
if (typeof fn !== 'function') {
|
|
return
|
|
}
|
|
|
|
if (document.readyState !== 'loading') {
|
|
return fn()
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', fn, false);
|
|
}
|
|
|
|
// internal
|
|
function getElement$1 (el) {
|
|
if (el === void 0 || el === null) {
|
|
return void 0
|
|
}
|
|
|
|
if (typeof el === 'string') {
|
|
try {
|
|
return document.querySelector(el) || void 0
|
|
}
|
|
catch (err) {
|
|
return void 0
|
|
}
|
|
}
|
|
|
|
const target = vue.unref(el);
|
|
if (target) {
|
|
return target.$el || target
|
|
}
|
|
}
|
|
|
|
// internal
|
|
function childHasFocus (el, focusedEl) {
|
|
if (el === void 0 || el === null || el.contains(focusedEl) === true) {
|
|
return true
|
|
}
|
|
|
|
for (let next = el.nextElementSibling; next !== null; next = next.nextElementSibling) {
|
|
if (next.contains(focusedEl)) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
var dom = {
|
|
offset,
|
|
style,
|
|
height,
|
|
width: width$1,
|
|
css,
|
|
cssBatch,
|
|
ready
|
|
};
|
|
|
|
function throttle (fn, limit = 250) {
|
|
let wait = false, result;
|
|
|
|
return function (/* ...args */) {
|
|
if (wait === false) {
|
|
wait = true;
|
|
setTimeout(() => { wait = false; }, limit);
|
|
result = fn.apply(this, arguments);
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
function showRipple (evt, el, ctx, forceCenter) {
|
|
ctx.modifiers.stop === true && stop(evt);
|
|
|
|
const color = ctx.modifiers.color;
|
|
let center = ctx.modifiers.center;
|
|
center = center === true || forceCenter === true;
|
|
|
|
const
|
|
node = document.createElement('span'),
|
|
innerNode = document.createElement('span'),
|
|
pos = position(evt),
|
|
{ left, top, width, height } = el.getBoundingClientRect(),
|
|
diameter = Math.sqrt(width * width + height * height),
|
|
radius = diameter / 2,
|
|
centerX = `${ (width - diameter) / 2 }px`,
|
|
x = center ? centerX : `${ pos.left - left - radius }px`,
|
|
centerY = `${ (height - diameter) / 2 }px`,
|
|
y = center ? centerY : `${ pos.top - top - radius }px`;
|
|
|
|
innerNode.className = 'q-ripple__inner';
|
|
css(innerNode, {
|
|
height: `${ diameter }px`,
|
|
width: `${ diameter }px`,
|
|
transform: `translate3d(${ x },${ y },0) scale3d(.2,.2,1)`,
|
|
opacity: 0
|
|
});
|
|
|
|
node.className = `q-ripple${ color ? ' text-' + color : '' }`;
|
|
node.setAttribute('dir', 'ltr');
|
|
node.appendChild(innerNode);
|
|
el.appendChild(node);
|
|
|
|
const abort = () => {
|
|
node.remove();
|
|
clearTimeout(timer);
|
|
};
|
|
ctx.abort.push(abort);
|
|
|
|
let timer = setTimeout(() => {
|
|
innerNode.classList.add('q-ripple__inner--enter');
|
|
innerNode.style.transform = `translate3d(${ centerX },${ centerY },0) scale3d(1,1,1)`;
|
|
innerNode.style.opacity = 0.2;
|
|
|
|
timer = setTimeout(() => {
|
|
innerNode.classList.remove('q-ripple__inner--enter');
|
|
innerNode.classList.add('q-ripple__inner--leave');
|
|
innerNode.style.opacity = 0;
|
|
|
|
timer = setTimeout(() => {
|
|
node.remove();
|
|
ctx.abort.splice(ctx.abort.indexOf(abort), 1);
|
|
}, 275);
|
|
}, 250);
|
|
}, 50);
|
|
}
|
|
|
|
function updateModifiers$1 (ctx, { modifiers, value, arg }) {
|
|
const cfg = Object.assign({}, ctx.cfg.ripple, modifiers, value);
|
|
ctx.modifiers = {
|
|
early: cfg.early === true,
|
|
stop: cfg.stop === true,
|
|
center: cfg.center === true,
|
|
color: cfg.color || arg,
|
|
keyCodes: [].concat(cfg.keyCodes || 13)
|
|
};
|
|
}
|
|
|
|
var Ripple = createDirective({
|
|
name: 'ripple',
|
|
|
|
beforeMount (el, binding) {
|
|
const cfg = binding.instance.$.appContext.config.globalProperties.$q.config || {};
|
|
|
|
if (cfg.ripple === false) {
|
|
return
|
|
}
|
|
|
|
const ctx = {
|
|
cfg,
|
|
enabled: binding.value !== false,
|
|
modifiers: {},
|
|
abort: [],
|
|
|
|
start (evt) {
|
|
if (
|
|
ctx.enabled === true
|
|
&& evt.qSkipRipple !== true
|
|
&& evt.type === (ctx.modifiers.early === true ? 'pointerdown' : 'click')
|
|
) {
|
|
showRipple(evt, el, ctx, evt.qKeyEvent === true);
|
|
}
|
|
},
|
|
|
|
keystart: throttle(evt => {
|
|
if (
|
|
ctx.enabled === true
|
|
&& evt.qSkipRipple !== true
|
|
&& isKeyCode(evt, ctx.modifiers.keyCodes) === true
|
|
&& evt.type === `key${ ctx.modifiers.early === true ? 'down' : 'up' }`
|
|
) {
|
|
showRipple(evt, el, ctx, true);
|
|
}
|
|
}, 300)
|
|
};
|
|
|
|
updateModifiers$1(ctx, binding);
|
|
|
|
el.__qripple = ctx;
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'pointerdown', 'start', 'passive' ],
|
|
[ el, 'click', 'start', 'passive' ],
|
|
[ el, 'keydown', 'keystart', 'passive' ],
|
|
[ el, 'keyup', 'keystart', 'passive' ]
|
|
]);
|
|
},
|
|
|
|
updated (el, binding) {
|
|
if (binding.oldValue !== binding.value) {
|
|
const ctx = el.__qripple;
|
|
if (ctx !== void 0) {
|
|
ctx.enabled = binding.value !== false;
|
|
|
|
if (ctx.enabled === true && Object(binding.value) === binding.value) {
|
|
updateModifiers$1(ctx, binding);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qripple;
|
|
if (ctx !== void 0) {
|
|
ctx.abort.forEach(fn => { fn(); });
|
|
cleanEvt(ctx, 'main');
|
|
delete el._qripple;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
const btnPadding = {
|
|
none: 0,
|
|
xs: 4,
|
|
sm: 8,
|
|
md: 16,
|
|
lg: 24,
|
|
xl: 32
|
|
};
|
|
|
|
const defaultSizes$2 = {
|
|
xs: 8,
|
|
sm: 10,
|
|
md: 14,
|
|
lg: 20,
|
|
xl: 24
|
|
};
|
|
|
|
const formTypes = [ 'button', 'submit', 'reset' ];
|
|
const mediaTypeRE = /[^\s]\/[^\s]/;
|
|
|
|
const btnDesignOptions = [ 'flat', 'outline', 'push', 'unelevated' ];
|
|
const getBtnDesign = (props, defaultValue) => {
|
|
if (props.flat === true) return 'flat'
|
|
if (props.outline === true) return 'outline'
|
|
if (props.push === true) return 'push'
|
|
if (props.unelevated === true) return 'unelevated'
|
|
return defaultValue
|
|
};
|
|
const getBtnDesignAttr = props => {
|
|
const design = getBtnDesign(props);
|
|
return design !== void 0
|
|
? { [ design ]: true }
|
|
: {}
|
|
};
|
|
|
|
const useBtnProps = {
|
|
...useSizeProps,
|
|
...useRouterLinkProps,
|
|
|
|
type: {
|
|
type: String,
|
|
default: 'button'
|
|
},
|
|
|
|
label: [ Number, String ],
|
|
icon: String,
|
|
iconRight: String,
|
|
|
|
...btnDesignOptions.reduce(
|
|
(acc, val) => (acc[ val ] = Boolean) && acc,
|
|
{}
|
|
),
|
|
|
|
square: Boolean,
|
|
round: Boolean,
|
|
rounded: Boolean,
|
|
glossy: Boolean,
|
|
|
|
size: String,
|
|
fab: Boolean,
|
|
fabMini: Boolean,
|
|
padding: String,
|
|
|
|
color: String,
|
|
textColor: String,
|
|
noCaps: Boolean,
|
|
noWrap: Boolean,
|
|
dense: Boolean,
|
|
|
|
tabindex: [ Number, String ],
|
|
|
|
ripple: {
|
|
type: [ Boolean, Object ],
|
|
default: true
|
|
},
|
|
|
|
align: {
|
|
...useAlignProps.align,
|
|
default: 'center'
|
|
},
|
|
stack: Boolean,
|
|
stretch: Boolean,
|
|
loading: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
disable: Boolean
|
|
};
|
|
|
|
function useBtn (props) {
|
|
const sizeStyle = useSize(props, defaultSizes$2);
|
|
const alignClass = useAlign(props);
|
|
const { hasRouterLink, hasLink, linkTag, linkAttrs, navigateOnClick } = useRouterLink({
|
|
fallbackTag: 'button'
|
|
});
|
|
|
|
const style = vue.computed(() => {
|
|
const obj = props.fab === false && props.fabMini === false
|
|
? sizeStyle.value
|
|
: {};
|
|
|
|
return props.padding !== void 0
|
|
? Object.assign({}, obj, {
|
|
padding: props.padding
|
|
.split(/\s+/)
|
|
.map(v => (v in btnPadding ? btnPadding[ v ] + 'px' : v))
|
|
.join(' '),
|
|
minWidth: '0',
|
|
minHeight: '0'
|
|
})
|
|
: obj
|
|
});
|
|
|
|
const isRounded = vue.computed(() =>
|
|
props.rounded === true || props.fab === true || props.fabMini === true
|
|
);
|
|
|
|
const isActionable = vue.computed(() =>
|
|
props.disable !== true && props.loading !== true
|
|
);
|
|
|
|
const tabIndex = vue.computed(() => (
|
|
isActionable.value === true ? props.tabindex || 0 : -1
|
|
));
|
|
|
|
const design = vue.computed(() => getBtnDesign(props, 'standard'));
|
|
|
|
const attributes = vue.computed(() => {
|
|
const acc = { tabindex: tabIndex.value };
|
|
|
|
if (hasLink.value === true) {
|
|
Object.assign(acc, linkAttrs.value);
|
|
}
|
|
else if (formTypes.includes(props.type) === true) {
|
|
acc.type = props.type;
|
|
}
|
|
|
|
if (linkTag.value === 'a') {
|
|
if (props.disable === true) {
|
|
acc[ 'aria-disabled' ] = 'true';
|
|
}
|
|
else if (acc.href === void 0) {
|
|
acc.role = 'button';
|
|
}
|
|
|
|
if (hasRouterLink.value !== true && mediaTypeRE.test(props.type) === true) {
|
|
acc.type = props.type;
|
|
}
|
|
}
|
|
else if (props.disable === true) {
|
|
acc.disabled = '';
|
|
acc[ 'aria-disabled' ] = 'true';
|
|
}
|
|
|
|
if (props.loading === true && props.percentage !== void 0) {
|
|
Object.assign(acc, {
|
|
role: 'progressbar',
|
|
'aria-valuemin': 0,
|
|
'aria-valuemax': 100,
|
|
'aria-valuenow': props.percentage
|
|
});
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
const classes = vue.computed(() => {
|
|
let colors;
|
|
|
|
if (props.color !== void 0) {
|
|
if (props.flat === true || props.outline === true) {
|
|
colors = `text-${ props.textColor || props.color }`;
|
|
}
|
|
else {
|
|
colors = `bg-${ props.color } text-${ props.textColor || 'white' }`;
|
|
}
|
|
}
|
|
else if (props.textColor) {
|
|
colors = `text-${ props.textColor }`;
|
|
}
|
|
|
|
const shape = props.round === true
|
|
? 'round'
|
|
: `rectangle${ isRounded.value === true ? ' q-btn--rounded' : (props.square === true ? ' q-btn--square' : '') }`;
|
|
|
|
return `q-btn--${ design.value } q-btn--${ shape }`
|
|
+ (colors !== void 0 ? ' ' + colors : '')
|
|
+ (isActionable.value === true ? ' q-btn--actionable q-focusable q-hoverable' : (props.disable === true ? ' disabled' : ''))
|
|
+ (props.fab === true ? ' q-btn--fab' : (props.fabMini === true ? ' q-btn--fab-mini' : ''))
|
|
+ (props.noCaps === true ? ' q-btn--no-uppercase' : '')
|
|
+ (props.dense === true ? ' q-btn--dense' : '')
|
|
+ (props.stretch === true ? ' no-border-radius self-stretch' : '')
|
|
+ (props.glossy === true ? ' glossy' : '')
|
|
+ (props.square ? ' q-btn--square' : '')
|
|
});
|
|
|
|
const innerClasses = vue.computed(() =>
|
|
alignClass.value + (props.stack === true ? ' column' : ' row')
|
|
+ (props.noWrap === true ? ' no-wrap text-no-wrap' : '')
|
|
+ (props.loading === true ? ' q-btn__content--hidden' : '')
|
|
);
|
|
|
|
return {
|
|
classes,
|
|
style,
|
|
innerClasses,
|
|
attributes,
|
|
hasLink,
|
|
linkTag,
|
|
navigateOnClick,
|
|
isActionable
|
|
}
|
|
}
|
|
|
|
const { passiveCapture } = listenOpts;
|
|
|
|
let
|
|
touchTarget = null,
|
|
keyboardTarget = null,
|
|
mouseTarget = null;
|
|
|
|
var QBtn = createComponent({
|
|
name: 'QBtn',
|
|
|
|
props: {
|
|
...useBtnProps,
|
|
|
|
percentage: Number,
|
|
darkPercentage: Boolean,
|
|
|
|
onTouchstart: [ Function, Array ]
|
|
},
|
|
|
|
emits: [ 'click', 'keydown', 'mousedown', 'keyup' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const {
|
|
classes, style, innerClasses,
|
|
attributes,
|
|
hasLink, linkTag, navigateOnClick,
|
|
isActionable
|
|
} = useBtn(props);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const blurTargetRef = vue.ref(null);
|
|
|
|
let localTouchTargetEl = null, avoidMouseRipple, mouseTimer = null;
|
|
|
|
const hasLabel = vue.computed(() =>
|
|
props.label !== void 0 && props.label !== null && props.label !== ''
|
|
);
|
|
|
|
const ripple = vue.computed(() => (
|
|
props.disable === true || props.ripple === false
|
|
? false
|
|
: {
|
|
keyCodes: hasLink.value === true ? [ 13, 32 ] : [ 13 ],
|
|
...(props.ripple === true ? {} : props.ripple)
|
|
}
|
|
));
|
|
|
|
const rippleProps = vue.computed(() => ({ center: props.round }));
|
|
|
|
const percentageStyle = vue.computed(() => {
|
|
const val = Math.max(0, Math.min(100, props.percentage));
|
|
return val > 0
|
|
? { transition: 'transform 0.6s', transform: `translateX(${ val - 100 }%)` }
|
|
: {}
|
|
});
|
|
|
|
const onEvents = vue.computed(() => {
|
|
if (props.loading === true) {
|
|
return {
|
|
onMousedown: onLoadingEvt,
|
|
onTouchstart: onLoadingEvt,
|
|
onClick: onLoadingEvt,
|
|
onKeydown: onLoadingEvt,
|
|
onKeyup: onLoadingEvt
|
|
}
|
|
}
|
|
|
|
if (isActionable.value === true) {
|
|
const acc = {
|
|
onClick,
|
|
onKeydown,
|
|
onMousedown
|
|
};
|
|
|
|
if (proxy.$q.platform.has.touch === true) {
|
|
const suffix = props.onTouchstart !== void 0
|
|
? ''
|
|
: 'Passive';
|
|
|
|
acc[ `onTouchstart${ suffix }` ] = onTouchstart;
|
|
}
|
|
|
|
return acc
|
|
}
|
|
|
|
return {
|
|
// needed; especially for disabled <a> tags
|
|
onClick: stopAndPrevent
|
|
}
|
|
});
|
|
|
|
const nodeProps = vue.computed(() => ({
|
|
ref: rootRef,
|
|
class: 'q-btn q-btn-item non-selectable no-outline ' + classes.value,
|
|
style: style.value,
|
|
...attributes.value,
|
|
...onEvents.value
|
|
}));
|
|
|
|
function onClick (e) {
|
|
// is it already destroyed?
|
|
if (rootRef.value === null) { return }
|
|
|
|
if (e !== void 0) {
|
|
if (e.defaultPrevented === true) {
|
|
return
|
|
}
|
|
|
|
const el = document.activeElement;
|
|
// focus button if it came from ENTER on form
|
|
// prevent the new submit (already done)
|
|
if (
|
|
props.type === 'submit'
|
|
&& el !== document.body
|
|
&& rootRef.value.contains(el) === false
|
|
// required for iOS and desktop Safari
|
|
&& el.contains(rootRef.value) === false
|
|
) {
|
|
rootRef.value.focus();
|
|
|
|
const onClickCleanup = () => {
|
|
document.removeEventListener('keydown', stopAndPrevent, true);
|
|
document.removeEventListener('keyup', onClickCleanup, passiveCapture);
|
|
rootRef.value !== null && rootRef.value.removeEventListener('blur', onClickCleanup, passiveCapture);
|
|
};
|
|
|
|
document.addEventListener('keydown', stopAndPrevent, true);
|
|
document.addEventListener('keyup', onClickCleanup, passiveCapture);
|
|
rootRef.value.addEventListener('blur', onClickCleanup, passiveCapture);
|
|
}
|
|
}
|
|
|
|
navigateOnClick(e);
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
// is it already destroyed?
|
|
if (rootRef.value === null) { return }
|
|
|
|
emit('keydown', e);
|
|
|
|
if (isKeyCode(e, [ 13, 32 ]) === true && keyboardTarget !== rootRef.value) {
|
|
keyboardTarget !== null && cleanup();
|
|
|
|
if (e.defaultPrevented !== true) {
|
|
// focus external button if the focus helper was focused before
|
|
rootRef.value.focus();
|
|
|
|
keyboardTarget = rootRef.value;
|
|
rootRef.value.classList.add('q-btn--active');
|
|
document.addEventListener('keyup', onPressEnd, true);
|
|
rootRef.value.addEventListener('blur', onPressEnd, passiveCapture);
|
|
}
|
|
|
|
stopAndPrevent(e);
|
|
}
|
|
}
|
|
|
|
function onTouchstart (e) {
|
|
// is it already destroyed?
|
|
if (rootRef.value === null) { return }
|
|
|
|
emit('touchstart', e);
|
|
|
|
if (e.defaultPrevented === true) { return }
|
|
|
|
if (touchTarget !== rootRef.value) {
|
|
touchTarget !== null && cleanup();
|
|
touchTarget = rootRef.value;
|
|
|
|
localTouchTargetEl = e.target;
|
|
localTouchTargetEl.addEventListener('touchcancel', onPressEnd, passiveCapture);
|
|
localTouchTargetEl.addEventListener('touchend', onPressEnd, passiveCapture);
|
|
}
|
|
|
|
// avoid duplicated mousedown event
|
|
// triggering another early ripple
|
|
avoidMouseRipple = true;
|
|
mouseTimer !== null && clearTimeout(mouseTimer);
|
|
mouseTimer = setTimeout(() => {
|
|
mouseTimer = null;
|
|
avoidMouseRipple = false;
|
|
}, 200);
|
|
}
|
|
|
|
function onMousedown (e) {
|
|
// is it already destroyed?
|
|
if (rootRef.value === null) { return }
|
|
|
|
e.qSkipRipple = avoidMouseRipple === true;
|
|
emit('mousedown', e);
|
|
|
|
if (e.defaultPrevented !== true && mouseTarget !== rootRef.value) {
|
|
mouseTarget !== null && cleanup();
|
|
mouseTarget = rootRef.value;
|
|
rootRef.value.classList.add('q-btn--active');
|
|
document.addEventListener('mouseup', onPressEnd, passiveCapture);
|
|
}
|
|
}
|
|
|
|
function onPressEnd (e) {
|
|
// is it already destroyed?
|
|
if (rootRef.value === null) { return }
|
|
|
|
// needed for IE (because it emits blur when focusing button from focus helper)
|
|
if (e !== void 0 && e.type === 'blur' && document.activeElement === rootRef.value) {
|
|
return
|
|
}
|
|
|
|
if (e !== void 0 && e.type === 'keyup') {
|
|
if (keyboardTarget === rootRef.value && isKeyCode(e, [ 13, 32 ]) === true) {
|
|
// for click trigger
|
|
const evt = new MouseEvent('click', e);
|
|
evt.qKeyEvent = true;
|
|
e.defaultPrevented === true && prevent(evt);
|
|
e.cancelBubble === true && stop(evt);
|
|
rootRef.value.dispatchEvent(evt);
|
|
|
|
stopAndPrevent(e);
|
|
|
|
// for ripple
|
|
e.qKeyEvent = true;
|
|
}
|
|
|
|
emit('keyup', e);
|
|
}
|
|
|
|
cleanup();
|
|
}
|
|
|
|
function cleanup (destroying) {
|
|
const blurTarget = blurTargetRef.value;
|
|
|
|
if (
|
|
destroying !== true
|
|
&& (touchTarget === rootRef.value || mouseTarget === rootRef.value)
|
|
&& blurTarget !== null
|
|
&& blurTarget !== document.activeElement
|
|
) {
|
|
blurTarget.setAttribute('tabindex', -1);
|
|
blurTarget.focus();
|
|
}
|
|
|
|
if (touchTarget === rootRef.value) {
|
|
if (localTouchTargetEl !== null) {
|
|
localTouchTargetEl.removeEventListener('touchcancel', onPressEnd, passiveCapture);
|
|
localTouchTargetEl.removeEventListener('touchend', onPressEnd, passiveCapture);
|
|
}
|
|
touchTarget = localTouchTargetEl = null;
|
|
}
|
|
|
|
if (mouseTarget === rootRef.value) {
|
|
document.removeEventListener('mouseup', onPressEnd, passiveCapture);
|
|
mouseTarget = null;
|
|
}
|
|
|
|
if (keyboardTarget === rootRef.value) {
|
|
document.removeEventListener('keyup', onPressEnd, true);
|
|
rootRef.value !== null && rootRef.value.removeEventListener('blur', onPressEnd, passiveCapture);
|
|
keyboardTarget = null;
|
|
}
|
|
|
|
rootRef.value !== null && rootRef.value.classList.remove('q-btn--active');
|
|
}
|
|
|
|
function onLoadingEvt (evt) {
|
|
stopAndPrevent(evt);
|
|
evt.qSkipRipple = true;
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
cleanup(true);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { click: onClick });
|
|
|
|
return () => {
|
|
let inner = [];
|
|
|
|
props.icon !== void 0 && inner.push(
|
|
vue.h(QIcon, {
|
|
name: props.icon,
|
|
left: props.stack === false && hasLabel.value === true,
|
|
role: 'img',
|
|
'aria-hidden': 'true'
|
|
})
|
|
);
|
|
|
|
hasLabel.value === true && inner.push(
|
|
vue.h('span', { class: 'block' }, [ props.label ])
|
|
);
|
|
|
|
inner = hMergeSlot(slots.default, inner);
|
|
|
|
if (props.iconRight !== void 0 && props.round === false) {
|
|
inner.push(
|
|
vue.h(QIcon, {
|
|
name: props.iconRight,
|
|
right: props.stack === false && hasLabel.value === true,
|
|
role: 'img',
|
|
'aria-hidden': 'true'
|
|
})
|
|
);
|
|
}
|
|
|
|
const child = [
|
|
vue.h('span', {
|
|
class: 'q-focus-helper',
|
|
ref: blurTargetRef
|
|
})
|
|
];
|
|
|
|
if (props.loading === true && props.percentage !== void 0) {
|
|
child.push(
|
|
vue.h('span', {
|
|
class: 'q-btn__progress absolute-full overflow-hidden' + (props.darkPercentage === true ? ' q-btn__progress--dark' : '')
|
|
}, [
|
|
vue.h('span', {
|
|
class: 'q-btn__progress-indicator fit block',
|
|
style: percentageStyle.value
|
|
})
|
|
])
|
|
);
|
|
}
|
|
|
|
child.push(
|
|
vue.h('span', {
|
|
class: 'q-btn__content text-center col items-center q-anchor--skip ' + innerClasses.value
|
|
}, inner)
|
|
);
|
|
|
|
props.loading !== null && child.push(
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--fade'
|
|
}, () => (
|
|
props.loading === true
|
|
? [
|
|
vue.h('span', {
|
|
key: 'loading',
|
|
class: 'absolute-full flex flex-center'
|
|
}, slots.loading !== void 0 ? slots.loading() : [ vue.h(QSpinner) ])
|
|
]
|
|
: null
|
|
))
|
|
);
|
|
|
|
return vue.withDirectives(
|
|
vue.h(
|
|
linkTag.value,
|
|
nodeProps.value,
|
|
child
|
|
),
|
|
[ [
|
|
Ripple,
|
|
ripple.value,
|
|
void 0,
|
|
rippleProps.value
|
|
] ]
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QBtnGroup = createComponent({
|
|
name: 'QBtnGroup',
|
|
|
|
props: {
|
|
unelevated: Boolean,
|
|
outline: Boolean,
|
|
flat: Boolean,
|
|
rounded: Boolean,
|
|
square: Boolean,
|
|
push: Boolean,
|
|
stretch: Boolean,
|
|
glossy: Boolean,
|
|
spread: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() => {
|
|
const cls = [ 'unelevated', 'outline', 'flat', 'rounded', 'square', 'push', 'stretch', 'glossy' ]
|
|
.filter(t => props[ t ] === true)
|
|
.map(t => `q-btn-group--${ t }`).join(' ');
|
|
|
|
return `q-btn-group row no-wrap${ cls.length !== 0 ? ' ' + cls : '' }`
|
|
+ (props.spread === true ? ' q-btn-group--spread' : ' inline')
|
|
});
|
|
|
|
return () => vue.h('div', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
function clearSelection () {
|
|
if (window.getSelection !== void 0) {
|
|
const selection = window.getSelection();
|
|
if (selection.empty !== void 0) {
|
|
selection.empty();
|
|
}
|
|
else if (selection.removeAllRanges !== void 0) {
|
|
selection.removeAllRanges();
|
|
Platform.is.mobile !== true && selection.addRange(document.createRange());
|
|
}
|
|
}
|
|
else if (document.selection !== void 0) {
|
|
document.selection.empty();
|
|
}
|
|
}
|
|
|
|
const useAnchorProps = {
|
|
target: {
|
|
default: true
|
|
},
|
|
noParentEvent: Boolean,
|
|
contextMenu: Boolean
|
|
};
|
|
|
|
function useAnchor ({
|
|
showing,
|
|
avoidEmit, // required for QPopupProxy (true)
|
|
configureAnchorEl // optional
|
|
}) {
|
|
const { props, proxy, emit } = vue.getCurrentInstance();
|
|
|
|
const anchorEl = vue.ref(null);
|
|
|
|
let touchTimer = null;
|
|
|
|
function canShow (evt) {
|
|
// abort with no parent configured or on multi-touch
|
|
return anchorEl.value === null
|
|
? false
|
|
: (evt === void 0 || evt.touches === void 0 || evt.touches.length <= 1)
|
|
}
|
|
|
|
const anchorEvents = {};
|
|
|
|
if (configureAnchorEl === void 0) {
|
|
// default configureAnchorEl is designed for
|
|
// QMenu & QPopupProxy (which is why it's handled here)
|
|
|
|
Object.assign(anchorEvents, {
|
|
hide (evt) {
|
|
proxy.hide(evt);
|
|
},
|
|
|
|
toggle (evt) {
|
|
proxy.toggle(evt);
|
|
evt.qAnchorHandled = true;
|
|
},
|
|
|
|
toggleKey (evt) {
|
|
isKeyCode(evt, 13) === true && anchorEvents.toggle(evt);
|
|
},
|
|
|
|
contextClick (evt) {
|
|
proxy.hide(evt);
|
|
prevent(evt);
|
|
vue.nextTick(() => {
|
|
proxy.show(evt);
|
|
evt.qAnchorHandled = true;
|
|
});
|
|
},
|
|
|
|
prevent,
|
|
|
|
mobileTouch (evt) {
|
|
anchorEvents.mobileCleanup(evt);
|
|
|
|
if (canShow(evt) !== true) {
|
|
return
|
|
}
|
|
|
|
proxy.hide(evt);
|
|
anchorEl.value.classList.add('non-selectable');
|
|
|
|
const target = evt.target;
|
|
addEvt(anchorEvents, 'anchor', [
|
|
[ target, 'touchmove', 'mobileCleanup', 'passive' ],
|
|
[ target, 'touchend', 'mobileCleanup', 'passive' ],
|
|
[ target, 'touchcancel', 'mobileCleanup', 'passive' ],
|
|
[ anchorEl.value, 'contextmenu', 'prevent', 'notPassive' ]
|
|
]);
|
|
|
|
touchTimer = setTimeout(() => {
|
|
touchTimer = null;
|
|
proxy.show(evt);
|
|
evt.qAnchorHandled = true;
|
|
}, 300);
|
|
},
|
|
|
|
mobileCleanup (evt) {
|
|
anchorEl.value.classList.remove('non-selectable');
|
|
|
|
if (touchTimer !== null) {
|
|
clearTimeout(touchTimer);
|
|
touchTimer = null;
|
|
}
|
|
|
|
if (showing.value === true && evt !== void 0) {
|
|
clearSelection();
|
|
}
|
|
}
|
|
});
|
|
|
|
configureAnchorEl = function (context = props.contextMenu) {
|
|
if (props.noParentEvent === true || anchorEl.value === null) { return }
|
|
|
|
let evts;
|
|
|
|
if (context === true) {
|
|
if (proxy.$q.platform.is.mobile === true) {
|
|
evts = [
|
|
[ anchorEl.value, 'touchstart', 'mobileTouch', 'passive' ]
|
|
];
|
|
}
|
|
else {
|
|
evts = [
|
|
[ anchorEl.value, 'mousedown', 'hide', 'passive' ],
|
|
[ anchorEl.value, 'contextmenu', 'contextClick', 'notPassive' ]
|
|
];
|
|
}
|
|
}
|
|
else {
|
|
evts = [
|
|
[ anchorEl.value, 'click', 'toggle', 'passive' ],
|
|
[ anchorEl.value, 'keyup', 'toggleKey', 'passive' ]
|
|
];
|
|
}
|
|
|
|
addEvt(anchorEvents, 'anchor', evts);
|
|
};
|
|
}
|
|
|
|
function unconfigureAnchorEl () {
|
|
cleanEvt(anchorEvents, 'anchor');
|
|
}
|
|
|
|
function setAnchorEl (el) {
|
|
anchorEl.value = el;
|
|
while (anchorEl.value.classList.contains('q-anchor--skip')) {
|
|
anchorEl.value = anchorEl.value.parentNode;
|
|
}
|
|
configureAnchorEl();
|
|
}
|
|
|
|
function pickAnchorEl () {
|
|
if (props.target === false || props.target === '' || proxy.$el.parentNode === null) {
|
|
anchorEl.value = null;
|
|
}
|
|
else if (props.target === true) {
|
|
setAnchorEl(proxy.$el.parentNode);
|
|
}
|
|
else {
|
|
let el = props.target;
|
|
|
|
if (typeof props.target === 'string') {
|
|
try {
|
|
el = document.querySelector(props.target);
|
|
}
|
|
catch (err) {
|
|
el = void 0;
|
|
}
|
|
}
|
|
|
|
if (el !== void 0 && el !== null) {
|
|
anchorEl.value = el.$el || el;
|
|
configureAnchorEl();
|
|
}
|
|
else {
|
|
anchorEl.value = null;
|
|
console.error(`Anchor: target "${ props.target }" not found`);
|
|
}
|
|
}
|
|
}
|
|
|
|
vue.watch(() => props.contextMenu, val => {
|
|
if (anchorEl.value !== null) {
|
|
unconfigureAnchorEl();
|
|
configureAnchorEl(val);
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.target, () => {
|
|
if (anchorEl.value !== null) {
|
|
unconfigureAnchorEl();
|
|
}
|
|
|
|
pickAnchorEl();
|
|
});
|
|
|
|
vue.watch(() => props.noParentEvent, val => {
|
|
if (anchorEl.value !== null) {
|
|
if (val === true) {
|
|
unconfigureAnchorEl();
|
|
}
|
|
else {
|
|
configureAnchorEl();
|
|
}
|
|
}
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
pickAnchorEl();
|
|
|
|
if (avoidEmit !== true && props.modelValue === true && anchorEl.value === null) {
|
|
emit('update:modelValue', false);
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
touchTimer !== null && clearTimeout(touchTimer);
|
|
unconfigureAnchorEl();
|
|
});
|
|
|
|
return {
|
|
anchorEl,
|
|
canShow,
|
|
anchorEvents
|
|
}
|
|
}
|
|
|
|
function useScrollTarget (
|
|
props,
|
|
configureScrollTarget
|
|
) {
|
|
const localScrollTarget = vue.ref(null);
|
|
let scrollFn;
|
|
|
|
function changeScrollEvent (scrollTarget, fn) {
|
|
const fnProp = `${ fn !== void 0 ? 'add' : 'remove' }EventListener`;
|
|
const fnHandler = fn !== void 0 ? fn : scrollFn;
|
|
|
|
if (scrollTarget !== window) {
|
|
scrollTarget[ fnProp ]('scroll', fnHandler, listenOpts.passive);
|
|
}
|
|
|
|
window[ fnProp ]('scroll', fnHandler, listenOpts.passive);
|
|
|
|
scrollFn = fn;
|
|
}
|
|
|
|
function unconfigureScrollTarget () {
|
|
if (localScrollTarget.value !== null) {
|
|
changeScrollEvent(localScrollTarget.value);
|
|
localScrollTarget.value = null;
|
|
}
|
|
}
|
|
|
|
const noParentEventWatcher = vue.watch(() => props.noParentEvent, () => {
|
|
if (localScrollTarget.value !== null) {
|
|
unconfigureScrollTarget();
|
|
configureScrollTarget();
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(noParentEventWatcher);
|
|
|
|
return {
|
|
localScrollTarget,
|
|
unconfigureScrollTarget,
|
|
changeScrollEvent
|
|
}
|
|
}
|
|
|
|
const useModelToggleProps = {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
'onUpdate:modelValue': [ Function, Array ]
|
|
};
|
|
|
|
const useModelToggleEmits = [
|
|
'beforeShow', 'show', 'beforeHide', 'hide'
|
|
];
|
|
|
|
// handleShow/handleHide -> removeTick(), self (& emit show)
|
|
|
|
function useModelToggle ({
|
|
showing,
|
|
canShow, // optional
|
|
hideOnRouteChange, // optional
|
|
handleShow, // optional
|
|
handleHide, // optional
|
|
processOnMount // optional
|
|
}) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { props, emit, proxy } = vm;
|
|
|
|
let payload;
|
|
|
|
function toggle (evt) {
|
|
if (showing.value === true) {
|
|
hide(evt);
|
|
}
|
|
else {
|
|
show(evt);
|
|
}
|
|
}
|
|
|
|
function show (evt) {
|
|
if (
|
|
props.disable === true
|
|
|| (evt !== void 0 && evt.qAnchorHandled === true)
|
|
|| (canShow !== void 0 && canShow(evt) !== true)
|
|
) {
|
|
return
|
|
}
|
|
|
|
const listener = props[ 'onUpdate:modelValue' ] !== void 0;
|
|
|
|
if (listener === true && false !== true) {
|
|
emit('update:modelValue', true);
|
|
payload = evt;
|
|
vue.nextTick(() => {
|
|
if (payload === evt) {
|
|
payload = void 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (props.modelValue === null || listener === false || false) {
|
|
processShow(evt);
|
|
}
|
|
}
|
|
|
|
function processShow (evt) {
|
|
if (showing.value === true) {
|
|
return
|
|
}
|
|
|
|
showing.value = true;
|
|
|
|
emit('beforeShow', evt);
|
|
|
|
if (handleShow !== void 0) {
|
|
handleShow(evt);
|
|
}
|
|
else {
|
|
emit('show', evt);
|
|
}
|
|
}
|
|
|
|
function hide (evt) {
|
|
if (props.disable === true) {
|
|
return
|
|
}
|
|
|
|
const listener = props[ 'onUpdate:modelValue' ] !== void 0;
|
|
|
|
if (listener === true && false !== true) {
|
|
emit('update:modelValue', false);
|
|
payload = evt;
|
|
vue.nextTick(() => {
|
|
if (payload === evt) {
|
|
payload = void 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (props.modelValue === null || listener === false || false) {
|
|
processHide(evt);
|
|
}
|
|
}
|
|
|
|
function processHide (evt) {
|
|
if (showing.value === false) {
|
|
return
|
|
}
|
|
|
|
showing.value = false;
|
|
|
|
emit('beforeHide', evt);
|
|
|
|
if (handleHide !== void 0) {
|
|
handleHide(evt);
|
|
}
|
|
else {
|
|
emit('hide', evt);
|
|
}
|
|
}
|
|
|
|
function processModelChange (val) {
|
|
if (props.disable === true && val === true) {
|
|
if (props[ 'onUpdate:modelValue' ] !== void 0) {
|
|
emit('update:modelValue', false);
|
|
}
|
|
}
|
|
else if ((val === true) !== showing.value) {
|
|
const fn = val === true ? processShow : processHide;
|
|
fn(payload);
|
|
}
|
|
}
|
|
|
|
vue.watch(() => props.modelValue, processModelChange);
|
|
|
|
if (hideOnRouteChange !== void 0 && vmHasRouter(vm) === true) {
|
|
vue.watch(() => proxy.$route.fullPath, () => {
|
|
if (hideOnRouteChange.value === true && showing.value === true) {
|
|
hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
processOnMount === true && vue.onMounted(() => {
|
|
processModelChange(props.modelValue);
|
|
});
|
|
|
|
// expose public methods
|
|
const publicMethods = { show, hide, toggle };
|
|
Object.assign(proxy, publicMethods);
|
|
|
|
return publicMethods
|
|
}
|
|
|
|
let queue = [];
|
|
let waitFlags = [];
|
|
|
|
function clearFlag (flag) {
|
|
waitFlags = waitFlags.filter(entry => entry !== flag);
|
|
}
|
|
|
|
function addFocusWaitFlag (flag) {
|
|
clearFlag(flag);
|
|
waitFlags.push(flag);
|
|
}
|
|
|
|
function removeFocusWaitFlag (flag) {
|
|
clearFlag(flag);
|
|
|
|
if (waitFlags.length === 0 && queue.length !== 0) {
|
|
// only call last focus handler (can't focus multiple things at once)
|
|
queue[ queue.length - 1 ]();
|
|
queue = [];
|
|
}
|
|
}
|
|
|
|
function addFocusFn (fn) {
|
|
if (waitFlags.length === 0) {
|
|
fn();
|
|
}
|
|
else {
|
|
queue.push(fn);
|
|
}
|
|
}
|
|
|
|
function removeFocusFn (fn) {
|
|
queue = queue.filter(entry => entry !== fn);
|
|
}
|
|
|
|
const nodesList = [];
|
|
const portalTypeList = [];
|
|
|
|
let portalIndex = 1;
|
|
let target = document.body;
|
|
|
|
function createGlobalNode (id, portalType) {
|
|
const el = document.createElement('div');
|
|
|
|
el.id = portalType !== void 0
|
|
? `q-portal--${ portalType }--${ portalIndex++ }`
|
|
: id;
|
|
|
|
if (globalConfig.globalNodes !== void 0) {
|
|
const cls = globalConfig.globalNodes.class;
|
|
if (cls !== void 0) {
|
|
el.className = cls;
|
|
}
|
|
}
|
|
|
|
target.appendChild(el);
|
|
nodesList.push(el);
|
|
portalTypeList.push(portalType);
|
|
|
|
return el
|
|
}
|
|
|
|
function removeGlobalNode (el) {
|
|
const nodeIndex = nodesList.indexOf(el);
|
|
|
|
nodesList.splice(nodeIndex, 1);
|
|
portalTypeList.splice(nodeIndex, 1);
|
|
|
|
el.remove();
|
|
}
|
|
|
|
function changeGlobalNodesTarget (newTarget) {
|
|
if (newTarget === target) {
|
|
return
|
|
}
|
|
|
|
target = newTarget;
|
|
|
|
if (
|
|
target === document.body
|
|
// or we have less than 2 dialogs:
|
|
|| portalTypeList.reduce((acc, type) => (type === 'dialog' ? acc + 1 : acc), 0) < 2
|
|
) {
|
|
nodesList.forEach(node => {
|
|
if (node.contains(target) === false) {
|
|
target.appendChild(node);
|
|
}
|
|
});
|
|
|
|
return
|
|
}
|
|
|
|
const lastDialogIndex = portalTypeList.lastIndexOf('dialog');
|
|
|
|
for (let i = 0; i < nodesList.length; i++) {
|
|
const el = nodesList[ i ];
|
|
|
|
if (
|
|
(i === lastDialogIndex || portalTypeList[ i ] !== 'dialog')
|
|
&& el.contains(target) === false
|
|
) {
|
|
target.appendChild(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
const portalProxyList = [];
|
|
|
|
function getPortalProxy (el) {
|
|
return portalProxyList.find(proxy =>
|
|
proxy.contentEl !== null
|
|
&& proxy.contentEl.contains(el)
|
|
)
|
|
}
|
|
|
|
function closePortalMenus (proxy, evt) {
|
|
do {
|
|
if (proxy.$options.name === 'QMenu') {
|
|
proxy.hide(evt);
|
|
|
|
// is this a point of separation?
|
|
if (proxy.$props.separateClosePopup === true) {
|
|
return getParentProxy(proxy)
|
|
}
|
|
}
|
|
else if (proxy.__qPortal === true) {
|
|
// treat it as point of separation if parent is QPopupProxy
|
|
// (so mobile matches desktop behavior)
|
|
// and hide it too
|
|
const parent = getParentProxy(proxy);
|
|
|
|
if (parent !== void 0 && parent.$options.name === 'QPopupProxy') {
|
|
proxy.hide(evt);
|
|
return parent
|
|
}
|
|
else {
|
|
return proxy
|
|
}
|
|
}
|
|
|
|
proxy = getParentProxy(proxy);
|
|
} while (proxy !== void 0 && proxy !== null)
|
|
}
|
|
|
|
function closePortals (proxy, evt, depth) {
|
|
while (depth !== 0 && proxy !== void 0 && proxy !== null) {
|
|
if (proxy.__qPortal === true) {
|
|
depth--;
|
|
|
|
if (proxy.$options.name === 'QMenu') {
|
|
proxy = closePortalMenus(proxy, evt);
|
|
continue
|
|
}
|
|
|
|
proxy.hide(evt);
|
|
}
|
|
|
|
proxy = getParentProxy(proxy);
|
|
}
|
|
}
|
|
|
|
function isOnGlobalDialog (vm) {
|
|
vm = vm.parent;
|
|
|
|
while (vm !== void 0 && vm !== null) {
|
|
if (vm.type.name === 'QGlobalDialog') {
|
|
return true
|
|
}
|
|
if (vm.type.name === 'QDialog' || vm.type.name === 'QMenu') {
|
|
return false
|
|
}
|
|
|
|
vm = vm.parent;
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Warning!
|
|
// You MUST specify "inheritAttrs: false" in your component
|
|
|
|
function usePortal (vm, innerRef, renderPortalContent, type) {
|
|
// showing, including while in show/hide transition
|
|
const portalIsActive = vue.ref(false);
|
|
|
|
// showing & not in any show/hide transition
|
|
const portalIsAccessible = vue.ref(false);
|
|
|
|
let portalEl = null;
|
|
const focusObj = {};
|
|
const onGlobalDialog = type === 'dialog' && isOnGlobalDialog(vm);
|
|
|
|
function showPortal (isReady) {
|
|
if (isReady === true) {
|
|
removeFocusWaitFlag(focusObj);
|
|
portalIsAccessible.value = true;
|
|
return
|
|
}
|
|
|
|
portalIsAccessible.value = false;
|
|
|
|
if (portalIsActive.value === false) {
|
|
if (onGlobalDialog === false && portalEl === null) {
|
|
portalEl = createGlobalNode(false, type);
|
|
}
|
|
|
|
portalIsActive.value = true;
|
|
|
|
// register portal
|
|
portalProxyList.push(vm.proxy);
|
|
|
|
addFocusWaitFlag(focusObj);
|
|
}
|
|
}
|
|
|
|
function hidePortal (isReady) {
|
|
portalIsAccessible.value = false;
|
|
|
|
if (isReady !== true) { return }
|
|
|
|
removeFocusWaitFlag(focusObj);
|
|
portalIsActive.value = false;
|
|
|
|
// unregister portal
|
|
const index = portalProxyList.indexOf(vm.proxy);
|
|
if (index !== -1) {
|
|
portalProxyList.splice(index, 1);
|
|
}
|
|
|
|
if (portalEl !== null) {
|
|
removeGlobalNode(portalEl);
|
|
portalEl = null;
|
|
}
|
|
}
|
|
|
|
vue.onUnmounted(() => { hidePortal(true); });
|
|
|
|
// needed for portal vm detection
|
|
vm.proxy.__qPortal = true;
|
|
|
|
// public way of accessing the rendered content
|
|
injectProp(vm.proxy, 'contentEl', () => innerRef.value);
|
|
|
|
return {
|
|
showPortal,
|
|
hidePortal,
|
|
|
|
portalIsActive,
|
|
portalIsAccessible,
|
|
|
|
renderPortal: () => (
|
|
onGlobalDialog === true
|
|
? renderPortalContent()
|
|
: (
|
|
portalIsActive.value === true
|
|
? [ vue.h(vue.Teleport, { to: portalEl }, renderPortalContent()) ]
|
|
: void 0
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
const useTransitionProps = {
|
|
transitionShow: {
|
|
type: String,
|
|
default: 'fade'
|
|
},
|
|
|
|
transitionHide: {
|
|
type: String,
|
|
default: 'fade'
|
|
},
|
|
|
|
transitionDuration: {
|
|
type: [ String, Number ],
|
|
default: 300
|
|
}
|
|
};
|
|
|
|
function useTransition (props, defaultShowFn = () => {}, defaultHideFn = () => {}) {
|
|
return {
|
|
transitionProps: vue.computed(() => {
|
|
const show = `q-transition--${ props.transitionShow || defaultShowFn() }`;
|
|
const hide = `q-transition--${ props.transitionHide || defaultHideFn() }`;
|
|
|
|
return {
|
|
appear: true,
|
|
|
|
enterFromClass: `${ show }-enter-from`,
|
|
enterActiveClass: `${ show }-enter-active`,
|
|
enterToClass: `${ show }-enter-to`,
|
|
|
|
leaveFromClass: `${ hide }-leave-from`,
|
|
leaveActiveClass: `${ hide }-leave-active`,
|
|
leaveToClass: `${ hide }-leave-to`
|
|
}
|
|
}),
|
|
|
|
transitionStyle: vue.computed(() => `--q-transition-duration: ${ props.transitionDuration }ms`)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Usage:
|
|
* registerTick(fn)
|
|
* removeTick()
|
|
*/
|
|
|
|
function useTick () {
|
|
let tickFn;
|
|
const vm = vue.getCurrentInstance();
|
|
|
|
function removeTick () {
|
|
tickFn = void 0;
|
|
}
|
|
|
|
vue.onDeactivated(removeTick);
|
|
vue.onBeforeUnmount(removeTick);
|
|
|
|
return {
|
|
removeTick,
|
|
|
|
registerTick (fn) {
|
|
tickFn = fn;
|
|
|
|
vue.nextTick(() => {
|
|
if (tickFn === fn) {
|
|
// we also check if VM is destroyed, since if it
|
|
// got to trigger one nextTick() we cannot stop it
|
|
vmIsDestroyed(vm) === false && tickFn();
|
|
tickFn = void 0;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Usage:
|
|
* registerTimeout(fn[, delay])
|
|
* removeTimeout()
|
|
*/
|
|
|
|
function useTimeout () {
|
|
let timer = null;
|
|
const vm = vue.getCurrentInstance();
|
|
|
|
function removeTimeout () {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
}
|
|
|
|
vue.onDeactivated(removeTimeout);
|
|
vue.onBeforeUnmount(removeTimeout);
|
|
|
|
return {
|
|
removeTimeout,
|
|
|
|
registerTimeout (fn, delay) {
|
|
removeTimeout();
|
|
|
|
if (vmIsDestroyed(vm) === false) {
|
|
timer = setTimeout(fn, delay);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const scrollTargets = [ null, document, document.body, document.scrollingElement, document.documentElement ];
|
|
|
|
function getScrollTarget (el, targetEl) {
|
|
let target = getElement$1(targetEl);
|
|
|
|
if (target === void 0) {
|
|
if (el === void 0 || el === null) {
|
|
return window
|
|
}
|
|
|
|
target = el.closest('.scroll,.scroll-y,.overflow-auto');
|
|
}
|
|
|
|
return scrollTargets.includes(target)
|
|
? window
|
|
: target
|
|
}
|
|
|
|
function getScrollHeight (el) {
|
|
return (el === window ? document.body : el).scrollHeight
|
|
}
|
|
|
|
function getScrollWidth (el) {
|
|
return (el === window ? document.body : el).scrollWidth
|
|
}
|
|
|
|
function getVerticalScrollPosition (scrollTarget) {
|
|
return scrollTarget === window
|
|
? window.pageYOffset || window.scrollY || document.body.scrollTop || 0
|
|
: scrollTarget.scrollTop
|
|
}
|
|
|
|
function getHorizontalScrollPosition (scrollTarget) {
|
|
return scrollTarget === window
|
|
? window.pageXOffset || window.scrollX || document.body.scrollLeft || 0
|
|
: scrollTarget.scrollLeft
|
|
}
|
|
|
|
function animVerticalScrollTo (el, to, duration = 0 /* , prevTime */) {
|
|
const prevTime = arguments[ 3 ] === void 0 ? performance.now() : arguments[ 3 ];
|
|
const pos = getVerticalScrollPosition(el);
|
|
|
|
if (duration <= 0) {
|
|
if (pos !== to) {
|
|
setScroll$1(el, to);
|
|
}
|
|
return
|
|
}
|
|
|
|
requestAnimationFrame(nowTime => {
|
|
const frameTime = nowTime - prevTime;
|
|
const newPos = pos + (to - pos) / Math.max(frameTime, duration) * frameTime;
|
|
setScroll$1(el, newPos);
|
|
if (newPos !== to) {
|
|
animVerticalScrollTo(el, to, duration - frameTime, nowTime);
|
|
}
|
|
});
|
|
}
|
|
|
|
function animHorizontalScrollTo (el, to, duration = 0 /* , prevTime */) {
|
|
const prevTime = arguments[ 3 ] === void 0 ? performance.now() : arguments[ 3 ];
|
|
const pos = getHorizontalScrollPosition(el);
|
|
|
|
if (duration <= 0) {
|
|
if (pos !== to) {
|
|
setHorizontalScroll(el, to);
|
|
}
|
|
return
|
|
}
|
|
|
|
requestAnimationFrame(nowTime => {
|
|
const frameTime = nowTime - prevTime;
|
|
const newPos = pos + (to - pos) / Math.max(frameTime, duration) * frameTime;
|
|
setHorizontalScroll(el, newPos);
|
|
if (newPos !== to) {
|
|
animHorizontalScrollTo(el, to, duration - frameTime, nowTime);
|
|
}
|
|
});
|
|
}
|
|
|
|
function setScroll$1 (scrollTarget, offset) {
|
|
if (scrollTarget === window) {
|
|
window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, offset);
|
|
return
|
|
}
|
|
scrollTarget.scrollTop = offset;
|
|
}
|
|
|
|
function setHorizontalScroll (scrollTarget, offset) {
|
|
if (scrollTarget === window) {
|
|
window.scrollTo(offset, window.pageYOffset || window.scrollY || document.body.scrollTop || 0);
|
|
return
|
|
}
|
|
scrollTarget.scrollLeft = offset;
|
|
}
|
|
|
|
function setVerticalScrollPosition (scrollTarget, offset, duration) {
|
|
if (duration) {
|
|
animVerticalScrollTo(scrollTarget, offset, duration);
|
|
return
|
|
}
|
|
setScroll$1(scrollTarget, offset);
|
|
}
|
|
|
|
function setHorizontalScrollPosition (scrollTarget, offset, duration) {
|
|
if (duration) {
|
|
animHorizontalScrollTo(scrollTarget, offset, duration);
|
|
return
|
|
}
|
|
setHorizontalScroll(scrollTarget, offset);
|
|
}
|
|
|
|
let size;
|
|
function getScrollbarWidth () {
|
|
if (size !== undefined) {
|
|
return size
|
|
}
|
|
|
|
const
|
|
inner = document.createElement('p'),
|
|
outer = document.createElement('div');
|
|
|
|
css(inner, {
|
|
width: '100%',
|
|
height: '200px'
|
|
});
|
|
css(outer, {
|
|
position: 'absolute',
|
|
top: '0px',
|
|
left: '0px',
|
|
visibility: 'hidden',
|
|
width: '200px',
|
|
height: '150px',
|
|
overflow: 'hidden'
|
|
});
|
|
|
|
outer.appendChild(inner);
|
|
|
|
document.body.appendChild(outer);
|
|
|
|
const w1 = inner.offsetWidth;
|
|
outer.style.overflow = 'scroll';
|
|
let w2 = inner.offsetWidth;
|
|
|
|
if (w1 === w2) {
|
|
w2 = outer.clientWidth;
|
|
}
|
|
|
|
outer.remove();
|
|
size = w1 - w2;
|
|
|
|
return size
|
|
}
|
|
|
|
function hasScrollbar (el, onY = true) {
|
|
if (!el || el.nodeType !== Node.ELEMENT_NODE) {
|
|
return false
|
|
}
|
|
|
|
return onY
|
|
? (
|
|
el.scrollHeight > el.clientHeight && (
|
|
el.classList.contains('scroll')
|
|
|| el.classList.contains('overflow-auto')
|
|
|| [ 'auto', 'scroll' ].includes(window.getComputedStyle(el)[ 'overflow-y' ])
|
|
)
|
|
)
|
|
: (
|
|
el.scrollWidth > el.clientWidth && (
|
|
el.classList.contains('scroll')
|
|
|| el.classList.contains('overflow-auto')
|
|
|| [ 'auto', 'scroll' ].includes(window.getComputedStyle(el)[ 'overflow-x' ])
|
|
)
|
|
)
|
|
}
|
|
|
|
var scroll = {
|
|
getScrollTarget,
|
|
|
|
getScrollHeight,
|
|
getScrollWidth,
|
|
|
|
getVerticalScrollPosition,
|
|
getHorizontalScrollPosition,
|
|
|
|
animVerticalScrollTo,
|
|
animHorizontalScrollTo,
|
|
|
|
setVerticalScrollPosition,
|
|
setHorizontalScrollPosition,
|
|
|
|
getScrollbarWidth,
|
|
hasScrollbar
|
|
};
|
|
|
|
const handlers$1 = [];
|
|
let escDown;
|
|
|
|
function onKeydown (evt) {
|
|
escDown = evt.keyCode === 27;
|
|
}
|
|
|
|
function onBlur () {
|
|
if (escDown === true) {
|
|
escDown = false;
|
|
}
|
|
}
|
|
|
|
function onKeyup (evt) {
|
|
if (escDown === true) {
|
|
escDown = false;
|
|
|
|
if (isKeyCode(evt, 27) === true) {
|
|
handlers$1[ handlers$1.length - 1 ](evt);
|
|
}
|
|
}
|
|
}
|
|
|
|
function update$4 (action) {
|
|
window[ action ]('keydown', onKeydown);
|
|
window[ action ]('blur', onBlur);
|
|
window[ action ]('keyup', onKeyup);
|
|
escDown = false;
|
|
}
|
|
|
|
function addEscapeKey (fn) {
|
|
if (client.is.desktop === true) {
|
|
handlers$1.push(fn);
|
|
|
|
if (handlers$1.length === 1) {
|
|
update$4('addEventListener');
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeEscapeKey (fn) {
|
|
const index = handlers$1.indexOf(fn);
|
|
if (index > -1) {
|
|
handlers$1.splice(index, 1);
|
|
|
|
if (handlers$1.length === 0) {
|
|
update$4('removeEventListener');
|
|
}
|
|
}
|
|
}
|
|
|
|
const handlers = [];
|
|
|
|
function trigger$1 (e) {
|
|
handlers[ handlers.length - 1 ](e);
|
|
}
|
|
|
|
function addFocusout (fn) {
|
|
if (client.is.desktop === true) {
|
|
handlers.push(fn);
|
|
|
|
if (handlers.length === 1) {
|
|
document.body.addEventListener('focusin', trigger$1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeFocusout (fn) {
|
|
const index = handlers.indexOf(fn);
|
|
if (index > -1) {
|
|
handlers.splice(index, 1);
|
|
|
|
if (handlers.length === 0) {
|
|
document.body.removeEventListener('focusin', trigger$1);
|
|
}
|
|
}
|
|
}
|
|
|
|
const
|
|
{ notPassiveCapture } = listenOpts,
|
|
registeredList = [];
|
|
|
|
function globalHandler (evt) {
|
|
|
|
const target = evt.target;
|
|
|
|
if (
|
|
target === void 0
|
|
|| target.nodeType === 8
|
|
|| target.classList.contains('no-pointer-events') === true
|
|
) {
|
|
return
|
|
}
|
|
|
|
// check last portal vm if it's
|
|
// a QDialog and not in seamless mode
|
|
let portalIndex = portalProxyList.length - 1;
|
|
|
|
while (portalIndex >= 0) {
|
|
const proxy = portalProxyList[ portalIndex ].$;
|
|
|
|
// skip QTooltip portals
|
|
if (proxy.type.name === 'QTooltip') {
|
|
portalIndex--;
|
|
continue
|
|
}
|
|
|
|
if (proxy.type.name !== 'QDialog') {
|
|
break
|
|
}
|
|
|
|
if (proxy.props.seamless !== true) {
|
|
return
|
|
}
|
|
|
|
portalIndex--;
|
|
}
|
|
|
|
for (let i = registeredList.length - 1; i >= 0; i--) {
|
|
const state = registeredList[ i ];
|
|
|
|
if (
|
|
(
|
|
state.anchorEl.value === null
|
|
|| state.anchorEl.value.contains(target) === false
|
|
)
|
|
&& (
|
|
target === document.body
|
|
|| (
|
|
state.innerRef.value !== null
|
|
&& state.innerRef.value.contains(target) === false
|
|
)
|
|
)
|
|
) {
|
|
// mark the event as being processed by clickOutside
|
|
// used to prevent refocus after menu close
|
|
evt.qClickOutside = true;
|
|
state.onClickOutside(evt);
|
|
}
|
|
else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function addClickOutside (clickOutsideProps) {
|
|
registeredList.push(clickOutsideProps);
|
|
|
|
if (registeredList.length === 1) {
|
|
document.addEventListener('mousedown', globalHandler, notPassiveCapture);
|
|
document.addEventListener('touchstart', globalHandler, notPassiveCapture);
|
|
}
|
|
}
|
|
|
|
function removeClickOutside (clickOutsideProps) {
|
|
const index = registeredList.findIndex(h => h === clickOutsideProps);
|
|
|
|
if (index > -1) {
|
|
registeredList.splice(index, 1);
|
|
|
|
if (registeredList.length === 0) {
|
|
|
|
document.removeEventListener('mousedown', globalHandler, notPassiveCapture);
|
|
document.removeEventListener('touchstart', globalHandler, notPassiveCapture);
|
|
}
|
|
}
|
|
}
|
|
|
|
let vpLeft, vpTop;
|
|
|
|
function validatePosition (pos) {
|
|
const parts = pos.split(' ');
|
|
if (parts.length !== 2) {
|
|
return false
|
|
}
|
|
if ([ 'top', 'center', 'bottom' ].includes(parts[ 0 ]) !== true) {
|
|
console.error('Anchor/Self position must start with one of top/center/bottom');
|
|
return false
|
|
}
|
|
if ([ 'left', 'middle', 'right', 'start', 'end' ].includes(parts[ 1 ]) !== true) {
|
|
console.error('Anchor/Self position must end with one of left/middle/right/start/end');
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
function validateOffset (val) {
|
|
if (!val) { return true }
|
|
if (val.length !== 2) { return false }
|
|
if (typeof val[ 0 ] !== 'number' || typeof val[ 1 ] !== 'number') {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
const horizontalPos = {
|
|
'start#ltr': 'left',
|
|
'start#rtl': 'right',
|
|
'end#ltr': 'right',
|
|
'end#rtl': 'left'
|
|
}
|
|
|
|
;[ 'left', 'middle', 'right' ].forEach(pos => {
|
|
horizontalPos[ `${ pos }#ltr` ] = pos;
|
|
horizontalPos[ `${ pos }#rtl` ] = pos;
|
|
});
|
|
|
|
function parsePosition (pos, rtl) {
|
|
const parts = pos.split(' ');
|
|
return {
|
|
vertical: parts[ 0 ],
|
|
horizontal: horizontalPos[ `${ parts[ 1 ] }#${ rtl === true ? 'rtl' : 'ltr' }` ]
|
|
}
|
|
}
|
|
|
|
function getAnchorProps (el, offset) {
|
|
let { top, left, right, bottom, width, height } = el.getBoundingClientRect();
|
|
|
|
if (offset !== void 0) {
|
|
top -= offset[ 1 ];
|
|
left -= offset[ 0 ];
|
|
bottom += offset[ 1 ];
|
|
right += offset[ 0 ];
|
|
|
|
width += offset[ 0 ];
|
|
height += offset[ 1 ];
|
|
}
|
|
|
|
return {
|
|
top, bottom, height,
|
|
left, right, width,
|
|
middle: left + (right - left) / 2,
|
|
center: top + (bottom - top) / 2
|
|
}
|
|
}
|
|
|
|
function getAbsoluteAnchorProps (el, absoluteOffset, offset) {
|
|
let { top, left } = el.getBoundingClientRect();
|
|
|
|
top += absoluteOffset.top;
|
|
left += absoluteOffset.left;
|
|
|
|
if (offset !== void 0) {
|
|
top += offset[ 1 ];
|
|
left += offset[ 0 ];
|
|
}
|
|
|
|
return {
|
|
top, bottom: top + 1, height: 1,
|
|
left, right: left + 1, width: 1,
|
|
middle: left,
|
|
center: top
|
|
}
|
|
}
|
|
|
|
function getTargetProps (width, height) {
|
|
return {
|
|
top: 0,
|
|
center: height / 2,
|
|
bottom: height,
|
|
left: 0,
|
|
middle: width / 2,
|
|
right: width
|
|
}
|
|
}
|
|
|
|
function getTopLeftProps (anchorProps, targetProps, anchorOrigin, selfOrigin) {
|
|
return {
|
|
top: anchorProps[ anchorOrigin.vertical ] - targetProps[ selfOrigin.vertical ],
|
|
left: anchorProps[ anchorOrigin.horizontal ] - targetProps[ selfOrigin.horizontal ]
|
|
}
|
|
}
|
|
|
|
function setPosition (cfg, retryNumber = 0) {
|
|
if (
|
|
cfg.targetEl === null
|
|
|| cfg.anchorEl === null
|
|
|| retryNumber > 5 // we should try only a few times
|
|
) {
|
|
return
|
|
}
|
|
|
|
// some browsers report zero height or width because
|
|
// we are trying too early to get these dimensions
|
|
if (cfg.targetEl.offsetHeight === 0 || cfg.targetEl.offsetWidth === 0) {
|
|
setTimeout(() => {
|
|
setPosition(cfg, retryNumber + 1);
|
|
}, 10);
|
|
return
|
|
}
|
|
|
|
const {
|
|
targetEl,
|
|
offset,
|
|
anchorEl,
|
|
anchorOrigin,
|
|
selfOrigin,
|
|
absoluteOffset,
|
|
fit,
|
|
cover,
|
|
maxHeight,
|
|
maxWidth
|
|
} = cfg;
|
|
|
|
if (client.is.ios === true && window.visualViewport !== void 0) {
|
|
// uses the q-position-engine CSS class
|
|
|
|
const el = document.body.style;
|
|
const { offsetLeft: left, offsetTop: top } = window.visualViewport;
|
|
|
|
if (left !== vpLeft) {
|
|
el.setProperty('--q-pe-left', left + 'px');
|
|
vpLeft = left;
|
|
}
|
|
if (top !== vpTop) {
|
|
el.setProperty('--q-pe-top', top + 'px');
|
|
vpTop = top;
|
|
}
|
|
}
|
|
|
|
// scroll position might change
|
|
// if max-height/-width changes, so we
|
|
// need to restore it after we calculate
|
|
// the new positioning
|
|
const { scrollLeft, scrollTop } = targetEl;
|
|
|
|
const anchorProps = absoluteOffset === void 0
|
|
? getAnchorProps(anchorEl, cover === true ? [ 0, 0 ] : offset)
|
|
: getAbsoluteAnchorProps(anchorEl, absoluteOffset, offset);
|
|
|
|
// we "reset" the critical CSS properties
|
|
// so we can take an accurate measurement
|
|
Object.assign(targetEl.style, {
|
|
top: 0,
|
|
left: 0,
|
|
minWidth: null,
|
|
minHeight: null,
|
|
maxWidth: maxWidth || '100vw',
|
|
maxHeight: maxHeight || '100vh',
|
|
visibility: 'visible'
|
|
});
|
|
|
|
const { offsetWidth: origElWidth, offsetHeight: origElHeight } = targetEl;
|
|
const { elWidth, elHeight } = fit === true || cover === true
|
|
? { elWidth: Math.max(anchorProps.width, origElWidth), elHeight: cover === true ? Math.max(anchorProps.height, origElHeight) : origElHeight }
|
|
: { elWidth: origElWidth, elHeight: origElHeight };
|
|
|
|
let elStyle = { maxWidth, maxHeight };
|
|
|
|
if (fit === true || cover === true) {
|
|
elStyle.minWidth = anchorProps.width + 'px';
|
|
if (cover === true) {
|
|
elStyle.minHeight = anchorProps.height + 'px';
|
|
}
|
|
}
|
|
|
|
Object.assign(targetEl.style, elStyle);
|
|
|
|
const targetProps = getTargetProps(elWidth, elHeight);
|
|
let props = getTopLeftProps(anchorProps, targetProps, anchorOrigin, selfOrigin);
|
|
|
|
if (absoluteOffset === void 0 || offset === void 0) {
|
|
applyBoundaries(props, anchorProps, targetProps, anchorOrigin, selfOrigin);
|
|
}
|
|
else { // we have touch position or context menu with offset
|
|
const { top, left } = props; // cache initial values
|
|
|
|
// apply initial boundaries
|
|
applyBoundaries(props, anchorProps, targetProps, anchorOrigin, selfOrigin);
|
|
|
|
let hasChanged = false;
|
|
|
|
// did it flip vertically?
|
|
if (props.top !== top) {
|
|
hasChanged = true;
|
|
const offsetY = 2 * offset[ 1 ];
|
|
anchorProps.center = anchorProps.top -= offsetY;
|
|
anchorProps.bottom -= offsetY + 2;
|
|
}
|
|
|
|
// did it flip horizontally?
|
|
if (props.left !== left) {
|
|
hasChanged = true;
|
|
const offsetX = 2 * offset[ 0 ];
|
|
anchorProps.middle = anchorProps.left -= offsetX;
|
|
anchorProps.right -= offsetX + 2;
|
|
}
|
|
|
|
if (hasChanged === true) {
|
|
// re-calculate props with the new anchor
|
|
props = getTopLeftProps(anchorProps, targetProps, anchorOrigin, selfOrigin);
|
|
|
|
// and re-apply boundaries
|
|
applyBoundaries(props, anchorProps, targetProps, anchorOrigin, selfOrigin);
|
|
}
|
|
}
|
|
|
|
elStyle = {
|
|
top: props.top + 'px',
|
|
left: props.left + 'px'
|
|
};
|
|
|
|
if (props.maxHeight !== void 0) {
|
|
elStyle.maxHeight = props.maxHeight + 'px';
|
|
|
|
if (anchorProps.height > props.maxHeight) {
|
|
elStyle.minHeight = elStyle.maxHeight;
|
|
}
|
|
}
|
|
if (props.maxWidth !== void 0) {
|
|
elStyle.maxWidth = props.maxWidth + 'px';
|
|
|
|
if (anchorProps.width > props.maxWidth) {
|
|
elStyle.minWidth = elStyle.maxWidth;
|
|
}
|
|
}
|
|
|
|
Object.assign(targetEl.style, elStyle);
|
|
|
|
// restore scroll position
|
|
if (targetEl.scrollTop !== scrollTop) {
|
|
targetEl.scrollTop = scrollTop;
|
|
}
|
|
if (targetEl.scrollLeft !== scrollLeft) {
|
|
targetEl.scrollLeft = scrollLeft;
|
|
}
|
|
}
|
|
|
|
function applyBoundaries (props, anchorProps, targetProps, anchorOrigin, selfOrigin) {
|
|
const
|
|
currentHeight = targetProps.bottom,
|
|
currentWidth = targetProps.right,
|
|
margin = getScrollbarWidth(),
|
|
innerHeight = window.innerHeight - margin,
|
|
innerWidth = document.body.clientWidth;
|
|
|
|
if (props.top < 0 || props.top + currentHeight > innerHeight) {
|
|
if (selfOrigin.vertical === 'center') {
|
|
props.top = anchorProps[ anchorOrigin.vertical ] > innerHeight / 2
|
|
? Math.max(0, innerHeight - currentHeight)
|
|
: 0;
|
|
props.maxHeight = Math.min(currentHeight, innerHeight);
|
|
}
|
|
else if (anchorProps[ anchorOrigin.vertical ] > innerHeight / 2) {
|
|
const anchorY = Math.min(
|
|
innerHeight,
|
|
anchorOrigin.vertical === 'center'
|
|
? anchorProps.center
|
|
: (anchorOrigin.vertical === selfOrigin.vertical ? anchorProps.bottom : anchorProps.top)
|
|
);
|
|
props.maxHeight = Math.min(currentHeight, anchorY);
|
|
props.top = Math.max(0, anchorY - currentHeight);
|
|
}
|
|
else {
|
|
props.top = Math.max(0, anchorOrigin.vertical === 'center'
|
|
? anchorProps.center
|
|
: (anchorOrigin.vertical === selfOrigin.vertical ? anchorProps.top : anchorProps.bottom)
|
|
);
|
|
props.maxHeight = Math.min(currentHeight, innerHeight - props.top);
|
|
}
|
|
}
|
|
|
|
if (props.left < 0 || props.left + currentWidth > innerWidth) {
|
|
props.maxWidth = Math.min(currentWidth, innerWidth);
|
|
if (selfOrigin.horizontal === 'middle') {
|
|
props.left = anchorProps[ anchorOrigin.horizontal ] > innerWidth / 2
|
|
? Math.max(0, innerWidth - currentWidth)
|
|
: 0;
|
|
}
|
|
else if (anchorProps[ anchorOrigin.horizontal ] > innerWidth / 2) {
|
|
const anchorX = Math.min(
|
|
innerWidth,
|
|
anchorOrigin.horizontal === 'middle'
|
|
? anchorProps.middle
|
|
: (anchorOrigin.horizontal === selfOrigin.horizontal ? anchorProps.right : anchorProps.left)
|
|
);
|
|
props.maxWidth = Math.min(currentWidth, anchorX);
|
|
props.left = Math.max(0, anchorX - props.maxWidth);
|
|
}
|
|
else {
|
|
props.left = Math.max(0, anchorOrigin.horizontal === 'middle'
|
|
? anchorProps.middle
|
|
: (anchorOrigin.horizontal === selfOrigin.horizontal ? anchorProps.left : anchorProps.right)
|
|
);
|
|
props.maxWidth = Math.min(currentWidth, innerWidth - props.left);
|
|
}
|
|
}
|
|
}
|
|
|
|
var QMenu = createComponent({
|
|
name: 'QMenu',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useAnchorProps,
|
|
...useModelToggleProps,
|
|
...useDarkProps,
|
|
...useTransitionProps,
|
|
|
|
persistent: Boolean,
|
|
autoClose: Boolean,
|
|
separateClosePopup: Boolean,
|
|
|
|
noRouteDismiss: Boolean,
|
|
noRefocus: Boolean,
|
|
noFocus: Boolean,
|
|
|
|
fit: Boolean,
|
|
cover: Boolean,
|
|
|
|
square: Boolean,
|
|
|
|
anchor: {
|
|
type: String,
|
|
validator: validatePosition
|
|
},
|
|
self: {
|
|
type: String,
|
|
validator: validatePosition
|
|
},
|
|
offset: {
|
|
type: Array,
|
|
validator: validateOffset
|
|
},
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
},
|
|
|
|
touchPosition: Boolean,
|
|
|
|
maxHeight: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
maxWidth: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
},
|
|
|
|
emits: [
|
|
...useModelToggleEmits,
|
|
'click', 'escapeKey'
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
let refocusTarget = null, absoluteOffset, unwatchPosition, avoidAutoClose;
|
|
|
|
const vm = vue.getCurrentInstance();
|
|
const { proxy } = vm;
|
|
const { $q } = proxy;
|
|
|
|
const innerRef = vue.ref(null);
|
|
const showing = vue.ref(false);
|
|
|
|
const hideOnRouteChange = vue.computed(() =>
|
|
props.persistent !== true
|
|
&& props.noRouteDismiss !== true
|
|
);
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { registerTick, removeTick } = useTick();
|
|
const { registerTimeout } = useTimeout();
|
|
const { transitionProps, transitionStyle } = useTransition(props);
|
|
const { localScrollTarget, changeScrollEvent, unconfigureScrollTarget } = useScrollTarget(props, configureScrollTarget);
|
|
|
|
const { anchorEl, canShow } = useAnchor({ showing });
|
|
|
|
const { hide } = useModelToggle({
|
|
showing, canShow, handleShow, handleHide,
|
|
hideOnRouteChange,
|
|
processOnMount: true
|
|
});
|
|
|
|
const { showPortal, hidePortal, renderPortal } = usePortal(vm, innerRef, renderPortalContent, 'menu');
|
|
|
|
const clickOutsideProps = {
|
|
anchorEl,
|
|
innerRef,
|
|
onClickOutside (e) {
|
|
if (props.persistent !== true && showing.value === true) {
|
|
hide(e);
|
|
|
|
if (
|
|
// always prevent touch event
|
|
e.type === 'touchstart'
|
|
// prevent click if it's on a dialog backdrop
|
|
|| e.target.classList.contains('q-dialog__backdrop')
|
|
) {
|
|
stopAndPrevent(e);
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
};
|
|
|
|
const anchorOrigin = vue.computed(() =>
|
|
parsePosition(
|
|
props.anchor || (
|
|
props.cover === true ? 'center middle' : 'bottom start'
|
|
),
|
|
$q.lang.rtl
|
|
)
|
|
);
|
|
|
|
const selfOrigin = vue.computed(() => (
|
|
props.cover === true
|
|
? anchorOrigin.value
|
|
: parsePosition(props.self || 'top start', $q.lang.rtl)
|
|
));
|
|
|
|
const menuClass = vue.computed(() =>
|
|
(props.square === true ? ' q-menu--square' : '')
|
|
+ (isDark.value === true ? ' q-menu--dark q-dark' : '')
|
|
);
|
|
|
|
const onEvents = vue.computed(() => (
|
|
props.autoClose === true
|
|
? { onClick: onAutoClose }
|
|
: {}
|
|
));
|
|
|
|
const handlesFocus = vue.computed(() =>
|
|
showing.value === true && props.persistent !== true
|
|
);
|
|
|
|
vue.watch(handlesFocus, val => {
|
|
if (val === true) {
|
|
addEscapeKey(onEscapeKey);
|
|
addClickOutside(clickOutsideProps);
|
|
}
|
|
else {
|
|
removeEscapeKey(onEscapeKey);
|
|
removeClickOutside(clickOutsideProps);
|
|
}
|
|
});
|
|
|
|
function focus () {
|
|
addFocusFn(() => {
|
|
let node = innerRef.value;
|
|
|
|
if (node && node.contains(document.activeElement) !== true) {
|
|
node = node.querySelector('[autofocus][tabindex], [data-autofocus][tabindex]')
|
|
|| node.querySelector('[autofocus] [tabindex], [data-autofocus] [tabindex]')
|
|
|| node.querySelector('[autofocus], [data-autofocus]')
|
|
|| node;
|
|
node.focus({ preventScroll: true });
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleShow (evt) {
|
|
refocusTarget = props.noRefocus === false
|
|
? document.activeElement
|
|
: null;
|
|
|
|
addFocusout(onFocusout);
|
|
|
|
showPortal();
|
|
configureScrollTarget();
|
|
|
|
absoluteOffset = void 0;
|
|
|
|
if (evt !== void 0 && (props.touchPosition || props.contextMenu)) {
|
|
const pos = position(evt);
|
|
|
|
if (pos.left !== void 0) {
|
|
const { top, left } = anchorEl.value.getBoundingClientRect();
|
|
absoluteOffset = { left: pos.left - left, top: pos.top - top };
|
|
}
|
|
}
|
|
|
|
if (unwatchPosition === void 0) {
|
|
unwatchPosition = vue.watch(
|
|
() => $q.screen.width + '|' + $q.screen.height + '|' + props.self + '|' + props.anchor + '|' + $q.lang.rtl,
|
|
updatePosition
|
|
);
|
|
}
|
|
|
|
if (props.noFocus !== true) {
|
|
document.activeElement.blur();
|
|
}
|
|
|
|
// should removeTick() if this gets removed
|
|
registerTick(() => {
|
|
updatePosition();
|
|
props.noFocus !== true && focus();
|
|
});
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
// required in order to avoid the "double-tap needed" issue
|
|
if ($q.platform.is.ios === true) {
|
|
// if auto-close, then this click should
|
|
// not close the menu
|
|
avoidAutoClose = props.autoClose;
|
|
innerRef.value.click();
|
|
}
|
|
|
|
updatePosition();
|
|
showPortal(true); // done showing portal
|
|
emit('show', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function handleHide (evt) {
|
|
removeTick();
|
|
hidePortal();
|
|
|
|
anchorCleanup(true);
|
|
|
|
if (
|
|
refocusTarget !== null
|
|
&& (
|
|
// menu was hidden from code or ESC plugin
|
|
evt === void 0
|
|
// menu was not closed from a mouse or touch clickOutside
|
|
|| evt.qClickOutside !== true
|
|
)
|
|
) {
|
|
((evt && evt.type.indexOf('key') === 0
|
|
? refocusTarget.closest('[tabindex]:not([tabindex^="-"])')
|
|
: void 0
|
|
) || refocusTarget).focus();
|
|
refocusTarget = null;
|
|
}
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
hidePortal(true); // done hiding, now destroy
|
|
emit('hide', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function anchorCleanup (hiding) {
|
|
absoluteOffset = void 0;
|
|
|
|
if (unwatchPosition !== void 0) {
|
|
unwatchPosition();
|
|
unwatchPosition = void 0;
|
|
}
|
|
|
|
if (hiding === true || showing.value === true) {
|
|
removeFocusout(onFocusout);
|
|
unconfigureScrollTarget();
|
|
removeClickOutside(clickOutsideProps);
|
|
removeEscapeKey(onEscapeKey);
|
|
}
|
|
|
|
if (hiding !== true) {
|
|
refocusTarget = null;
|
|
}
|
|
}
|
|
|
|
function configureScrollTarget () {
|
|
if (anchorEl.value !== null || props.scrollTarget !== void 0) {
|
|
localScrollTarget.value = getScrollTarget(anchorEl.value, props.scrollTarget);
|
|
changeScrollEvent(localScrollTarget.value, updatePosition);
|
|
}
|
|
}
|
|
|
|
function onAutoClose (e) {
|
|
// if auto-close, then the ios double-tap fix which
|
|
// issues a click should not close the menu
|
|
if (avoidAutoClose !== true) {
|
|
closePortalMenus(proxy, e);
|
|
emit('click', e);
|
|
}
|
|
else {
|
|
avoidAutoClose = false;
|
|
}
|
|
}
|
|
|
|
function onFocusout (evt) {
|
|
// the focus is not in a vue child component
|
|
if (
|
|
handlesFocus.value === true
|
|
&& props.noFocus !== true
|
|
&& childHasFocus(innerRef.value, evt.target) !== true
|
|
) {
|
|
focus();
|
|
}
|
|
}
|
|
|
|
function onEscapeKey (evt) {
|
|
emit('escapeKey');
|
|
hide(evt);
|
|
}
|
|
|
|
function updatePosition () {
|
|
setPosition({
|
|
targetEl: innerRef.value,
|
|
offset: props.offset,
|
|
anchorEl: anchorEl.value,
|
|
anchorOrigin: anchorOrigin.value,
|
|
selfOrigin: selfOrigin.value,
|
|
absoluteOffset,
|
|
fit: props.fit,
|
|
cover: props.cover,
|
|
maxHeight: props.maxHeight,
|
|
maxWidth: props.maxWidth
|
|
});
|
|
}
|
|
|
|
function renderPortalContent () {
|
|
return vue.h(
|
|
vue.Transition,
|
|
transitionProps.value,
|
|
() => (
|
|
showing.value === true
|
|
? vue.h('div', {
|
|
role: 'menu',
|
|
...attrs,
|
|
ref: innerRef,
|
|
tabindex: -1,
|
|
class: [
|
|
'q-menu q-position-engine scroll' + menuClass.value,
|
|
attrs.class
|
|
],
|
|
style: [
|
|
attrs.style,
|
|
transitionStyle.value
|
|
],
|
|
...onEvents.value
|
|
}, hSlot(slots.default))
|
|
: null
|
|
)
|
|
)
|
|
}
|
|
|
|
vue.onBeforeUnmount(anchorCleanup);
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { focus, updatePosition });
|
|
|
|
return renderPortal
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Based on the work of https://github.com/jchook/uuid-random
|
|
*/
|
|
|
|
let
|
|
buf,
|
|
bufIdx = 0;
|
|
const hexBytes = new Array(256);
|
|
|
|
// Pre-calculate toString(16) for speed
|
|
for (let i = 0; i < 256; i++) {
|
|
hexBytes[ i ] = (i + 0x100).toString(16).substring(1);
|
|
}
|
|
|
|
// Use best available PRNG
|
|
const randomBytes = (() => {
|
|
// Node & Browser support
|
|
const lib = typeof crypto !== 'undefined'
|
|
? crypto
|
|
: (
|
|
typeof window !== 'undefined'
|
|
? window.crypto || window.msCrypto
|
|
: void 0
|
|
);
|
|
|
|
if (lib !== void 0) {
|
|
if (lib.randomBytes !== void 0) {
|
|
return lib.randomBytes
|
|
}
|
|
if (lib.getRandomValues !== void 0) {
|
|
return n => {
|
|
const bytes = new Uint8Array(n);
|
|
lib.getRandomValues(bytes);
|
|
return bytes
|
|
}
|
|
}
|
|
}
|
|
|
|
return n => {
|
|
const r = [];
|
|
for (let i = n; i > 0; i--) {
|
|
r.push(Math.floor(Math.random() * 256));
|
|
}
|
|
return r
|
|
}
|
|
})();
|
|
|
|
// Buffer random numbers for speed
|
|
// Reduce memory usage by decreasing this number (min 16)
|
|
// or improve speed by increasing this number (try 16384)
|
|
const BUFFER_SIZE = 4096;
|
|
|
|
function uid$3 () {
|
|
// Buffer some random bytes for speed
|
|
if (buf === void 0 || (bufIdx + 16 > BUFFER_SIZE)) {
|
|
bufIdx = 0;
|
|
buf = randomBytes(BUFFER_SIZE);
|
|
}
|
|
|
|
const b = Array.prototype.slice.call(buf, bufIdx, (bufIdx += 16));
|
|
b[ 6 ] = (b[ 6 ] & 0x0f) | 0x40;
|
|
b[ 8 ] = (b[ 8 ] & 0x3f) | 0x80;
|
|
|
|
return hexBytes[ b[ 0 ] ] + hexBytes[ b[ 1 ] ]
|
|
+ hexBytes[ b[ 2 ] ] + hexBytes[ b[ 3 ] ] + '-'
|
|
+ hexBytes[ b[ 4 ] ] + hexBytes[ b[ 5 ] ] + '-'
|
|
+ hexBytes[ b[ 6 ] ] + hexBytes[ b[ 7 ] ] + '-'
|
|
+ hexBytes[ b[ 8 ] ] + hexBytes[ b[ 9 ] ] + '-'
|
|
+ hexBytes[ b[ 10 ] ] + hexBytes[ b[ 11 ] ]
|
|
+ hexBytes[ b[ 12 ] ] + hexBytes[ b[ 13 ] ]
|
|
+ hexBytes[ b[ 14 ] ] + hexBytes[ b[ 15 ] ]
|
|
}
|
|
|
|
const btnPropsList = Object.keys(useBtnProps);
|
|
|
|
const passBtnProps = props => btnPropsList.reduce(
|
|
(acc, key) => {
|
|
const val = props[ key ];
|
|
if (val !== void 0) {
|
|
acc[ key ] = val;
|
|
}
|
|
return acc
|
|
},
|
|
{}
|
|
);
|
|
|
|
var QBtnDropdown = createComponent({
|
|
name: 'QBtnDropdown',
|
|
|
|
props: {
|
|
...useBtnProps,
|
|
...useTransitionProps,
|
|
|
|
modelValue: Boolean,
|
|
split: Boolean,
|
|
dropdownIcon: String,
|
|
|
|
contentClass: [ Array, String, Object ],
|
|
contentStyle: [ Array, String, Object ],
|
|
|
|
cover: Boolean,
|
|
persistent: Boolean,
|
|
noRouteDismiss: Boolean,
|
|
autoClose: Boolean,
|
|
|
|
menuAnchor: {
|
|
type: String,
|
|
default: 'bottom end'
|
|
},
|
|
menuSelf: {
|
|
type: String,
|
|
default: 'top end'
|
|
},
|
|
menuOffset: Array,
|
|
|
|
disableMainBtn: Boolean,
|
|
disableDropdown: Boolean,
|
|
|
|
noIconAnimation: Boolean,
|
|
|
|
toggleAriaLabel: String
|
|
},
|
|
|
|
emits: [ 'update:modelValue', 'click', 'beforeShow', 'show', 'beforeHide', 'hide' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const showing = vue.ref(props.modelValue);
|
|
const menuRef = vue.ref(null);
|
|
const targetUid = uid$3();
|
|
|
|
const ariaAttrs = vue.computed(() => {
|
|
const acc = {
|
|
'aria-expanded': showing.value === true ? 'true' : 'false',
|
|
'aria-haspopup': 'true',
|
|
'aria-controls': targetUid,
|
|
'aria-label': props.toggleAriaLabel || proxy.$q.lang.label[ showing.value === true ? 'collapse' : 'expand' ](props.label)
|
|
};
|
|
|
|
if (
|
|
props.disable === true
|
|
|| (
|
|
(props.split === false && props.disableMainBtn === true)
|
|
|| props.disableDropdown === true
|
|
)
|
|
) {
|
|
acc[ 'aria-disabled' ] = 'true';
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
const iconClass = vue.computed(() =>
|
|
'q-btn-dropdown__arrow'
|
|
+ (showing.value === true && props.noIconAnimation === false ? ' rotate-180' : '')
|
|
+ (props.split === false ? ' q-btn-dropdown__arrow-container' : '')
|
|
);
|
|
|
|
const btnDesignAttr = vue.computed(() => getBtnDesignAttr(props));
|
|
const btnProps = vue.computed(() => passBtnProps(props));
|
|
|
|
vue.watch(() => props.modelValue, val => {
|
|
menuRef.value !== null && menuRef.value[ val ? 'show' : 'hide' ]();
|
|
});
|
|
|
|
vue.watch(() => props.split, hide);
|
|
|
|
function onBeforeShow (e) {
|
|
showing.value = true;
|
|
emit('beforeShow', e);
|
|
}
|
|
|
|
function onShow (e) {
|
|
emit('show', e);
|
|
emit('update:modelValue', true);
|
|
}
|
|
|
|
function onBeforeHide (e) {
|
|
showing.value = false;
|
|
emit('beforeHide', e);
|
|
}
|
|
|
|
function onHide (e) {
|
|
emit('hide', e);
|
|
emit('update:modelValue', false);
|
|
}
|
|
|
|
function onClick (e) {
|
|
emit('click', e);
|
|
}
|
|
|
|
function onClickHide (e) {
|
|
stop(e);
|
|
hide();
|
|
emit('click', e);
|
|
}
|
|
|
|
function toggle (evt) {
|
|
menuRef.value !== null && menuRef.value.toggle(evt);
|
|
}
|
|
|
|
function show (evt) {
|
|
menuRef.value !== null && menuRef.value.show(evt);
|
|
}
|
|
|
|
function hide (evt) {
|
|
menuRef.value !== null && menuRef.value.hide(evt);
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
show, hide, toggle
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
props.modelValue === true && show();
|
|
});
|
|
|
|
return () => {
|
|
const Arrow = [
|
|
vue.h(QIcon, {
|
|
class: iconClass.value,
|
|
name: props.dropdownIcon || proxy.$q.iconSet.arrow.dropdown
|
|
})
|
|
];
|
|
|
|
props.disableDropdown !== true && Arrow.push(
|
|
vue.h(QMenu, {
|
|
ref: menuRef,
|
|
id: targetUid,
|
|
class: props.contentClass,
|
|
style: props.contentStyle,
|
|
cover: props.cover,
|
|
fit: true,
|
|
persistent: props.persistent,
|
|
noRouteDismiss: props.noRouteDismiss,
|
|
autoClose: props.autoClose,
|
|
anchor: props.menuAnchor,
|
|
self: props.menuSelf,
|
|
offset: props.menuOffset,
|
|
separateClosePopup: true,
|
|
transitionShow: props.transitionShow,
|
|
transitionHide: props.transitionHide,
|
|
transitionDuration: props.transitionDuration,
|
|
onBeforeShow,
|
|
onShow,
|
|
onBeforeHide,
|
|
onHide
|
|
}, slots.default)
|
|
);
|
|
|
|
if (props.split === false) {
|
|
return vue.h(QBtn, {
|
|
class: 'q-btn-dropdown q-btn-dropdown--simple',
|
|
...btnProps.value,
|
|
...ariaAttrs.value,
|
|
disable: props.disable === true || props.disableMainBtn === true,
|
|
noWrap: true,
|
|
round: false,
|
|
onClick
|
|
}, {
|
|
default: () => hSlot(slots.label, []).concat(Arrow),
|
|
loading: slots.loading
|
|
})
|
|
}
|
|
|
|
return vue.h(QBtnGroup, {
|
|
class: 'q-btn-dropdown q-btn-dropdown--split no-wrap q-btn-item',
|
|
rounded: props.rounded,
|
|
square: props.square,
|
|
...btnDesignAttr.value,
|
|
glossy: props.glossy,
|
|
stretch: props.stretch
|
|
}, () => [
|
|
vue.h(QBtn, {
|
|
class: 'q-btn-dropdown--current',
|
|
...btnProps.value,
|
|
disable: props.disable === true || props.disableMainBtn === true,
|
|
noWrap: true,
|
|
round: false,
|
|
onClick: onClickHide
|
|
}, {
|
|
default: slots.label,
|
|
loading: slots.loading
|
|
}),
|
|
|
|
vue.h(QBtn, {
|
|
class: 'q-btn-dropdown__arrow-container q-anchor--skip',
|
|
...ariaAttrs.value,
|
|
...btnDesignAttr.value,
|
|
disable: props.disable === true || props.disableDropdown === true,
|
|
rounded: props.rounded,
|
|
color: props.color,
|
|
textColor: props.textColor,
|
|
dense: props.dense,
|
|
size: props.size,
|
|
padding: props.padding,
|
|
ripple: props.ripple
|
|
}, () => Arrow)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
const useFormProps = {
|
|
name: String
|
|
};
|
|
|
|
function useFormAttrs (props) {
|
|
return vue.computed(() => ({
|
|
type: 'hidden',
|
|
name: props.name,
|
|
value: props.modelValue
|
|
}))
|
|
}
|
|
|
|
function useFormInject (formAttrs = {}) {
|
|
return (child, action, className) => {
|
|
child[ action ](
|
|
vue.h('input', {
|
|
class: 'hidden' + (className || ''),
|
|
...formAttrs.value
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
function useFormInputNameAttr (props) {
|
|
return vue.computed(() => props.name || props.for)
|
|
}
|
|
|
|
var QBtnToggle = createComponent({
|
|
name: 'QBtnToggle',
|
|
|
|
props: {
|
|
...useFormProps,
|
|
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
|
|
options: {
|
|
type: Array,
|
|
required: true,
|
|
validator: v => v.every(
|
|
opt => ('label' in opt || 'icon' in opt || 'slot' in opt) && 'value' in opt
|
|
)
|
|
},
|
|
|
|
// To avoid seeing the active raise shadow through
|
|
// the transparent button, give it a color (even white)
|
|
color: String,
|
|
textColor: String,
|
|
toggleColor: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
toggleTextColor: String,
|
|
|
|
outline: Boolean,
|
|
flat: Boolean,
|
|
unelevated: Boolean,
|
|
rounded: Boolean,
|
|
push: Boolean,
|
|
glossy: Boolean,
|
|
|
|
size: String,
|
|
padding: String,
|
|
|
|
noCaps: Boolean,
|
|
noWrap: Boolean,
|
|
dense: Boolean,
|
|
readonly: Boolean,
|
|
disable: Boolean,
|
|
|
|
stack: Boolean,
|
|
stretch: Boolean,
|
|
|
|
spread: Boolean,
|
|
|
|
clearable: Boolean,
|
|
|
|
ripple: {
|
|
type: [ Boolean, Object ],
|
|
default: true
|
|
}
|
|
},
|
|
|
|
emits: [ 'update:modelValue', 'clear', 'click' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const hasActiveValue = vue.computed(() =>
|
|
props.options.find(opt => opt.value === props.modelValue) !== void 0
|
|
);
|
|
|
|
const formAttrs = vue.computed(() => ({
|
|
type: 'hidden',
|
|
name: props.name,
|
|
value: props.modelValue
|
|
}));
|
|
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const btnDesignAttr = vue.computed(() => getBtnDesignAttr(props));
|
|
|
|
const btnOptionDesign = vue.computed(() => ({
|
|
rounded: props.rounded,
|
|
dense: props.dense,
|
|
...btnDesignAttr.value
|
|
}));
|
|
|
|
const btnOptions = vue.computed(() => props.options.map((item, i) => {
|
|
const { attrs, value, slot, ...opt } = item;
|
|
|
|
return {
|
|
slot,
|
|
props: {
|
|
key: i,
|
|
|
|
'aria-pressed': value === props.modelValue ? 'true' : 'false',
|
|
...attrs,
|
|
...opt,
|
|
...btnOptionDesign.value,
|
|
|
|
disable: props.disable === true || opt.disable === true,
|
|
|
|
// Options that come from the button specific options first, then from general props
|
|
color: value === props.modelValue
|
|
? mergeOpt(opt, 'toggleColor')
|
|
: mergeOpt(opt, 'color'),
|
|
textColor: value === props.modelValue
|
|
? mergeOpt(opt, 'toggleTextColor')
|
|
: mergeOpt(opt, 'textColor'),
|
|
noCaps: mergeOpt(opt, 'noCaps') === true,
|
|
noWrap: mergeOpt(opt, 'noWrap') === true,
|
|
|
|
size: mergeOpt(opt, 'size'),
|
|
padding: mergeOpt(opt, 'padding'),
|
|
ripple: mergeOpt(opt, 'ripple'),
|
|
stack: mergeOpt(opt, 'stack') === true,
|
|
stretch: mergeOpt(opt, 'stretch') === true,
|
|
|
|
onClick (e) { set(value, item, e); }
|
|
}
|
|
}
|
|
}));
|
|
|
|
function set (value, opt, e) {
|
|
if (props.readonly !== true) {
|
|
if (props.modelValue === value) {
|
|
if (props.clearable === true) {
|
|
emit('update:modelValue', null, null);
|
|
emit('clear');
|
|
}
|
|
}
|
|
else {
|
|
emit('update:modelValue', value, opt);
|
|
}
|
|
|
|
emit('click', e);
|
|
}
|
|
}
|
|
|
|
function mergeOpt (opt, key) {
|
|
return opt[ key ] === void 0 ? props[ key ] : opt[ key ]
|
|
}
|
|
|
|
function getContent () {
|
|
const child = btnOptions.value.map(opt => {
|
|
return vue.h(QBtn, opt.props, opt.slot !== void 0 ? slots[ opt.slot ] : void 0)
|
|
});
|
|
|
|
if (props.name !== void 0 && props.disable !== true && hasActiveValue.value === true) {
|
|
injectFormInput(child, 'push');
|
|
}
|
|
|
|
return hMergeSlot(slots.default, child)
|
|
}
|
|
|
|
return () => vue.h(QBtnGroup, {
|
|
class: 'q-btn-toggle',
|
|
...btnDesignAttr.value,
|
|
rounded: props.rounded,
|
|
stretch: props.stretch,
|
|
glossy: props.glossy,
|
|
spread: props.spread
|
|
}, getContent)
|
|
}
|
|
});
|
|
|
|
var QCard = createComponent({
|
|
name: 'QCard',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
square: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-card'
|
|
+ (isDark.value === true ? ' q-card--dark q-dark' : '')
|
|
+ (props.bordered === true ? ' q-card--bordered' : '')
|
|
+ (props.square === true ? ' q-card--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-card--flat no-shadow' : '')
|
|
);
|
|
|
|
return () => vue.h(props.tag, { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QCardSection = createComponent({
|
|
name: 'QCardSection',
|
|
|
|
props: {
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
horizontal: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() =>
|
|
'q-card__section'
|
|
+ ` q-card__section--${ props.horizontal === true ? 'horiz row no-wrap' : 'vert' }`
|
|
);
|
|
|
|
return () => vue.h(props.tag, { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QCardActions = createComponent({
|
|
name: 'QCardActions',
|
|
|
|
props: {
|
|
...useAlignProps,
|
|
vertical: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const alignClass = useAlign(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-card__actions ${ alignClass.value }`
|
|
+ ` q-card__actions--${ props.vertical === true ? 'vert column' : 'horiz row' }`
|
|
);
|
|
|
|
return () => vue.h('div', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
const modifiersAll = {
|
|
left: true,
|
|
right: true,
|
|
up: true,
|
|
down: true,
|
|
horizontal: true,
|
|
vertical: true
|
|
};
|
|
|
|
const directionList = Object.keys(modifiersAll);
|
|
|
|
modifiersAll.all = true;
|
|
|
|
function getModifierDirections (mod) {
|
|
const dir = {};
|
|
|
|
for (const direction of directionList) {
|
|
if (mod[ direction ] === true) {
|
|
dir[ direction ] = true;
|
|
}
|
|
}
|
|
|
|
if (Object.keys(dir).length === 0) {
|
|
return modifiersAll
|
|
}
|
|
|
|
if (dir.horizontal === true) {
|
|
dir.left = dir.right = true;
|
|
}
|
|
else if (dir.left === true && dir.right === true) {
|
|
dir.horizontal = true;
|
|
}
|
|
|
|
if (dir.vertical === true) {
|
|
dir.up = dir.down = true;
|
|
}
|
|
else if (dir.up === true && dir.down === true) {
|
|
dir.vertical = true;
|
|
}
|
|
|
|
if (dir.horizontal === true && dir.vertical === true) {
|
|
dir.all = true;
|
|
}
|
|
|
|
return dir
|
|
}
|
|
|
|
// This is especially important (not the main reason, but important)
|
|
// for TouchSwipe directive running on Firefox
|
|
// because text selection on such elements cannot be determined
|
|
// without additional work (on top of getSelection() API)
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=85686
|
|
const avoidNodeNamesList = [ 'INPUT', 'TEXTAREA' ];
|
|
|
|
function shouldStart (evt, ctx) {
|
|
return ctx.event === void 0
|
|
&& evt.target !== void 0
|
|
&& evt.target.draggable !== true
|
|
&& typeof ctx.handler === 'function'
|
|
&& avoidNodeNamesList.includes(evt.target.nodeName.toUpperCase()) === false
|
|
&& (evt.qClonedBy === void 0 || evt.qClonedBy.indexOf(ctx.uid) === -1)
|
|
}
|
|
|
|
function parseArg (arg) {
|
|
// delta (min velocity -- dist / time)
|
|
// mobile min distance on first move
|
|
// desktop min distance until deciding if it's a swipe or not
|
|
const data = [ 0.06, 6, 50 ];
|
|
|
|
if (typeof arg === 'string' && arg.length) {
|
|
arg.split(':').forEach((val, index) => {
|
|
const v = parseFloat(val);
|
|
v && (data[ index ] = v);
|
|
});
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
var TouchSwipe = createDirective({
|
|
name: 'touch-swipe',
|
|
|
|
beforeMount (el, { value, arg, modifiers }) {
|
|
// early return, we don't need to do anything
|
|
if (modifiers.mouse !== true && client.has.touch !== true) {
|
|
return
|
|
}
|
|
|
|
const mouseCapture = modifiers.mouseCapture === true ? 'Capture' : '';
|
|
|
|
const ctx = {
|
|
handler: value,
|
|
sensitivity: parseArg(arg),
|
|
direction: getModifierDirections(modifiers),
|
|
|
|
noop,
|
|
|
|
mouseStart (evt) {
|
|
if (shouldStart(evt, ctx) && leftClick(evt)) {
|
|
addEvt(ctx, 'temp', [
|
|
[ document, 'mousemove', 'move', `notPassive${ mouseCapture }` ],
|
|
[ document, 'mouseup', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt, true);
|
|
}
|
|
},
|
|
|
|
touchStart (evt) {
|
|
if (shouldStart(evt, ctx)) {
|
|
const target = evt.target;
|
|
addEvt(ctx, 'temp', [
|
|
[ target, 'touchmove', 'move', 'notPassiveCapture' ],
|
|
[ target, 'touchcancel', 'end', 'notPassiveCapture' ],
|
|
[ target, 'touchend', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt);
|
|
}
|
|
},
|
|
|
|
start (evt, mouseEvent) {
|
|
client.is.firefox === true && preventDraggable(el, true);
|
|
|
|
const pos = position(evt);
|
|
|
|
ctx.event = {
|
|
x: pos.left,
|
|
y: pos.top,
|
|
time: Date.now(),
|
|
mouse: mouseEvent === true,
|
|
dir: false
|
|
};
|
|
},
|
|
|
|
move (evt) {
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
if (ctx.event.dir !== false) {
|
|
stopAndPrevent(evt);
|
|
return
|
|
}
|
|
|
|
const time = Date.now() - ctx.event.time;
|
|
|
|
if (time === 0) {
|
|
return
|
|
}
|
|
|
|
const
|
|
pos = position(evt),
|
|
distX = pos.left - ctx.event.x,
|
|
absX = Math.abs(distX),
|
|
distY = pos.top - ctx.event.y,
|
|
absY = Math.abs(distY);
|
|
|
|
if (ctx.event.mouse !== true) {
|
|
if (absX < ctx.sensitivity[ 1 ] && absY < ctx.sensitivity[ 1 ]) {
|
|
ctx.end(evt);
|
|
return
|
|
}
|
|
}
|
|
// is user trying to select text?
|
|
// if so, then something should be reported here
|
|
// (previous selection, if any, was discarded when swipe started)
|
|
else if (window.getSelection().toString() !== '') {
|
|
ctx.end(evt);
|
|
return
|
|
}
|
|
else if (absX < ctx.sensitivity[ 2 ] && absY < ctx.sensitivity[ 2 ]) {
|
|
return
|
|
}
|
|
|
|
const
|
|
velX = absX / time,
|
|
velY = absY / time;
|
|
|
|
if (
|
|
ctx.direction.vertical === true
|
|
&& absX < absY
|
|
&& absX < 100
|
|
&& velY > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = distY < 0 ? 'up' : 'down';
|
|
}
|
|
|
|
if (
|
|
ctx.direction.horizontal === true
|
|
&& absX > absY
|
|
&& absY < 100
|
|
&& velX > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = distX < 0 ? 'left' : 'right';
|
|
}
|
|
|
|
if (
|
|
ctx.direction.up === true
|
|
&& absX < absY
|
|
&& distY < 0
|
|
&& absX < 100
|
|
&& velY > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = 'up';
|
|
}
|
|
|
|
if (
|
|
ctx.direction.down === true
|
|
&& absX < absY
|
|
&& distY > 0
|
|
&& absX < 100
|
|
&& velY > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = 'down';
|
|
}
|
|
|
|
if (
|
|
ctx.direction.left === true
|
|
&& absX > absY
|
|
&& distX < 0
|
|
&& absY < 100
|
|
&& velX > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = 'left';
|
|
}
|
|
|
|
if (
|
|
ctx.direction.right === true
|
|
&& absX > absY
|
|
&& distX > 0
|
|
&& absY < 100
|
|
&& velX > ctx.sensitivity[ 0 ]
|
|
) {
|
|
ctx.event.dir = 'right';
|
|
}
|
|
|
|
if (ctx.event.dir !== false) {
|
|
stopAndPrevent(evt);
|
|
|
|
if (ctx.event.mouse === true) {
|
|
document.body.classList.add('no-pointer-events--children');
|
|
document.body.classList.add('non-selectable');
|
|
clearSelection();
|
|
|
|
ctx.styleCleanup = withDelay => {
|
|
ctx.styleCleanup = void 0;
|
|
|
|
document.body.classList.remove('non-selectable');
|
|
|
|
const remove = () => {
|
|
document.body.classList.remove('no-pointer-events--children');
|
|
};
|
|
|
|
if (withDelay === true) { setTimeout(remove, 50); }
|
|
else { remove(); }
|
|
};
|
|
}
|
|
|
|
ctx.handler({
|
|
evt,
|
|
touch: ctx.event.mouse !== true,
|
|
mouse: ctx.event.mouse,
|
|
direction: ctx.event.dir,
|
|
duration: time,
|
|
distance: {
|
|
x: absX,
|
|
y: absY
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
ctx.end(evt);
|
|
}
|
|
},
|
|
|
|
end (evt) {
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
cleanEvt(ctx, 'temp');
|
|
client.is.firefox === true && preventDraggable(el, false);
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup(true);
|
|
evt !== void 0 && ctx.event.dir !== false && stopAndPrevent(evt);
|
|
|
|
ctx.event = void 0;
|
|
}
|
|
};
|
|
|
|
el.__qtouchswipe = ctx;
|
|
|
|
if (modifiers.mouse === true) {
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true
|
|
? 'Capture'
|
|
: '';
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'mousedown', 'mouseStart', `passive${ capture }` ]
|
|
]);
|
|
}
|
|
|
|
client.has.touch === true && addEvt(ctx, 'main', [
|
|
[ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ],
|
|
[ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll)
|
|
]);
|
|
},
|
|
|
|
updated (el, bindings) {
|
|
const ctx = el.__qtouchswipe;
|
|
|
|
if (ctx !== void 0) {
|
|
if (bindings.oldValue !== bindings.value) {
|
|
typeof bindings.value !== 'function' && ctx.end();
|
|
ctx.handler = bindings.value;
|
|
}
|
|
|
|
ctx.direction = getModifierDirections(bindings.modifiers);
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qtouchswipe;
|
|
|
|
if (ctx !== void 0) {
|
|
cleanEvt(ctx, 'main');
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
client.is.firefox === true && preventDraggable(el, false);
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup();
|
|
|
|
delete el.__qtouchswipe;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
function useCache () {
|
|
const cache = new Map();
|
|
|
|
return {
|
|
getCache: function (key, obj) {
|
|
return cache[ key ] === void 0
|
|
? (cache[ key ] = obj)
|
|
: cache[ key ]
|
|
},
|
|
|
|
getCacheWithFn: function (key, fn) {
|
|
return cache[ key ] === void 0
|
|
? (cache[ key ] = fn())
|
|
: cache[ key ]
|
|
}
|
|
}
|
|
}
|
|
|
|
const usePanelChildProps = {
|
|
name: { required: true },
|
|
disable: Boolean
|
|
};
|
|
|
|
const PanelWrapper$1 = {
|
|
setup (_, { slots }) {
|
|
return () => vue.h('div', {
|
|
class: 'q-panel scroll',
|
|
role: 'tabpanel'
|
|
}, hSlot(slots.default))
|
|
}
|
|
};
|
|
|
|
const usePanelProps = {
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
|
|
animated: Boolean,
|
|
infinite: Boolean,
|
|
swipeable: Boolean,
|
|
vertical: Boolean,
|
|
|
|
transitionPrev: String,
|
|
transitionNext: String,
|
|
transitionDuration: {
|
|
type: [ String, Number ],
|
|
default: 300
|
|
},
|
|
|
|
keepAlive: Boolean,
|
|
keepAliveInclude: [ String, Array, RegExp ],
|
|
keepAliveExclude: [ String, Array, RegExp ],
|
|
keepAliveMax: Number
|
|
};
|
|
|
|
const usePanelEmits = [ 'update:modelValue', 'beforeTransition', 'transition' ];
|
|
|
|
function usePanel () {
|
|
const { props, emit, proxy } = vue.getCurrentInstance();
|
|
const { getCacheWithFn } = useCache();
|
|
|
|
let panels, forcedPanelTransition;
|
|
|
|
const panelIndex = vue.ref(null);
|
|
const panelTransition = vue.ref(null);
|
|
|
|
function onSwipe (evt) {
|
|
const dir = props.vertical === true ? 'up' : 'left';
|
|
goToPanelByOffset((proxy.$q.lang.rtl === true ? -1 : 1) * (evt.direction === dir ? 1 : -1));
|
|
}
|
|
|
|
const panelDirectives = vue.computed(() => {
|
|
// if props.swipeable
|
|
return [ [
|
|
TouchSwipe,
|
|
onSwipe,
|
|
void 0,
|
|
{
|
|
horizontal: props.vertical !== true,
|
|
vertical: props.vertical,
|
|
mouse: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
const transitionPrev = vue.computed(() =>
|
|
props.transitionPrev || `slide-${ props.vertical === true ? 'down' : 'right' }`
|
|
);
|
|
|
|
const transitionNext = vue.computed(() =>
|
|
props.transitionNext || `slide-${ props.vertical === true ? 'up' : 'left' }`
|
|
);
|
|
|
|
const transitionStyle = vue.computed(
|
|
() => `--q-transition-duration: ${ props.transitionDuration }ms`
|
|
);
|
|
|
|
const contentKey = vue.computed(() => (
|
|
typeof props.modelValue === 'string' || typeof props.modelValue === 'number'
|
|
? props.modelValue
|
|
: String(props.modelValue)
|
|
));
|
|
|
|
const keepAliveProps = vue.computed(() => ({
|
|
include: props.keepAliveInclude,
|
|
exclude: props.keepAliveExclude,
|
|
max: props.keepAliveMax
|
|
}));
|
|
|
|
const needsUniqueKeepAliveWrapper = vue.computed(() =>
|
|
props.keepAliveInclude !== void 0
|
|
|| props.keepAliveExclude !== void 0
|
|
);
|
|
|
|
vue.watch(() => props.modelValue, (newVal, oldVal) => {
|
|
const index = isValidPanelName(newVal) === true
|
|
? getPanelIndex(newVal)
|
|
: -1;
|
|
|
|
if (forcedPanelTransition !== true) {
|
|
updatePanelTransition(
|
|
index === -1 ? 0 : (index < getPanelIndex(oldVal) ? -1 : 1)
|
|
);
|
|
}
|
|
|
|
if (panelIndex.value !== index) {
|
|
panelIndex.value = index;
|
|
emit('beforeTransition', newVal, oldVal);
|
|
vue.nextTick(() => {
|
|
emit('transition', newVal, oldVal);
|
|
});
|
|
}
|
|
});
|
|
|
|
function nextPanel () { goToPanelByOffset(1); }
|
|
function previousPanel () { goToPanelByOffset(-1); }
|
|
|
|
function goToPanel (name) {
|
|
emit('update:modelValue', name);
|
|
}
|
|
|
|
function isValidPanelName (name) {
|
|
return name !== void 0 && name !== null && name !== ''
|
|
}
|
|
|
|
function getPanelIndex (name) {
|
|
return panels.findIndex(panel => {
|
|
return panel.props.name === name
|
|
&& panel.props.disable !== ''
|
|
&& panel.props.disable !== true
|
|
})
|
|
}
|
|
|
|
function getEnabledPanels () {
|
|
return panels.filter(panel => {
|
|
return panel.props.disable !== ''
|
|
&& panel.props.disable !== true
|
|
})
|
|
}
|
|
|
|
function updatePanelTransition (direction) {
|
|
const val = direction !== 0 && props.animated === true && panelIndex.value !== -1
|
|
? 'q-transition--' + (direction === -1 ? transitionPrev.value : transitionNext.value)
|
|
: null;
|
|
|
|
if (panelTransition.value !== val) {
|
|
panelTransition.value = val;
|
|
}
|
|
}
|
|
|
|
function goToPanelByOffset (direction, startIndex = panelIndex.value) {
|
|
let index = startIndex + direction;
|
|
|
|
while (index > -1 && index < panels.length) {
|
|
const opt = panels[ index ];
|
|
|
|
if (
|
|
opt !== void 0
|
|
&& opt.props.disable !== ''
|
|
&& opt.props.disable !== true
|
|
) {
|
|
updatePanelTransition(direction);
|
|
forcedPanelTransition = true;
|
|
emit('update:modelValue', opt.props.name);
|
|
setTimeout(() => {
|
|
forcedPanelTransition = false;
|
|
});
|
|
return
|
|
}
|
|
|
|
index += direction;
|
|
}
|
|
|
|
if (props.infinite === true && panels.length !== 0 && startIndex !== -1 && startIndex !== panels.length) {
|
|
goToPanelByOffset(direction, direction === -1 ? panels.length : -1);
|
|
}
|
|
}
|
|
|
|
function updatePanelIndex () {
|
|
const index = getPanelIndex(props.modelValue);
|
|
|
|
if (panelIndex.value !== index) {
|
|
panelIndex.value = index;
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
function getPanelContentChild () {
|
|
const panel = isValidPanelName(props.modelValue) === true
|
|
&& updatePanelIndex()
|
|
&& panels[ panelIndex.value ];
|
|
|
|
return props.keepAlive === true
|
|
? [
|
|
vue.h(vue.KeepAlive, keepAliveProps.value, [
|
|
vue.h(
|
|
needsUniqueKeepAliveWrapper.value === true
|
|
? getCacheWithFn(contentKey.value, () => ({ ...PanelWrapper$1, name: contentKey.value }))
|
|
: PanelWrapper$1,
|
|
{ key: contentKey.value, style: transitionStyle.value },
|
|
() => panel
|
|
)
|
|
])
|
|
]
|
|
: [
|
|
vue.h('div', {
|
|
class: 'q-panel scroll',
|
|
style: transitionStyle.value,
|
|
key: contentKey.value,
|
|
role: 'tabpanel'
|
|
}, [ panel ])
|
|
]
|
|
}
|
|
|
|
function getPanelContent () {
|
|
if (panels.length === 0) {
|
|
return
|
|
}
|
|
|
|
return props.animated === true
|
|
? [ vue.h(vue.Transition, { name: panelTransition.value }, getPanelContentChild) ]
|
|
: getPanelContentChild()
|
|
}
|
|
|
|
function updatePanelsList (slots) {
|
|
panels = getNormalizedVNodes(
|
|
hSlot(slots.default, [])
|
|
).filter(
|
|
panel => panel.props !== null
|
|
&& panel.props.slot === void 0
|
|
&& isValidPanelName(panel.props.name) === true
|
|
);
|
|
|
|
return panels.length
|
|
}
|
|
|
|
function getPanels () {
|
|
return panels
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
next: nextPanel,
|
|
previous: previousPanel,
|
|
goTo: goToPanel
|
|
});
|
|
|
|
return {
|
|
panelIndex,
|
|
panelDirectives,
|
|
|
|
updatePanelsList,
|
|
updatePanelIndex,
|
|
|
|
getPanelContent,
|
|
getEnabledPanels,
|
|
getPanels,
|
|
|
|
isValidPanelName,
|
|
|
|
keepAliveProps,
|
|
needsUniqueKeepAliveWrapper,
|
|
|
|
goToPanelByOffset,
|
|
goToPanel,
|
|
|
|
nextPanel,
|
|
previousPanel
|
|
}
|
|
}
|
|
|
|
let counter = 0;
|
|
|
|
const useFullscreenProps = {
|
|
fullscreen: Boolean,
|
|
noRouteFullscreenExit: Boolean
|
|
};
|
|
|
|
const useFullscreenEmits = [ 'update:fullscreen', 'fullscreen' ];
|
|
|
|
function useFullscreen () {
|
|
const vm = vue.getCurrentInstance();
|
|
const { props, emit, proxy } = vm;
|
|
|
|
let historyEntry, fullscreenFillerNode, container;
|
|
const inFullscreen = vue.ref(false);
|
|
|
|
vmHasRouter(vm) === true && vue.watch(() => proxy.$route.fullPath, () => {
|
|
props.noRouteFullscreenExit !== true && exitFullscreen();
|
|
});
|
|
|
|
vue.watch(() => props.fullscreen, v => {
|
|
if (inFullscreen.value !== v) {
|
|
toggleFullscreen();
|
|
}
|
|
});
|
|
|
|
vue.watch(inFullscreen, v => {
|
|
emit('update:fullscreen', v);
|
|
emit('fullscreen', v);
|
|
});
|
|
|
|
function toggleFullscreen () {
|
|
if (inFullscreen.value === true) {
|
|
exitFullscreen();
|
|
}
|
|
else {
|
|
setFullscreen();
|
|
}
|
|
}
|
|
|
|
function setFullscreen () {
|
|
if (inFullscreen.value === true) {
|
|
return
|
|
}
|
|
|
|
inFullscreen.value = true;
|
|
container = proxy.$el.parentNode;
|
|
container.replaceChild(fullscreenFillerNode, proxy.$el);
|
|
document.body.appendChild(proxy.$el);
|
|
|
|
counter++;
|
|
if (counter === 1) {
|
|
document.body.classList.add('q-body--fullscreen-mixin');
|
|
}
|
|
|
|
historyEntry = {
|
|
handler: exitFullscreen
|
|
};
|
|
History.add(historyEntry);
|
|
}
|
|
|
|
function exitFullscreen () {
|
|
if (inFullscreen.value !== true) {
|
|
return
|
|
}
|
|
|
|
if (historyEntry !== void 0) {
|
|
History.remove(historyEntry);
|
|
historyEntry = void 0;
|
|
}
|
|
|
|
container.replaceChild(proxy.$el, fullscreenFillerNode);
|
|
inFullscreen.value = false;
|
|
|
|
counter = Math.max(0, counter - 1);
|
|
|
|
if (counter === 0) {
|
|
document.body.classList.remove('q-body--fullscreen-mixin');
|
|
|
|
if (proxy.$el.scrollIntoView !== void 0) {
|
|
setTimeout(() => { proxy.$el.scrollIntoView(); });
|
|
}
|
|
}
|
|
}
|
|
|
|
vue.onBeforeMount(() => {
|
|
fullscreenFillerNode = document.createElement('span');
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
props.fullscreen === true && setFullscreen();
|
|
});
|
|
|
|
vue.onBeforeUnmount(exitFullscreen);
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
toggleFullscreen,
|
|
setFullscreen,
|
|
exitFullscreen
|
|
});
|
|
|
|
return {
|
|
inFullscreen,
|
|
toggleFullscreen
|
|
}
|
|
}
|
|
|
|
const navigationPositionOptions = [ 'top', 'right', 'bottom', 'left' ];
|
|
const controlTypeOptions = [ 'regular', 'flat', 'outline', 'push', 'unelevated' ];
|
|
|
|
var QCarousel = createComponent({
|
|
name: 'QCarousel',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...usePanelProps,
|
|
...useFullscreenProps,
|
|
|
|
transitionPrev: { // usePanelParentProps override
|
|
type: String,
|
|
default: 'fade'
|
|
},
|
|
transitionNext: { // usePanelParentProps override
|
|
type: String,
|
|
default: 'fade'
|
|
},
|
|
|
|
height: String,
|
|
padding: Boolean,
|
|
|
|
controlColor: String,
|
|
controlTextColor: String,
|
|
controlType: {
|
|
type: String,
|
|
validator: v => controlTypeOptions.includes(v),
|
|
default: 'flat'
|
|
},
|
|
|
|
autoplay: [ Number, Boolean ],
|
|
|
|
arrows: Boolean,
|
|
prevIcon: String,
|
|
nextIcon: String,
|
|
|
|
navigation: Boolean,
|
|
navigationPosition: {
|
|
type: String,
|
|
validator: v => navigationPositionOptions.includes(v)
|
|
},
|
|
navigationIcon: String,
|
|
navigationActiveIcon: String,
|
|
|
|
thumbnails: Boolean
|
|
},
|
|
|
|
emits: [
|
|
...useFullscreenEmits,
|
|
...usePanelEmits
|
|
],
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
let timer = null, panelsLen;
|
|
|
|
const {
|
|
updatePanelsList, getPanelContent,
|
|
panelDirectives, goToPanel,
|
|
previousPanel, nextPanel, getEnabledPanels,
|
|
panelIndex
|
|
} = usePanel();
|
|
|
|
const { inFullscreen } = useFullscreen();
|
|
|
|
const style = vue.computed(() => (
|
|
inFullscreen.value !== true && props.height !== void 0
|
|
? { height: props.height }
|
|
: {}
|
|
));
|
|
|
|
const direction = vue.computed(() => (props.vertical === true ? 'vertical' : 'horizontal'));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-carousel q-panel-parent q-carousel--with${ props.padding === true ? '' : 'out' }-padding`
|
|
+ (inFullscreen.value === true ? ' fullscreen' : '')
|
|
+ (isDark.value === true ? ' q-carousel--dark q-dark' : '')
|
|
+ (props.arrows === true ? ` q-carousel--arrows-${ direction.value }` : '')
|
|
+ (props.navigation === true ? ` q-carousel--navigation-${ navigationPosition.value }` : '')
|
|
);
|
|
|
|
const arrowIcons = vue.computed(() => {
|
|
const ico = [
|
|
props.prevIcon || $q.iconSet.carousel[ props.vertical === true ? 'up' : 'left' ],
|
|
props.nextIcon || $q.iconSet.carousel[ props.vertical === true ? 'down' : 'right' ]
|
|
];
|
|
|
|
return props.vertical === false && $q.lang.rtl === true
|
|
? ico.reverse()
|
|
: ico
|
|
});
|
|
|
|
const navIcon = vue.computed(() => props.navigationIcon || $q.iconSet.carousel.navigationIcon);
|
|
const navActiveIcon = vue.computed(() => props.navigationActiveIcon || navIcon.value);
|
|
const navigationPosition = vue.computed(() => props.navigationPosition
|
|
|| (props.vertical === true ? 'right' : 'bottom')
|
|
);
|
|
|
|
const controlProps = vue.computed(() => ({
|
|
color: props.controlColor,
|
|
textColor: props.controlTextColor,
|
|
round: true,
|
|
[ props.controlType ]: true,
|
|
dense: true
|
|
}));
|
|
|
|
vue.watch(() => props.modelValue, () => {
|
|
if (props.autoplay) {
|
|
startTimer();
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.autoplay, val => {
|
|
if (val) {
|
|
startTimer();
|
|
}
|
|
else if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
});
|
|
|
|
function startTimer () {
|
|
const duration = isNumber(props.autoplay) === true
|
|
? Math.abs(props.autoplay)
|
|
: 5000;
|
|
|
|
timer !== null && clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
|
|
if (duration >= 0) {
|
|
nextPanel();
|
|
}
|
|
else {
|
|
previousPanel();
|
|
}
|
|
}, duration);
|
|
}
|
|
|
|
vue.onMounted(() => {
|
|
props.autoplay && startTimer();
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
timer !== null && clearTimeout(timer);
|
|
});
|
|
|
|
function getNavigationContainer (type, mapping) {
|
|
return vue.h('div', {
|
|
class: 'q-carousel__control q-carousel__navigation no-wrap absolute flex'
|
|
+ ` q-carousel__navigation--${ type } q-carousel__navigation--${ navigationPosition.value }`
|
|
+ (props.controlColor !== void 0 ? ` text-${ props.controlColor }` : '')
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-carousel__navigation-inner flex flex-center no-wrap'
|
|
}, getEnabledPanels().map(mapping))
|
|
])
|
|
}
|
|
|
|
function getContent () {
|
|
const node = [];
|
|
|
|
if (props.navigation === true) {
|
|
const fn = slots[ 'navigation-icon' ] !== void 0
|
|
? slots[ 'navigation-icon' ]
|
|
: opts => vue.h(QBtn, {
|
|
key: 'nav' + opts.name,
|
|
class: `q-carousel__navigation-icon q-carousel__navigation-icon--${ opts.active === true ? '' : 'in' }active`,
|
|
...opts.btnProps,
|
|
onClick: opts.onClick
|
|
});
|
|
|
|
const maxIndex = panelsLen - 1;
|
|
node.push(
|
|
getNavigationContainer('buttons', (panel, index) => {
|
|
const name = panel.props.name;
|
|
const active = panelIndex.value === index;
|
|
|
|
return fn({
|
|
index,
|
|
maxIndex,
|
|
name,
|
|
active,
|
|
btnProps: {
|
|
icon: active === true ? navActiveIcon.value : navIcon.value,
|
|
size: 'sm',
|
|
...controlProps.value
|
|
},
|
|
onClick: () => { goToPanel(name); }
|
|
})
|
|
})
|
|
);
|
|
}
|
|
else if (props.thumbnails === true) {
|
|
const color = props.controlColor !== void 0
|
|
? ` text-${ props.controlColor }`
|
|
: '';
|
|
|
|
node.push(getNavigationContainer('thumbnails', panel => {
|
|
const slide = panel.props;
|
|
|
|
return vue.h('img', {
|
|
key: 'tmb#' + slide.name,
|
|
class: `q-carousel__thumbnail q-carousel__thumbnail--${ slide.name === props.modelValue ? '' : 'in' }active` + color,
|
|
src: slide.imgSrc || slide[ 'img-src' ],
|
|
onClick: () => { goToPanel(slide.name); }
|
|
})
|
|
}));
|
|
}
|
|
|
|
if (props.arrows === true && panelIndex.value >= 0) {
|
|
if (props.infinite === true || panelIndex.value > 0) {
|
|
node.push(
|
|
vue.h('div', {
|
|
key: 'prev',
|
|
class: `q-carousel__control q-carousel__arrow q-carousel__prev-arrow q-carousel__prev-arrow--${ direction.value } absolute flex flex-center`
|
|
}, [
|
|
vue.h(QBtn, {
|
|
icon: arrowIcons.value[ 0 ],
|
|
...controlProps.value,
|
|
onClick: previousPanel
|
|
})
|
|
])
|
|
);
|
|
}
|
|
|
|
if (props.infinite === true || panelIndex.value < panelsLen - 1) {
|
|
node.push(
|
|
vue.h('div', {
|
|
key: 'next',
|
|
class: 'q-carousel__control q-carousel__arrow q-carousel__next-arrow'
|
|
+ ` q-carousel__next-arrow--${ direction.value } absolute flex flex-center`
|
|
}, [
|
|
vue.h(QBtn, {
|
|
icon: arrowIcons.value[ 1 ],
|
|
...controlProps.value,
|
|
onClick: nextPanel
|
|
})
|
|
])
|
|
);
|
|
}
|
|
}
|
|
|
|
return hMergeSlot(slots.control, node)
|
|
}
|
|
|
|
return () => {
|
|
panelsLen = updatePanelsList(slots);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value
|
|
}, [
|
|
hDir(
|
|
'div',
|
|
{ class: 'q-carousel__slides-container' },
|
|
getPanelContent(),
|
|
'sl-cont',
|
|
props.swipeable,
|
|
() => panelDirectives.value
|
|
)
|
|
].concat(getContent()))
|
|
}
|
|
}
|
|
});
|
|
|
|
var QCarouselSlide = createComponent({
|
|
name: 'QCarouselSlide',
|
|
|
|
props: {
|
|
...usePanelChildProps,
|
|
imgSrc: String
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const style = vue.computed(() => (
|
|
props.imgSrc
|
|
? { backgroundImage: `url("${ props.imgSrc }")` }
|
|
: {}
|
|
));
|
|
|
|
return () => vue.h('div', {
|
|
class: 'q-carousel__slide',
|
|
style: style.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QCarouselControl = createComponent({
|
|
name: 'QCarouselControl',
|
|
|
|
props: {
|
|
position: {
|
|
type: String,
|
|
default: 'bottom-right',
|
|
validator: v => [
|
|
'top-right', 'top-left',
|
|
'bottom-right', 'bottom-left',
|
|
'top', 'right', 'bottom', 'left'
|
|
].includes(v)
|
|
},
|
|
offset: {
|
|
type: Array,
|
|
default: () => [ 18, 18 ],
|
|
validator: v => v.length === 2
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() => `q-carousel__control absolute absolute-${ props.position }`);
|
|
const style = vue.computed(() => ({
|
|
margin: `${ props.offset[ 1 ] }px ${ props.offset[ 0 ] }px`
|
|
}));
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QChatMessage = createComponent({
|
|
name: 'QChatMessage',
|
|
|
|
props: {
|
|
sent: Boolean,
|
|
label: String,
|
|
bgColor: String,
|
|
textColor: String,
|
|
name: String,
|
|
avatar: String,
|
|
text: Array,
|
|
stamp: String,
|
|
size: String,
|
|
labelHtml: Boolean,
|
|
nameHtml: Boolean,
|
|
textHtml: Boolean,
|
|
stampHtml: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const op = vue.computed(() => (props.sent === true ? 'sent' : 'received'));
|
|
|
|
const textClass = vue.computed(() =>
|
|
`q-message-text-content q-message-text-content--${ op.value }`
|
|
+ (props.textColor !== void 0 ? ` text-${ props.textColor }` : '')
|
|
);
|
|
|
|
const messageClass = vue.computed(() =>
|
|
`q-message-text q-message-text--${ op.value }`
|
|
+ (props.bgColor !== void 0 ? ` text-${ props.bgColor }` : '')
|
|
);
|
|
|
|
const containerClass = vue.computed(() =>
|
|
'q-message-container row items-end no-wrap'
|
|
+ (props.sent === true ? ' reverse' : '')
|
|
);
|
|
|
|
const sizeClass = vue.computed(() => (props.size !== void 0 ? `col-${ props.size }` : ''));
|
|
|
|
const domProps = vue.computed(() => ({
|
|
msg: props.textHtml === true ? 'innerHTML' : 'textContent',
|
|
stamp: props.stampHtml === true ? 'innerHTML' : 'textContent',
|
|
name: props.nameHtml === true ? 'innerHTML' : 'textContent',
|
|
label: props.labelHtml === true ? 'innerHTML' : 'textContent'
|
|
}));
|
|
|
|
function wrapStamp (node) {
|
|
if (slots.stamp !== void 0) {
|
|
return [ node, vue.h('div', { class: 'q-message-stamp' }, slots.stamp()) ]
|
|
}
|
|
|
|
if (props.stamp) {
|
|
return [
|
|
node,
|
|
vue.h('div', {
|
|
class: 'q-message-stamp',
|
|
[ domProps.value.stamp ]: props.stamp
|
|
})
|
|
]
|
|
}
|
|
|
|
return [ node ]
|
|
}
|
|
|
|
function getText (contentList, withSlots) {
|
|
const content = withSlots === true
|
|
? (contentList.length > 1 ? text => text : text => vue.h('div', [ text ]))
|
|
: text => vue.h('div', { [ domProps.value.msg ]: text });
|
|
|
|
return contentList.map((msg, index) => vue.h('div', {
|
|
key: index,
|
|
class: messageClass.value
|
|
}, [
|
|
vue.h('div', { class: textClass.value }, wrapStamp(content(msg)))
|
|
]))
|
|
}
|
|
|
|
return () => {
|
|
const container = [];
|
|
|
|
if (slots.avatar !== void 0) {
|
|
container.push(slots.avatar());
|
|
}
|
|
else if (props.avatar !== void 0) {
|
|
container.push(
|
|
vue.h('img', {
|
|
class: `q-message-avatar q-message-avatar--${ op.value }`,
|
|
src: props.avatar,
|
|
'aria-hidden': 'true'
|
|
})
|
|
);
|
|
}
|
|
|
|
const msg = [];
|
|
|
|
if (slots.name !== void 0) {
|
|
msg.push(
|
|
vue.h('div', { class: `q-message-name q-message-name--${ op.value }` }, slots.name())
|
|
);
|
|
}
|
|
else if (props.name !== void 0) {
|
|
msg.push(
|
|
vue.h('div', {
|
|
class: `q-message-name q-message-name--${ op.value }`,
|
|
[ domProps.value.name ]: props.name
|
|
})
|
|
);
|
|
}
|
|
|
|
if (slots.default !== void 0) {
|
|
msg.push(
|
|
getText(
|
|
getNormalizedVNodes(slots.default()),
|
|
true
|
|
)
|
|
);
|
|
}
|
|
else if (props.text !== void 0) {
|
|
msg.push(getText(props.text));
|
|
}
|
|
|
|
container.push(
|
|
vue.h('div', { class: sizeClass.value }, msg)
|
|
);
|
|
|
|
const child = [];
|
|
|
|
if (slots.label !== void 0) {
|
|
child.push(
|
|
vue.h('div', { class: 'q-message-label' }, slots.label())
|
|
);
|
|
}
|
|
else if (props.label !== void 0) {
|
|
child.push(
|
|
vue.h('div', {
|
|
class: 'q-message-label',
|
|
[ domProps.value.label ]: props.label
|
|
})
|
|
);
|
|
}
|
|
|
|
child.push(
|
|
vue.h('div', { class: containerClass.value }, container)
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: `q-message q-message-${ op.value }`
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
function useRefocusTarget (props, rootRef) {
|
|
const refocusRef = vue.ref(null);
|
|
|
|
const refocusTargetEl = vue.computed(() => {
|
|
if (props.disable === true) {
|
|
return null
|
|
}
|
|
|
|
return vue.h('span', {
|
|
ref: refocusRef,
|
|
class: 'no-outline',
|
|
tabindex: -1
|
|
})
|
|
});
|
|
|
|
function refocusTarget (e) {
|
|
const root = rootRef.value;
|
|
|
|
if (e !== void 0 && e.type.indexOf('key') === 0) {
|
|
if (
|
|
root !== null
|
|
&& document.activeElement !== root
|
|
&& root.contains(document.activeElement) === true
|
|
) {
|
|
root.focus();
|
|
}
|
|
}
|
|
else if (
|
|
refocusRef.value !== null
|
|
&& (e === void 0 || (root !== null && root.contains(e.target) === true))
|
|
) {
|
|
refocusRef.value.focus();
|
|
}
|
|
}
|
|
|
|
return {
|
|
refocusTargetEl,
|
|
refocusTarget
|
|
}
|
|
}
|
|
|
|
var optionSizes = {
|
|
xs: 30,
|
|
sm: 35,
|
|
md: 40,
|
|
lg: 50,
|
|
xl: 60
|
|
};
|
|
|
|
const useCheckboxProps = {
|
|
...useDarkProps,
|
|
...useSizeProps,
|
|
...useFormProps,
|
|
|
|
modelValue: {
|
|
required: true,
|
|
default: null
|
|
},
|
|
val: {},
|
|
|
|
trueValue: { default: true },
|
|
falseValue: { default: false },
|
|
indeterminateValue: { default: null },
|
|
|
|
checkedIcon: String,
|
|
uncheckedIcon: String,
|
|
indeterminateIcon: String,
|
|
|
|
toggleOrder: {
|
|
type: String,
|
|
validator: v => v === 'tf' || v === 'ft'
|
|
},
|
|
toggleIndeterminate: Boolean,
|
|
|
|
label: String,
|
|
leftLabel: Boolean,
|
|
|
|
color: String,
|
|
keepColor: Boolean,
|
|
dense: Boolean,
|
|
|
|
disable: Boolean,
|
|
tabindex: [ String, Number ]
|
|
};
|
|
|
|
const useCheckboxEmits = [ 'update:modelValue' ];
|
|
|
|
function useCheckbox (type, getInner) {
|
|
const { props, slots, emit, proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const { refocusTargetEl, refocusTarget } = useRefocusTarget(props, rootRef);
|
|
const sizeStyle = useSize(props, optionSizes);
|
|
|
|
const modelIsArray = vue.computed(() =>
|
|
props.val !== void 0 && Array.isArray(props.modelValue)
|
|
);
|
|
|
|
const index = vue.computed(() => {
|
|
const val = vue.toRaw(props.val);
|
|
return modelIsArray.value === true
|
|
? props.modelValue.findIndex(opt => vue.toRaw(opt) === val)
|
|
: -1
|
|
});
|
|
|
|
const isTrue = vue.computed(() => (
|
|
modelIsArray.value === true
|
|
? index.value > -1
|
|
: vue.toRaw(props.modelValue) === vue.toRaw(props.trueValue)
|
|
));
|
|
|
|
const isFalse = vue.computed(() => (
|
|
modelIsArray.value === true
|
|
? index.value === -1
|
|
: vue.toRaw(props.modelValue) === vue.toRaw(props.falseValue)
|
|
));
|
|
|
|
const isIndeterminate = vue.computed(() =>
|
|
isTrue.value === false && isFalse.value === false
|
|
);
|
|
|
|
const tabindex = vue.computed(() => (
|
|
props.disable === true ? -1 : props.tabindex || 0
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-${ type } cursor-pointer no-outline row inline no-wrap items-center`
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (isDark.value === true ? ` q-${ type }--dark` : '')
|
|
+ (props.dense === true ? ` q-${ type }--dense` : '')
|
|
+ (props.leftLabel === true ? ' reverse' : '')
|
|
);
|
|
|
|
const innerClass = vue.computed(() => {
|
|
const state = isTrue.value === true ? 'truthy' : (isFalse.value === true ? 'falsy' : 'indet');
|
|
const color = props.color !== void 0 && (
|
|
props.keepColor === true
|
|
|| (type === 'toggle' ? isTrue.value === true : isFalse.value !== true)
|
|
)
|
|
? ` text-${ props.color }`
|
|
: '';
|
|
|
|
return `q-${ type }__inner relative-position non-selectable q-${ type }__inner--${ state }${ color }`
|
|
});
|
|
|
|
const formAttrs = vue.computed(() => {
|
|
const prop = { type: 'checkbox' };
|
|
|
|
props.name !== void 0 && Object.assign(prop, {
|
|
// see https://vuejs.org/guide/extras/render-function.html#creating-vnodes (.prop)
|
|
'.checked': isTrue.value,
|
|
'^checked': isTrue.value === true ? 'checked' : void 0,
|
|
name: props.name,
|
|
value: modelIsArray.value === true
|
|
? props.val
|
|
: props.trueValue
|
|
});
|
|
|
|
return prop
|
|
});
|
|
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const attributes = vue.computed(() => {
|
|
const attrs = {
|
|
tabindex: tabindex.value,
|
|
role: type === 'toggle' ? 'switch' : 'checkbox',
|
|
'aria-label': props.label,
|
|
'aria-checked': isIndeterminate.value === true
|
|
? 'mixed'
|
|
: (isTrue.value === true ? 'true' : 'false')
|
|
};
|
|
|
|
if (props.disable === true) {
|
|
attrs[ 'aria-disabled' ] = 'true';
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
function onClick (e) {
|
|
if (e !== void 0) {
|
|
stopAndPrevent(e);
|
|
refocusTarget(e);
|
|
}
|
|
|
|
if (props.disable !== true) {
|
|
emit('update:modelValue', getNextValue(), e);
|
|
}
|
|
}
|
|
|
|
function getNextValue () {
|
|
if (modelIsArray.value === true) {
|
|
if (isTrue.value === true) {
|
|
const val = props.modelValue.slice();
|
|
val.splice(index.value, 1);
|
|
return val
|
|
}
|
|
|
|
return props.modelValue.concat([ props.val ])
|
|
}
|
|
|
|
if (isTrue.value === true) {
|
|
if (props.toggleOrder !== 'ft' || props.toggleIndeterminate === false) {
|
|
return props.falseValue
|
|
}
|
|
}
|
|
else if (isFalse.value === true) {
|
|
if (props.toggleOrder === 'ft' || props.toggleIndeterminate === false) {
|
|
return props.trueValue
|
|
}
|
|
}
|
|
else {
|
|
return props.toggleOrder !== 'ft'
|
|
? props.trueValue
|
|
: props.falseValue
|
|
}
|
|
|
|
return props.indeterminateValue
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
stopAndPrevent(e);
|
|
}
|
|
}
|
|
|
|
function onKeyup (e) {
|
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
onClick(e);
|
|
}
|
|
}
|
|
|
|
const getInnerContent = getInner(isTrue, isIndeterminate);
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { toggle: onClick });
|
|
|
|
return () => {
|
|
const inner = getInnerContent();
|
|
|
|
props.disable !== true && injectFormInput(
|
|
inner,
|
|
'unshift',
|
|
` q-${ type }__native absolute q-ma-none q-pa-none`
|
|
);
|
|
|
|
const child = [
|
|
vue.h('div', {
|
|
class: innerClass.value,
|
|
style: sizeStyle.value,
|
|
'aria-hidden': 'true'
|
|
}, inner)
|
|
];
|
|
|
|
if (refocusTargetEl.value !== null) {
|
|
child.push(refocusTargetEl.value);
|
|
}
|
|
|
|
const label = props.label !== void 0
|
|
? hMergeSlot(slots.default, [ props.label ])
|
|
: hSlot(slots.default);
|
|
|
|
label !== void 0 && child.push(
|
|
vue.h('div', {
|
|
class: `q-${ type }__label q-anchor--skip`
|
|
}, label)
|
|
);
|
|
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
...attributes.value,
|
|
onClick,
|
|
onKeydown,
|
|
onKeyup
|
|
}, child)
|
|
}
|
|
}
|
|
|
|
const bgNode = vue.h('div', {
|
|
key: 'svg',
|
|
class: 'q-checkbox__bg absolute'
|
|
}, [
|
|
vue.h('svg', {
|
|
class: 'q-checkbox__svg fit absolute-full',
|
|
viewBox: '0 0 24 24'
|
|
}, [
|
|
vue.h('path', {
|
|
class: 'q-checkbox__truthy',
|
|
fill: 'none',
|
|
d: 'M1.73,12.91 8.1,19.28 22.79,4.59'
|
|
}),
|
|
|
|
vue.h('path', {
|
|
class: 'q-checkbox__indet',
|
|
d: 'M4,14H20V10H4'
|
|
})
|
|
])
|
|
]);
|
|
|
|
var QCheckbox = createComponent({
|
|
name: 'QCheckbox',
|
|
|
|
props: useCheckboxProps,
|
|
emits: useCheckboxEmits,
|
|
|
|
setup (props) {
|
|
function getInner (isTrue, isIndeterminate) {
|
|
const icon = vue.computed(() =>
|
|
(isTrue.value === true
|
|
? props.checkedIcon
|
|
: (isIndeterminate.value === true
|
|
? props.indeterminateIcon
|
|
: props.uncheckedIcon
|
|
)
|
|
) || null
|
|
);
|
|
|
|
return () => (
|
|
icon.value !== null
|
|
? [
|
|
vue.h('div', {
|
|
key: 'icon',
|
|
class: 'q-checkbox__icon-container absolute-full flex flex-center no-wrap'
|
|
}, [
|
|
vue.h(QIcon, {
|
|
class: 'q-checkbox__icon',
|
|
name: icon.value
|
|
})
|
|
])
|
|
]
|
|
: [ bgNode ]
|
|
)
|
|
}
|
|
|
|
return useCheckbox('checkbox', getInner)
|
|
}
|
|
});
|
|
|
|
const defaultSizes$1 = {
|
|
xs: 8,
|
|
sm: 10,
|
|
md: 14,
|
|
lg: 20,
|
|
xl: 24
|
|
};
|
|
|
|
var QChip = createComponent({
|
|
name: 'QChip',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useSizeProps,
|
|
|
|
dense: Boolean,
|
|
|
|
icon: String,
|
|
iconRight: String,
|
|
iconRemove: String,
|
|
iconSelected: String,
|
|
label: [ String, Number ],
|
|
|
|
color: String,
|
|
textColor: String,
|
|
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
selected: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
square: Boolean,
|
|
outline: Boolean,
|
|
clickable: Boolean,
|
|
removable: Boolean,
|
|
|
|
removeAriaLabel: String,
|
|
|
|
tabindex: [ String, Number ],
|
|
disable: Boolean,
|
|
|
|
ripple: {
|
|
type: [ Boolean, Object ],
|
|
default: true
|
|
}
|
|
},
|
|
|
|
emits: [ 'update:modelValue', 'update:selected', 'remove', 'click' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, $q);
|
|
const sizeStyle = useSize(props, defaultSizes$1);
|
|
|
|
const hasLeftIcon = vue.computed(() => props.selected === true || props.icon !== void 0);
|
|
|
|
const leftIcon = vue.computed(() => (
|
|
props.selected === true
|
|
? props.iconSelected || $q.iconSet.chip.selected
|
|
: props.icon
|
|
));
|
|
|
|
const removeIcon = vue.computed(() => props.iconRemove || $q.iconSet.chip.remove);
|
|
|
|
const isClickable = vue.computed(() =>
|
|
props.disable === false
|
|
&& (props.clickable === true || props.selected !== null)
|
|
);
|
|
|
|
const classes = vue.computed(() => {
|
|
const text = props.outline === true
|
|
? props.color || props.textColor
|
|
: props.textColor;
|
|
|
|
return 'q-chip row inline no-wrap items-center'
|
|
+ (props.outline === false && props.color !== void 0 ? ` bg-${ props.color }` : '')
|
|
+ (text ? ` text-${ text } q-chip--colored` : '')
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (props.dense === true ? ' q-chip--dense' : '')
|
|
+ (props.outline === true ? ' q-chip--outline' : '')
|
|
+ (props.selected === true ? ' q-chip--selected' : '')
|
|
+ (isClickable.value === true ? ' q-chip--clickable cursor-pointer non-selectable q-hoverable' : '')
|
|
+ (props.square === true ? ' q-chip--square' : '')
|
|
+ (isDark.value === true ? ' q-chip--dark q-dark' : '')
|
|
});
|
|
|
|
const attributes = vue.computed(() => {
|
|
const chip = props.disable === true
|
|
? { tabindex: -1, 'aria-disabled': 'true' }
|
|
: { tabindex: props.tabindex || 0 };
|
|
const remove = {
|
|
...chip,
|
|
role: 'button',
|
|
'aria-hidden': 'false',
|
|
'aria-label': props.removeAriaLabel || $q.lang.label.remove
|
|
};
|
|
|
|
return { chip, remove }
|
|
});
|
|
|
|
function onKeyup (e) {
|
|
e.keyCode === 13 /* ENTER */ && onClick(e);
|
|
}
|
|
|
|
function onClick (e) {
|
|
if (!props.disable) {
|
|
emit('update:selected', !props.selected);
|
|
emit('click', e);
|
|
}
|
|
}
|
|
|
|
function onRemove (e) {
|
|
if (e.keyCode === void 0 || e.keyCode === 13) {
|
|
stopAndPrevent(e);
|
|
if (props.disable === false) {
|
|
emit('update:modelValue', false);
|
|
emit('remove');
|
|
}
|
|
}
|
|
}
|
|
|
|
function getContent () {
|
|
const child = [];
|
|
|
|
isClickable.value === true && child.push(
|
|
vue.h('div', { class: 'q-focus-helper' })
|
|
);
|
|
|
|
hasLeftIcon.value === true && child.push(
|
|
vue.h(QIcon, {
|
|
class: 'q-chip__icon q-chip__icon--left',
|
|
name: leftIcon.value
|
|
})
|
|
);
|
|
|
|
const label = props.label !== void 0
|
|
? [ vue.h('div', { class: 'ellipsis' }, [ props.label ]) ]
|
|
: void 0;
|
|
|
|
child.push(
|
|
vue.h('div', {
|
|
class: 'q-chip__content col row no-wrap items-center q-anchor--skip'
|
|
}, hMergeSlotSafely(slots.default, label))
|
|
);
|
|
|
|
props.iconRight && child.push(
|
|
vue.h(QIcon, {
|
|
class: 'q-chip__icon q-chip__icon--right',
|
|
name: props.iconRight
|
|
})
|
|
);
|
|
|
|
props.removable === true && child.push(
|
|
vue.h(QIcon, {
|
|
class: 'q-chip__icon q-chip__icon--remove cursor-pointer',
|
|
name: removeIcon.value,
|
|
...attributes.value.remove,
|
|
onClick: onRemove,
|
|
onKeyup: onRemove
|
|
})
|
|
);
|
|
|
|
return child
|
|
}
|
|
|
|
return () => {
|
|
if (props.modelValue === false) { return }
|
|
|
|
const data = {
|
|
class: classes.value,
|
|
style: sizeStyle.value
|
|
};
|
|
|
|
isClickable.value === true && Object.assign(
|
|
data,
|
|
attributes.value.chip,
|
|
{ onClick, onKeyup }
|
|
);
|
|
|
|
return hDir(
|
|
'div',
|
|
data,
|
|
getContent(),
|
|
'ripple',
|
|
props.ripple !== false && props.disable !== true,
|
|
() => [ [ Ripple, props.ripple ] ]
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
// also used by QKnob
|
|
const useCircularCommonProps = {
|
|
...useSizeProps,
|
|
|
|
min: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
max: {
|
|
type: Number,
|
|
default: 100
|
|
},
|
|
|
|
color: String,
|
|
centerColor: String,
|
|
trackColor: String,
|
|
|
|
fontSize: String,
|
|
rounded: Boolean,
|
|
|
|
// ratio
|
|
thickness: {
|
|
type: Number,
|
|
default: 0.2,
|
|
validator: v => v >= 0 && v <= 1
|
|
},
|
|
|
|
angle: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
|
|
showValue: Boolean,
|
|
reverse: Boolean,
|
|
|
|
instantFeedback: Boolean
|
|
};
|
|
|
|
const
|
|
radius = 50,
|
|
diameter = 2 * radius,
|
|
circumference = diameter * Math.PI,
|
|
strokeDashArray = Math.round(circumference * 1000) / 1000;
|
|
|
|
var QCircularProgress = createComponent({
|
|
name: 'QCircularProgress',
|
|
|
|
props: {
|
|
...useCircularCommonProps,
|
|
|
|
value: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
|
|
animationSpeed: {
|
|
type: [ String, Number ],
|
|
default: 600
|
|
},
|
|
|
|
indeterminate: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const sizeStyle = useSize(props);
|
|
|
|
const svgStyle = vue.computed(() => {
|
|
const angle = ($q.lang.rtl === true ? -1 : 1) * props.angle;
|
|
|
|
return {
|
|
transform: props.reverse !== ($q.lang.rtl === true)
|
|
? `scale3d(-1, 1, 1) rotate3d(0, 0, 1, ${ -90 - angle }deg)`
|
|
: `rotate3d(0, 0, 1, ${ angle - 90 }deg)`
|
|
}
|
|
});
|
|
|
|
const circleStyle = vue.computed(() => (
|
|
props.instantFeedback !== true && props.indeterminate !== true
|
|
? { transition: `stroke-dashoffset ${ props.animationSpeed }ms ease 0s, stroke ${ props.animationSpeed }ms ease` }
|
|
: ''
|
|
));
|
|
|
|
const viewBox = vue.computed(() => diameter / (1 - props.thickness / 2));
|
|
|
|
const viewBoxAttr = vue.computed(() =>
|
|
`${ viewBox.value / 2 } ${ viewBox.value / 2 } ${ viewBox.value } ${ viewBox.value }`
|
|
);
|
|
|
|
const normalized = vue.computed(() => between(props.value, props.min, props.max));
|
|
|
|
const strokeDashOffset = vue.computed(() => circumference * (
|
|
1 - (normalized.value - props.min) / (props.max - props.min)
|
|
));
|
|
|
|
const strokeWidth = vue.computed(() => props.thickness / 2 * viewBox.value);
|
|
|
|
function getCircle ({ thickness, offset, color, cls, rounded }) {
|
|
return vue.h('circle', {
|
|
class: 'q-circular-progress__' + cls + (color !== void 0 ? ` text-${ color }` : ''),
|
|
style: circleStyle.value,
|
|
fill: 'transparent',
|
|
stroke: 'currentColor',
|
|
'stroke-width': thickness,
|
|
'stroke-dasharray': strokeDashArray,
|
|
'stroke-dashoffset': offset,
|
|
'stroke-linecap': rounded,
|
|
cx: viewBox.value,
|
|
cy: viewBox.value,
|
|
r: radius
|
|
})
|
|
}
|
|
|
|
return () => {
|
|
const svgChild = [];
|
|
|
|
props.centerColor !== void 0 && props.centerColor !== 'transparent' && svgChild.push(
|
|
vue.h('circle', {
|
|
class: `q-circular-progress__center text-${ props.centerColor }`,
|
|
fill: 'currentColor',
|
|
r: radius - strokeWidth.value / 2,
|
|
cx: viewBox.value,
|
|
cy: viewBox.value
|
|
})
|
|
);
|
|
|
|
props.trackColor !== void 0 && props.trackColor !== 'transparent' && svgChild.push(
|
|
getCircle({
|
|
cls: 'track',
|
|
thickness: strokeWidth.value,
|
|
offset: 0,
|
|
color: props.trackColor
|
|
})
|
|
);
|
|
|
|
svgChild.push(
|
|
getCircle({
|
|
cls: 'circle',
|
|
thickness: strokeWidth.value,
|
|
offset: strokeDashOffset.value,
|
|
color: props.color,
|
|
rounded: props.rounded === true ? 'round' : void 0
|
|
})
|
|
);
|
|
|
|
const child = [
|
|
vue.h('svg', {
|
|
class: 'q-circular-progress__svg',
|
|
style: svgStyle.value,
|
|
viewBox: viewBoxAttr.value,
|
|
'aria-hidden': 'true'
|
|
}, svgChild)
|
|
];
|
|
|
|
props.showValue === true && child.push(
|
|
vue.h('div', {
|
|
class: 'q-circular-progress__text absolute-full row flex-center content-center',
|
|
style: { fontSize: props.fontSize }
|
|
}, slots.default !== void 0 ? slots.default() : [ vue.h('div', normalized.value) ])
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: `q-circular-progress q-circular-progress--${ props.indeterminate === true ? 'in' : '' }determinate`,
|
|
style: sizeStyle.value,
|
|
role: 'progressbar',
|
|
'aria-valuemin': props.min,
|
|
'aria-valuemax': props.max,
|
|
'aria-valuenow': props.indeterminate === true ? void 0 : normalized.value
|
|
}, hMergeSlotSafely(slots.internal, child)) // "internal" is used by QKnob
|
|
}
|
|
}
|
|
});
|
|
|
|
function getChanges (evt, ctx, isFinal) {
|
|
const pos = position(evt);
|
|
let
|
|
dir,
|
|
distX = pos.left - ctx.event.x,
|
|
distY = pos.top - ctx.event.y,
|
|
absX = Math.abs(distX),
|
|
absY = Math.abs(distY);
|
|
|
|
const direction = ctx.direction;
|
|
|
|
if (direction.horizontal === true && direction.vertical !== true) {
|
|
dir = distX < 0 ? 'left' : 'right';
|
|
}
|
|
else if (direction.horizontal !== true && direction.vertical === true) {
|
|
dir = distY < 0 ? 'up' : 'down';
|
|
}
|
|
else if (direction.up === true && distY < 0) {
|
|
dir = 'up';
|
|
if (absX > absY) {
|
|
if (direction.left === true && distX < 0) {
|
|
dir = 'left';
|
|
}
|
|
else if (direction.right === true && distX > 0) {
|
|
dir = 'right';
|
|
}
|
|
}
|
|
}
|
|
else if (direction.down === true && distY > 0) {
|
|
dir = 'down';
|
|
if (absX > absY) {
|
|
if (direction.left === true && distX < 0) {
|
|
dir = 'left';
|
|
}
|
|
else if (direction.right === true && distX > 0) {
|
|
dir = 'right';
|
|
}
|
|
}
|
|
}
|
|
else if (direction.left === true && distX < 0) {
|
|
dir = 'left';
|
|
if (absX < absY) {
|
|
if (direction.up === true && distY < 0) {
|
|
dir = 'up';
|
|
}
|
|
else if (direction.down === true && distY > 0) {
|
|
dir = 'down';
|
|
}
|
|
}
|
|
}
|
|
else if (direction.right === true && distX > 0) {
|
|
dir = 'right';
|
|
if (absX < absY) {
|
|
if (direction.up === true && distY < 0) {
|
|
dir = 'up';
|
|
}
|
|
else if (direction.down === true && distY > 0) {
|
|
dir = 'down';
|
|
}
|
|
}
|
|
}
|
|
|
|
let synthetic = false;
|
|
|
|
if (dir === void 0 && isFinal === false) {
|
|
if (ctx.event.isFirst === true || ctx.event.lastDir === void 0) {
|
|
return {}
|
|
}
|
|
|
|
dir = ctx.event.lastDir;
|
|
synthetic = true;
|
|
|
|
if (dir === 'left' || dir === 'right') {
|
|
pos.left -= distX;
|
|
absX = 0;
|
|
distX = 0;
|
|
}
|
|
else {
|
|
pos.top -= distY;
|
|
absY = 0;
|
|
distY = 0;
|
|
}
|
|
}
|
|
|
|
return {
|
|
synthetic,
|
|
payload: {
|
|
evt,
|
|
touch: ctx.event.mouse !== true,
|
|
mouse: ctx.event.mouse === true,
|
|
position: pos,
|
|
direction: dir,
|
|
isFirst: ctx.event.isFirst,
|
|
isFinal: isFinal === true,
|
|
duration: Date.now() - ctx.event.time,
|
|
distance: {
|
|
x: absX,
|
|
y: absY
|
|
},
|
|
offset: {
|
|
x: distX,
|
|
y: distY
|
|
},
|
|
delta: {
|
|
x: pos.left - ctx.event.lastX,
|
|
y: pos.top - ctx.event.lastY
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let uid$2 = 0;
|
|
|
|
var TouchPan = createDirective({
|
|
name: 'touch-pan',
|
|
|
|
beforeMount (el, { value, modifiers }) {
|
|
// early return, we don't need to do anything
|
|
if (modifiers.mouse !== true && client.has.touch !== true) {
|
|
return
|
|
}
|
|
|
|
function handleEvent (evt, mouseEvent) {
|
|
if (modifiers.mouse === true && mouseEvent === true) {
|
|
stopAndPrevent(evt);
|
|
}
|
|
else {
|
|
modifiers.stop === true && stop(evt);
|
|
modifiers.prevent === true && prevent(evt);
|
|
}
|
|
}
|
|
|
|
const ctx = {
|
|
uid: 'qvtp_' + (uid$2++),
|
|
handler: value,
|
|
modifiers,
|
|
direction: getModifierDirections(modifiers),
|
|
|
|
noop,
|
|
|
|
mouseStart (evt) {
|
|
if (shouldStart(evt, ctx) && leftClick(evt)) {
|
|
addEvt(ctx, 'temp', [
|
|
[ document, 'mousemove', 'move', 'notPassiveCapture' ],
|
|
[ document, 'mouseup', 'end', 'passiveCapture' ]
|
|
]);
|
|
|
|
ctx.start(evt, true);
|
|
}
|
|
},
|
|
|
|
touchStart (evt) {
|
|
if (shouldStart(evt, ctx)) {
|
|
const target = evt.target;
|
|
|
|
addEvt(ctx, 'temp', [
|
|
[ target, 'touchmove', 'move', 'notPassiveCapture' ],
|
|
[ target, 'touchcancel', 'end', 'passiveCapture' ],
|
|
[ target, 'touchend', 'end', 'passiveCapture' ]
|
|
]);
|
|
|
|
ctx.start(evt);
|
|
}
|
|
},
|
|
|
|
start (evt, mouseEvent) {
|
|
client.is.firefox === true && preventDraggable(el, true);
|
|
ctx.lastEvt = evt;
|
|
|
|
/*
|
|
* Stop propagation so possible upper v-touch-pan don't catch this as well;
|
|
* If we're not the target (based on modifiers), we'll re-emit the event later
|
|
*/
|
|
if (mouseEvent === true || modifiers.stop === true) {
|
|
/*
|
|
* are we directly switching to detected state?
|
|
* clone event only otherwise
|
|
*/
|
|
if (
|
|
ctx.direction.all !== true
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
&& (mouseEvent !== true || (ctx.modifiers.mouseAllDir !== true && ctx.modifiers.mousealldir !== true))
|
|
) {
|
|
const clone = evt.type.indexOf('mouse') > -1
|
|
? new MouseEvent(evt.type, evt)
|
|
: new TouchEvent(evt.type, evt);
|
|
|
|
evt.defaultPrevented === true && prevent(clone);
|
|
evt.cancelBubble === true && stop(clone);
|
|
|
|
Object.assign(clone, {
|
|
qKeyEvent: evt.qKeyEvent,
|
|
qClickOutside: evt.qClickOutside,
|
|
qAnchorHandled: evt.qAnchorHandled,
|
|
qClonedBy: evt.qClonedBy === void 0
|
|
? [ ctx.uid ]
|
|
: evt.qClonedBy.concat(ctx.uid)
|
|
});
|
|
|
|
ctx.initialEvent = {
|
|
target: evt.target,
|
|
event: clone
|
|
};
|
|
}
|
|
|
|
stop(evt);
|
|
}
|
|
|
|
const { left, top } = position(evt);
|
|
|
|
ctx.event = {
|
|
x: left,
|
|
y: top,
|
|
time: Date.now(),
|
|
mouse: mouseEvent === true,
|
|
detected: false,
|
|
isFirst: true,
|
|
isFinal: false,
|
|
lastX: left,
|
|
lastY: top
|
|
};
|
|
},
|
|
|
|
move (evt) {
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
const
|
|
pos = position(evt),
|
|
distX = pos.left - ctx.event.x,
|
|
distY = pos.top - ctx.event.y;
|
|
|
|
// prevent buggy browser behavior (like Blink-based engine ones on Windows)
|
|
// where the mousemove event occurs even if there's no movement after mousedown
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=161464
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=721341
|
|
// https://github.com/quasarframework/quasar/issues/10721
|
|
if (distX === 0 && distY === 0) {
|
|
return
|
|
}
|
|
|
|
ctx.lastEvt = evt;
|
|
|
|
const isMouseEvt = ctx.event.mouse === true;
|
|
const start = () => {
|
|
handleEvent(evt, isMouseEvt);
|
|
|
|
let cursor;
|
|
if (modifiers.preserveCursor !== true && modifiers.preservecursor !== true) {
|
|
cursor = document.documentElement.style.cursor || '';
|
|
document.documentElement.style.cursor = 'grabbing';
|
|
}
|
|
|
|
isMouseEvt === true && document.body.classList.add('no-pointer-events--children');
|
|
document.body.classList.add('non-selectable');
|
|
clearSelection();
|
|
|
|
ctx.styleCleanup = withDelayedFn => {
|
|
ctx.styleCleanup = void 0;
|
|
|
|
if (cursor !== void 0) {
|
|
document.documentElement.style.cursor = cursor;
|
|
}
|
|
|
|
document.body.classList.remove('non-selectable');
|
|
|
|
if (isMouseEvt === true) {
|
|
const remove = () => {
|
|
document.body.classList.remove('no-pointer-events--children');
|
|
};
|
|
|
|
if (withDelayedFn !== void 0) {
|
|
setTimeout(() => {
|
|
remove();
|
|
withDelayedFn();
|
|
}, 50);
|
|
}
|
|
else { remove(); }
|
|
}
|
|
else if (withDelayedFn !== void 0) {
|
|
withDelayedFn();
|
|
}
|
|
};
|
|
};
|
|
|
|
if (ctx.event.detected === true) {
|
|
ctx.event.isFirst !== true && handleEvent(evt, ctx.event.mouse);
|
|
|
|
const { payload, synthetic } = getChanges(evt, ctx, false);
|
|
|
|
if (payload !== void 0) {
|
|
if (ctx.handler(payload) === false) {
|
|
ctx.end(evt);
|
|
}
|
|
else {
|
|
if (ctx.styleCleanup === void 0 && ctx.event.isFirst === true) {
|
|
start();
|
|
}
|
|
|
|
ctx.event.lastX = payload.position.left;
|
|
ctx.event.lastY = payload.position.top;
|
|
ctx.event.lastDir = synthetic === true ? void 0 : payload.direction;
|
|
ctx.event.isFirst = false;
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if (
|
|
ctx.direction.all === true
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
|| (isMouseEvt === true && (ctx.modifiers.mouseAllDir === true || ctx.modifiers.mousealldir === true))
|
|
) {
|
|
start();
|
|
ctx.event.detected = true;
|
|
ctx.move(evt);
|
|
return
|
|
}
|
|
|
|
const
|
|
absX = Math.abs(distX),
|
|
absY = Math.abs(distY);
|
|
|
|
if (absX !== absY) {
|
|
if (
|
|
(ctx.direction.horizontal === true && absX > absY)
|
|
|| (ctx.direction.vertical === true && absX < absY)
|
|
|| (ctx.direction.up === true && absX < absY && distY < 0)
|
|
|| (ctx.direction.down === true && absX < absY && distY > 0)
|
|
|| (ctx.direction.left === true && absX > absY && distX < 0)
|
|
|| (ctx.direction.right === true && absX > absY && distX > 0)
|
|
) {
|
|
ctx.event.detected = true;
|
|
ctx.move(evt);
|
|
}
|
|
else {
|
|
ctx.end(evt, true);
|
|
}
|
|
}
|
|
},
|
|
|
|
end (evt, abort) {
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
cleanEvt(ctx, 'temp');
|
|
client.is.firefox === true && preventDraggable(el, false);
|
|
|
|
if (abort === true) {
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup();
|
|
|
|
if (ctx.event.detected !== true && ctx.initialEvent !== void 0) {
|
|
ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event);
|
|
}
|
|
}
|
|
else if (ctx.event.detected === true) {
|
|
ctx.event.isFirst === true && ctx.handler(getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx).payload);
|
|
|
|
const { payload } = getChanges(evt === void 0 ? ctx.lastEvt : evt, ctx, true);
|
|
const fn = () => { ctx.handler(payload); };
|
|
|
|
if (ctx.styleCleanup !== void 0) {
|
|
ctx.styleCleanup(fn);
|
|
}
|
|
else {
|
|
fn();
|
|
}
|
|
}
|
|
|
|
ctx.event = void 0;
|
|
ctx.initialEvent = void 0;
|
|
ctx.lastEvt = void 0;
|
|
}
|
|
};
|
|
|
|
el.__qtouchpan = ctx;
|
|
|
|
if (modifiers.mouse === true) {
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true
|
|
? 'Capture'
|
|
: '';
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'mousedown', 'mouseStart', `passive${ capture }` ]
|
|
]);
|
|
}
|
|
|
|
client.has.touch === true && addEvt(ctx, 'main', [
|
|
[ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ],
|
|
[ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll)
|
|
]);
|
|
},
|
|
|
|
updated (el, bindings) {
|
|
const ctx = el.__qtouchpan;
|
|
|
|
if (ctx !== void 0) {
|
|
if (bindings.oldValue !== bindings.value) {
|
|
typeof value !== 'function' && ctx.end();
|
|
ctx.handler = bindings.value;
|
|
}
|
|
|
|
ctx.direction = getModifierDirections(bindings.modifiers);
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qtouchpan;
|
|
|
|
if (ctx !== void 0) {
|
|
// emit the end event when the directive is destroyed while active
|
|
// this is only needed in TouchPan because the rest of the touch directives do not emit an end event
|
|
// the condition is also checked in the start of function but we avoid the call
|
|
ctx.event !== void 0 && ctx.end();
|
|
|
|
cleanEvt(ctx, 'main');
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
client.is.firefox === true && preventDraggable(el, false);
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup();
|
|
|
|
delete el.__qtouchpan;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
const markerPrefixClass = 'q-slider__marker-labels';
|
|
const defaultMarkerConvertFn = v => ({ value: v });
|
|
const defaultMarkerLabelRenderFn = ({ marker }) => vue.h('div', {
|
|
key: marker.value,
|
|
style: marker.style,
|
|
class: marker.classes
|
|
}, marker.label);
|
|
|
|
// PGDOWN, LEFT, DOWN, PGUP, RIGHT, UP
|
|
const keyCodes$2 = [ 34, 37, 40, 33, 39, 38 ];
|
|
|
|
const useSliderProps = {
|
|
...useDarkProps,
|
|
...useFormProps,
|
|
|
|
min: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
max: {
|
|
type: Number,
|
|
default: 100
|
|
},
|
|
innerMin: Number,
|
|
innerMax: Number,
|
|
|
|
step: {
|
|
type: Number,
|
|
default: 1,
|
|
validator: v => v >= 0
|
|
},
|
|
|
|
snap: Boolean,
|
|
|
|
vertical: Boolean,
|
|
reverse: Boolean,
|
|
|
|
hideSelection: Boolean,
|
|
|
|
color: String,
|
|
markerLabelsClass: String,
|
|
|
|
label: Boolean,
|
|
labelColor: String,
|
|
labelTextColor: String,
|
|
labelAlways: Boolean,
|
|
switchLabelSide: Boolean,
|
|
|
|
markers: [ Boolean, Number ],
|
|
markerLabels: [ Boolean, Array, Object, Function ],
|
|
switchMarkerLabelsSide: Boolean,
|
|
|
|
trackImg: String,
|
|
trackColor: String,
|
|
innerTrackImg: String,
|
|
innerTrackColor: String,
|
|
selectionColor: String,
|
|
selectionImg: String,
|
|
|
|
thumbSize: {
|
|
type: String,
|
|
default: '20px'
|
|
},
|
|
trackSize: {
|
|
type: String,
|
|
default: '4px'
|
|
},
|
|
|
|
disable: Boolean,
|
|
readonly: Boolean,
|
|
dense: Boolean,
|
|
|
|
tabindex: [ String, Number ],
|
|
|
|
thumbColor: String,
|
|
thumbPath: {
|
|
type: String,
|
|
default: 'M 4, 10 a 6,6 0 1,0 12,0 a 6,6 0 1,0 -12,0'
|
|
}
|
|
};
|
|
|
|
const useSliderEmits = [ 'pan', 'update:modelValue', 'change' ];
|
|
|
|
function useSlider ({ updateValue, updatePosition, getDragging, formAttrs }) {
|
|
const { props, emit, slots, proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const active = vue.ref(false);
|
|
const preventFocus = vue.ref(false);
|
|
const focus = vue.ref(false);
|
|
const dragging = vue.ref(false);
|
|
|
|
const axis = vue.computed(() => (props.vertical === true ? '--v' : '--h'));
|
|
const labelSide = vue.computed(() => '-' + (props.switchLabelSide === true ? 'switched' : 'standard'));
|
|
|
|
const isReversed = vue.computed(() => (
|
|
props.vertical === true
|
|
? props.reverse === true
|
|
: props.reverse !== ($q.lang.rtl === true)
|
|
));
|
|
|
|
const innerMin = vue.computed(() => (
|
|
isNaN(props.innerMin) === true || props.innerMin < props.min
|
|
? props.min
|
|
: props.innerMin
|
|
));
|
|
const innerMax = vue.computed(() => (
|
|
isNaN(props.innerMax) === true || props.innerMax > props.max
|
|
? props.max
|
|
: props.innerMax
|
|
));
|
|
|
|
const editable = vue.computed(() => (
|
|
props.disable !== true && props.readonly !== true
|
|
&& innerMin.value < innerMax.value
|
|
));
|
|
|
|
const decimals = vue.computed(() => (String(props.step).trim().split('.')[ 1 ] || '').length);
|
|
const step = vue.computed(() => (props.step === 0 ? 1 : props.step));
|
|
const tabindex = vue.computed(() => (editable.value === true ? props.tabindex || 0 : -1));
|
|
|
|
const trackLen = vue.computed(() => props.max - props.min);
|
|
const innerBarLen = vue.computed(() => innerMax.value - innerMin.value);
|
|
|
|
const innerMinRatio = vue.computed(() => convertModelToRatio(innerMin.value));
|
|
const innerMaxRatio = vue.computed(() => convertModelToRatio(innerMax.value));
|
|
|
|
const positionProp = vue.computed(() => (
|
|
props.vertical === true
|
|
? (isReversed.value === true ? 'bottom' : 'top')
|
|
: (isReversed.value === true ? 'right' : 'left')
|
|
));
|
|
|
|
const sizeProp = vue.computed(() => (props.vertical === true ? 'height' : 'width'));
|
|
const thicknessProp = vue.computed(() => (props.vertical === true ? 'width' : 'height'));
|
|
const orientation = vue.computed(() => (props.vertical === true ? 'vertical' : 'horizontal'));
|
|
|
|
const attributes = vue.computed(() => {
|
|
const acc = {
|
|
role: 'slider',
|
|
'aria-valuemin': innerMin.value,
|
|
'aria-valuemax': innerMax.value,
|
|
'aria-orientation': orientation.value,
|
|
'data-step': props.step
|
|
};
|
|
|
|
if (props.disable === true) {
|
|
acc[ 'aria-disabled' ] = 'true';
|
|
}
|
|
else if (props.readonly === true) {
|
|
acc[ 'aria-readonly' ] = 'true';
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-slider q-slider${ axis.value } q-slider--${ active.value === true ? '' : 'in' }active inline no-wrap `
|
|
+ (props.vertical === true ? 'row' : 'column')
|
|
+ (props.disable === true ? ' disabled' : ' q-slider--enabled' + (editable.value === true ? ' q-slider--editable' : ''))
|
|
+ (focus.value === 'both' ? ' q-slider--focus' : '')
|
|
+ (props.label || props.labelAlways === true ? ' q-slider--label' : '')
|
|
+ (props.labelAlways === true ? ' q-slider--label-always' : '')
|
|
+ (isDark.value === true ? ' q-slider--dark' : '')
|
|
+ (props.dense === true ? ' q-slider--dense q-slider--dense' + axis.value : '')
|
|
);
|
|
|
|
function getPositionClass (name) {
|
|
const cls = 'q-slider__' + name;
|
|
return `${ cls } ${ cls }${ axis.value } ${ cls }${ axis.value }${ labelSide.value }`
|
|
}
|
|
function getAxisClass (name) {
|
|
const cls = 'q-slider__' + name;
|
|
return `${ cls } ${ cls }${ axis.value }`
|
|
}
|
|
|
|
const selectionBarClass = vue.computed(() => {
|
|
const color = props.selectionColor || props.color;
|
|
return 'q-slider__selection absolute'
|
|
+ (color !== void 0 ? ` text-${ color }` : '')
|
|
});
|
|
const markerClass = vue.computed(() => getAxisClass('markers') + ' absolute overflow-hidden');
|
|
const trackContainerClass = vue.computed(() => getAxisClass('track-container'));
|
|
const pinClass = vue.computed(() => getPositionClass('pin'));
|
|
const labelClass = vue.computed(() => getPositionClass('label'));
|
|
const textContainerClass = vue.computed(() => getPositionClass('text-container'));
|
|
const markerLabelsContainerClass = vue.computed(() =>
|
|
getPositionClass('marker-labels-container')
|
|
+ (props.markerLabelsClass !== void 0 ? ` ${ props.markerLabelsClass }` : '')
|
|
);
|
|
|
|
const trackClass = vue.computed(() =>
|
|
'q-slider__track relative-position no-outline'
|
|
+ (props.trackColor !== void 0 ? ` bg-${ props.trackColor }` : '')
|
|
);
|
|
const trackStyle = vue.computed(() => {
|
|
const acc = { [ thicknessProp.value ]: props.trackSize };
|
|
if (props.trackImg !== void 0) {
|
|
acc.backgroundImage = `url(${ props.trackImg }) !important`;
|
|
}
|
|
return acc
|
|
});
|
|
|
|
const innerBarClass = vue.computed(() =>
|
|
'q-slider__inner absolute'
|
|
+ (props.innerTrackColor !== void 0 ? ` bg-${ props.innerTrackColor }` : '')
|
|
);
|
|
const innerBarStyle = vue.computed(() => {
|
|
const acc = {
|
|
[ positionProp.value ]: `${ 100 * innerMinRatio.value }%`,
|
|
[ sizeProp.value ]: `${ 100 * (innerMaxRatio.value - innerMinRatio.value) }%`
|
|
};
|
|
if (props.innerTrackImg !== void 0) {
|
|
acc.backgroundImage = `url(${ props.innerTrackImg }) !important`;
|
|
}
|
|
return acc
|
|
});
|
|
|
|
function convertRatioToModel (ratio) {
|
|
const { min, max, step } = props;
|
|
let model = min + ratio * (max - min);
|
|
|
|
if (step > 0) {
|
|
const modulo = (model - min) % step;
|
|
model += (Math.abs(modulo) >= step / 2 ? (modulo < 0 ? -1 : 1) * step : 0) - modulo;
|
|
}
|
|
|
|
if (decimals.value > 0) {
|
|
model = parseFloat(model.toFixed(decimals.value));
|
|
}
|
|
|
|
return between(model, innerMin.value, innerMax.value)
|
|
}
|
|
|
|
function convertModelToRatio (model) {
|
|
return trackLen.value === 0
|
|
? 0
|
|
: (model - props.min) / trackLen.value
|
|
}
|
|
|
|
function getDraggingRatio (evt, dragging) {
|
|
const
|
|
pos = position(evt),
|
|
val = props.vertical === true
|
|
? between((pos.top - dragging.top) / dragging.height, 0, 1)
|
|
: between((pos.left - dragging.left) / dragging.width, 0, 1);
|
|
|
|
return between(
|
|
isReversed.value === true ? 1.0 - val : val,
|
|
innerMinRatio.value,
|
|
innerMaxRatio.value
|
|
)
|
|
}
|
|
|
|
const markerStep = vue.computed(() => (
|
|
isNumber(props.markers) === true ? props.markers : step.value)
|
|
);
|
|
|
|
const markerTicks = vue.computed(() => {
|
|
const acc = [];
|
|
const step = markerStep.value;
|
|
const max = props.max;
|
|
|
|
let value = props.min;
|
|
do {
|
|
acc.push(value);
|
|
value += step;
|
|
} while (value < max)
|
|
|
|
acc.push(max);
|
|
return acc
|
|
});
|
|
|
|
const markerLabelClass = vue.computed(() => {
|
|
const prefix = ` ${ markerPrefixClass }${ axis.value }-`;
|
|
return markerPrefixClass
|
|
+ `${ prefix }${ props.switchMarkerLabelsSide === true ? 'switched' : 'standard' }`
|
|
+ `${ prefix }${ isReversed.value === true ? 'rtl' : 'ltr' }`
|
|
});
|
|
|
|
const markerLabelsList = vue.computed(() => {
|
|
if (props.markerLabels === false) { return null }
|
|
|
|
return getMarkerList(props.markerLabels).map((entry, index) => ({
|
|
index,
|
|
value: entry.value,
|
|
label: entry.label || entry.value,
|
|
classes: markerLabelClass.value
|
|
+ (entry.classes !== void 0 ? ' ' + entry.classes : ''),
|
|
style: {
|
|
...getMarkerLabelStyle(entry.value),
|
|
...(entry.style || {})
|
|
}
|
|
}))
|
|
});
|
|
|
|
const markerScope = vue.computed(() => ({
|
|
markerList: markerLabelsList.value,
|
|
markerMap: markerLabelsMap.value,
|
|
classes: markerLabelClass.value, // TODO ts definition
|
|
getStyle: getMarkerLabelStyle
|
|
}));
|
|
|
|
const markerStyle = vue.computed(() => {
|
|
if (innerBarLen.value !== 0) {
|
|
const size = 100 * markerStep.value / innerBarLen.value;
|
|
|
|
return {
|
|
...innerBarStyle.value,
|
|
backgroundSize: props.vertical === true
|
|
? `2px ${ size }%`
|
|
: `${ size }% 2px`
|
|
}
|
|
}
|
|
|
|
return null
|
|
});
|
|
|
|
function getMarkerList (def) {
|
|
if (def === false) { return null }
|
|
|
|
if (def === true) {
|
|
return markerTicks.value.map(defaultMarkerConvertFn)
|
|
}
|
|
|
|
if (typeof def === 'function') {
|
|
return markerTicks.value.map(value => {
|
|
const item = def(value);
|
|
return isObject(item) === true ? { ...item, value } : { value, label: item }
|
|
})
|
|
}
|
|
|
|
const filterFn = ({ value }) => value >= props.min && value <= props.max;
|
|
|
|
if (Array.isArray(def) === true) {
|
|
return def
|
|
.map(item => (isObject(item) === true ? item : { value: item }))
|
|
.filter(filterFn)
|
|
}
|
|
|
|
return Object.keys(def).map(key => {
|
|
const item = def[ key ];
|
|
const value = Number(key);
|
|
return isObject(item) === true ? { ...item, value } : { value, label: item }
|
|
}).filter(filterFn)
|
|
}
|
|
|
|
function getMarkerLabelStyle (val) {
|
|
return { [ positionProp.value ]: `${ 100 * (val - props.min) / trackLen.value }%` }
|
|
}
|
|
|
|
const markerLabelsMap = vue.computed(() => {
|
|
if (props.markerLabels === false) { return null }
|
|
|
|
const acc = {};
|
|
markerLabelsList.value.forEach(entry => {
|
|
acc[ entry.value ] = entry;
|
|
});
|
|
return acc
|
|
});
|
|
|
|
function getMarkerLabelsContent () {
|
|
if (slots[ 'marker-label-group' ] !== void 0) {
|
|
return slots[ 'marker-label-group' ](markerScope.value)
|
|
}
|
|
|
|
const fn = slots[ 'marker-label' ] || defaultMarkerLabelRenderFn;
|
|
return markerLabelsList.value.map(marker => fn({
|
|
marker,
|
|
...markerScope.value
|
|
}))
|
|
}
|
|
|
|
const panDirective = vue.computed(() => {
|
|
// if editable.value === true
|
|
return [ [
|
|
TouchPan,
|
|
onPan,
|
|
void 0,
|
|
{
|
|
[ orientation.value ]: true,
|
|
prevent: true,
|
|
stop: true,
|
|
mouse: true,
|
|
mouseAllDir: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
function onPan (event) {
|
|
if (event.isFinal === true) {
|
|
if (dragging.value !== void 0) {
|
|
updatePosition(event.evt);
|
|
// only if touch, because we also have mousedown/up:
|
|
event.touch === true && updateValue(true);
|
|
dragging.value = void 0;
|
|
emit('pan', 'end');
|
|
}
|
|
active.value = false;
|
|
focus.value = false;
|
|
}
|
|
else if (event.isFirst === true) {
|
|
dragging.value = getDragging(event.evt);
|
|
updatePosition(event.evt);
|
|
updateValue();
|
|
active.value = true;
|
|
emit('pan', 'start');
|
|
}
|
|
else {
|
|
updatePosition(event.evt);
|
|
updateValue();
|
|
}
|
|
}
|
|
|
|
function onBlur () {
|
|
focus.value = false;
|
|
}
|
|
|
|
function onActivate (evt) {
|
|
updatePosition(evt, getDragging(evt));
|
|
updateValue();
|
|
|
|
preventFocus.value = true;
|
|
active.value = true;
|
|
|
|
document.addEventListener('mouseup', onDeactivate, true);
|
|
}
|
|
|
|
function onDeactivate () {
|
|
preventFocus.value = false;
|
|
active.value = false;
|
|
|
|
updateValue(true);
|
|
onBlur();
|
|
|
|
document.removeEventListener('mouseup', onDeactivate, true);
|
|
}
|
|
|
|
function onMobileClick (evt) {
|
|
updatePosition(evt, getDragging(evt));
|
|
updateValue(true);
|
|
}
|
|
|
|
function onKeyup (evt) {
|
|
if (keyCodes$2.includes(evt.keyCode)) {
|
|
updateValue(true);
|
|
}
|
|
}
|
|
|
|
function getTextContainerStyle (ratio) {
|
|
if (props.vertical === true) { return null }
|
|
|
|
const p = $q.lang.rtl !== props.reverse ? 1 - ratio : ratio;
|
|
return {
|
|
transform: `translateX(calc(${ 2 * p - 1 } * ${ props.thumbSize } / 2 + ${ 50 - 100 * p }%))`
|
|
}
|
|
}
|
|
|
|
function getThumbRenderFn (thumb) {
|
|
const focusClass = vue.computed(() => (
|
|
preventFocus.value === false && (focus.value === thumb.focusValue || focus.value === 'both')
|
|
? ' q-slider--focus'
|
|
: ''
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-slider__thumb q-slider__thumb${ axis.value } q-slider__thumb${ axis.value }-${ isReversed.value === true ? 'rtl' : 'ltr' } absolute non-selectable`
|
|
+ focusClass.value
|
|
+ (thumb.thumbColor.value !== void 0 ? ` text-${ thumb.thumbColor.value }` : '')
|
|
);
|
|
|
|
const style = vue.computed(() => ({
|
|
width: props.thumbSize,
|
|
height: props.thumbSize,
|
|
[ positionProp.value ]: `${ 100 * thumb.ratio.value }%`,
|
|
zIndex: focus.value === thumb.focusValue ? 2 : void 0
|
|
}));
|
|
|
|
const pinColor = vue.computed(() => (
|
|
thumb.labelColor.value !== void 0
|
|
? ` text-${ thumb.labelColor.value }`
|
|
: ''
|
|
));
|
|
|
|
const textContainerStyle = vue.computed(() => getTextContainerStyle(thumb.ratio.value));
|
|
|
|
const textClass = vue.computed(() => (
|
|
'q-slider__text'
|
|
+ (thumb.labelTextColor.value !== void 0 ? ` text-${ thumb.labelTextColor.value }` : '')
|
|
));
|
|
|
|
return () => {
|
|
const thumbContent = [
|
|
vue.h('svg', {
|
|
class: 'q-slider__thumb-shape absolute-full',
|
|
viewBox: '0 0 20 20',
|
|
'aria-hidden': 'true'
|
|
}, [
|
|
vue.h('path', { d: props.thumbPath })
|
|
]),
|
|
|
|
vue.h('div', { class: 'q-slider__focus-ring fit' })
|
|
];
|
|
|
|
if (props.label === true || props.labelAlways === true) {
|
|
thumbContent.push(
|
|
vue.h('div', {
|
|
class: pinClass.value + ' absolute fit no-pointer-events' + pinColor.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: labelClass.value,
|
|
style: { minWidth: props.thumbSize }
|
|
}, [
|
|
vue.h('div', {
|
|
class: textContainerClass.value,
|
|
style: textContainerStyle.value
|
|
}, [
|
|
vue.h('span', { class: textClass.value }, thumb.label.value)
|
|
])
|
|
])
|
|
])
|
|
);
|
|
|
|
if (props.name !== void 0 && props.disable !== true) {
|
|
injectFormInput(thumbContent, 'push');
|
|
}
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
...thumb.getNodeData()
|
|
}, thumbContent)
|
|
}
|
|
}
|
|
|
|
function getContent (selectionBarStyle, trackContainerTabindex, trackContainerEvents, injectThumb) {
|
|
const trackContent = [];
|
|
|
|
props.innerTrackColor !== 'transparent' && trackContent.push(
|
|
vue.h('div', {
|
|
key: 'inner',
|
|
class: innerBarClass.value,
|
|
style: innerBarStyle.value
|
|
})
|
|
);
|
|
|
|
props.selectionColor !== 'transparent' && trackContent.push(
|
|
vue.h('div', {
|
|
key: 'selection',
|
|
class: selectionBarClass.value,
|
|
style: selectionBarStyle.value
|
|
})
|
|
);
|
|
|
|
props.markers !== false && trackContent.push(
|
|
vue.h('div', {
|
|
key: 'marker',
|
|
class: markerClass.value,
|
|
style: markerStyle.value
|
|
})
|
|
);
|
|
|
|
injectThumb(trackContent);
|
|
|
|
const content = [
|
|
hDir(
|
|
'div',
|
|
{
|
|
key: 'trackC',
|
|
class: trackContainerClass.value,
|
|
tabindex: trackContainerTabindex.value,
|
|
...trackContainerEvents.value
|
|
},
|
|
[
|
|
vue.h('div', {
|
|
class: trackClass.value,
|
|
style: trackStyle.value
|
|
}, trackContent)
|
|
],
|
|
'slide',
|
|
editable.value, () => panDirective.value
|
|
)
|
|
];
|
|
|
|
if (props.markerLabels !== false) {
|
|
const action = props.switchMarkerLabelsSide === true
|
|
? 'unshift'
|
|
: 'push';
|
|
|
|
content[ action ](
|
|
vue.h('div', {
|
|
key: 'markerL',
|
|
class: markerLabelsContainerClass.value
|
|
}, getMarkerLabelsContent())
|
|
);
|
|
}
|
|
|
|
return content
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
document.removeEventListener('mouseup', onDeactivate, true);
|
|
});
|
|
|
|
return {
|
|
state: {
|
|
active,
|
|
focus,
|
|
preventFocus,
|
|
dragging,
|
|
|
|
editable,
|
|
classes,
|
|
tabindex,
|
|
attributes,
|
|
|
|
step,
|
|
decimals,
|
|
trackLen,
|
|
innerMin,
|
|
innerMinRatio,
|
|
innerMax,
|
|
innerMaxRatio,
|
|
positionProp,
|
|
sizeProp,
|
|
isReversed
|
|
},
|
|
|
|
methods: {
|
|
onActivate,
|
|
onMobileClick,
|
|
onBlur,
|
|
onKeyup,
|
|
getContent,
|
|
getThumbRenderFn,
|
|
convertRatioToModel,
|
|
convertModelToRatio,
|
|
getDraggingRatio
|
|
}
|
|
}
|
|
}
|
|
|
|
const getNodeData = () => ({});
|
|
|
|
var QSlider = createComponent({
|
|
name: 'QSlider',
|
|
|
|
props: {
|
|
...useSliderProps,
|
|
|
|
modelValue: {
|
|
required: true,
|
|
default: null,
|
|
validator: v => typeof v === 'number' || v === null
|
|
},
|
|
|
|
labelValue: [ String, Number ]
|
|
},
|
|
|
|
emits: useSliderEmits,
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const { state, methods } = useSlider({
|
|
updateValue, updatePosition, getDragging,
|
|
formAttrs: useFormAttrs(props)
|
|
});
|
|
|
|
const rootRef = vue.ref(null);
|
|
const curRatio = vue.ref(0);
|
|
const model = vue.ref(0);
|
|
|
|
function normalizeModel () {
|
|
model.value = props.modelValue === null
|
|
? state.innerMin.value
|
|
: between(props.modelValue, state.innerMin.value, state.innerMax.value);
|
|
}
|
|
|
|
vue.watch(
|
|
() => `${ props.modelValue }|${ state.innerMin.value }|${ state.innerMax.value }`,
|
|
normalizeModel
|
|
);
|
|
|
|
normalizeModel();
|
|
|
|
const modelRatio = vue.computed(() => methods.convertModelToRatio(model.value));
|
|
const ratio = vue.computed(() => (state.active.value === true ? curRatio.value : modelRatio.value));
|
|
|
|
const selectionBarStyle = vue.computed(() => {
|
|
const acc = {
|
|
[ state.positionProp.value ]: `${ 100 * state.innerMinRatio.value }%`,
|
|
[ state.sizeProp.value ]: `${ 100 * (ratio.value - state.innerMinRatio.value) }%`
|
|
};
|
|
if (props.selectionImg !== void 0) {
|
|
acc.backgroundImage = `url(${ props.selectionImg }) !important`;
|
|
}
|
|
return acc
|
|
});
|
|
|
|
const getThumb = methods.getThumbRenderFn({
|
|
focusValue: true,
|
|
getNodeData,
|
|
ratio,
|
|
label: vue.computed(() => (
|
|
props.labelValue !== void 0
|
|
? props.labelValue
|
|
: model.value
|
|
)),
|
|
thumbColor: vue.computed(() => props.thumbColor || props.color),
|
|
labelColor: vue.computed(() => props.labelColor),
|
|
labelTextColor: vue.computed(() => props.labelTextColor)
|
|
});
|
|
|
|
const trackContainerEvents = vue.computed(() => {
|
|
if (state.editable.value !== true) {
|
|
return {}
|
|
}
|
|
|
|
return $q.platform.is.mobile === true
|
|
? { onClick: methods.onMobileClick }
|
|
: {
|
|
onMousedown: methods.onActivate,
|
|
onFocus,
|
|
onBlur: methods.onBlur,
|
|
onKeydown,
|
|
onKeyup: methods.onKeyup
|
|
}
|
|
});
|
|
|
|
function updateValue (change) {
|
|
if (model.value !== props.modelValue) {
|
|
emit('update:modelValue', model.value);
|
|
}
|
|
change === true && emit('change', model.value);
|
|
}
|
|
|
|
function getDragging () {
|
|
return rootRef.value.getBoundingClientRect()
|
|
}
|
|
|
|
function updatePosition (event, dragging = state.dragging.value) {
|
|
const ratio = methods.getDraggingRatio(event, dragging);
|
|
|
|
model.value = methods.convertRatioToModel(ratio);
|
|
|
|
curRatio.value = props.snap !== true || props.step === 0
|
|
? ratio
|
|
: methods.convertModelToRatio(model.value);
|
|
}
|
|
|
|
function onFocus () {
|
|
state.focus.value = true;
|
|
}
|
|
|
|
function onKeydown (evt) {
|
|
if (!keyCodes$2.includes(evt.keyCode)) {
|
|
return
|
|
}
|
|
|
|
stopAndPrevent(evt);
|
|
|
|
const
|
|
stepVal = ([ 34, 33 ].includes(evt.keyCode) ? 10 : 1) * state.step.value,
|
|
offset = (
|
|
([ 34, 37, 40 ].includes(evt.keyCode) ? -1 : 1)
|
|
* (state.isReversed.value === true ? -1 : 1)
|
|
* (props.vertical === true ? -1 : 1) * stepVal
|
|
);
|
|
|
|
model.value = between(
|
|
parseFloat((model.value + offset).toFixed(state.decimals.value)),
|
|
state.innerMin.value,
|
|
state.innerMax.value
|
|
);
|
|
|
|
updateValue();
|
|
}
|
|
|
|
return () => {
|
|
const content = methods.getContent(
|
|
selectionBarStyle,
|
|
state.tabindex,
|
|
trackContainerEvents,
|
|
node => { node.push(getThumb()); }
|
|
);
|
|
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: state.classes.value + (props.modelValue === null ? ' q-slider--no-value' : ''),
|
|
...state.attributes.value,
|
|
'aria-valuenow': props.modelValue
|
|
}, content)
|
|
}
|
|
}
|
|
});
|
|
|
|
function useCanRender () {
|
|
const canRender = vue.ref(!isRuntimeSsrPreHydration.value);
|
|
|
|
if (canRender.value === false) {
|
|
vue.onMounted(() => {
|
|
canRender.value = true;
|
|
});
|
|
}
|
|
|
|
return canRender
|
|
}
|
|
|
|
const hasObserver = typeof ResizeObserver !== 'undefined';
|
|
const resizeProps = hasObserver === true
|
|
? {}
|
|
: {
|
|
style: 'display:block;position:absolute;top:0;left:0;right:0;bottom:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1;',
|
|
url: 'about:blank'
|
|
};
|
|
|
|
var QResizeObserver = createComponent({
|
|
name: 'QResizeObserver',
|
|
|
|
props: {
|
|
debounce: {
|
|
type: [ String, Number ],
|
|
default: 100
|
|
}
|
|
},
|
|
|
|
emits: [ 'resize' ],
|
|
|
|
setup (props, { emit }) {
|
|
|
|
let timer = null, targetEl, size = { width: -1, height: -1 };
|
|
|
|
function trigger (immediately) {
|
|
if (immediately === true || props.debounce === 0 || props.debounce === '0') {
|
|
emitEvent();
|
|
}
|
|
else if (timer === null) {
|
|
timer = setTimeout(emitEvent, props.debounce);
|
|
}
|
|
}
|
|
|
|
function emitEvent () {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
if (targetEl) {
|
|
const { offsetWidth: width, offsetHeight: height } = targetEl;
|
|
|
|
if (width !== size.width || height !== size.height) {
|
|
size = { width, height };
|
|
emit('resize', size);
|
|
}
|
|
}
|
|
}
|
|
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
if (hasObserver === true) {
|
|
let observer;
|
|
|
|
// initialize as soon as possible
|
|
const init = stop => {
|
|
targetEl = proxy.$el.parentNode;
|
|
|
|
if (targetEl) {
|
|
observer = new ResizeObserver(trigger);
|
|
observer.observe(targetEl);
|
|
emitEvent();
|
|
}
|
|
else if (stop !== true) {
|
|
vue.nextTick(() => { init(true); });
|
|
}
|
|
};
|
|
|
|
vue.onMounted(() => { init(); });
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
timer !== null && clearTimeout(timer);
|
|
|
|
if (observer !== void 0) {
|
|
if (observer.disconnect !== void 0) {
|
|
observer.disconnect();
|
|
}
|
|
else if (targetEl) { // FF for Android
|
|
observer.unobserve(targetEl);
|
|
}
|
|
}
|
|
});
|
|
|
|
return noop
|
|
}
|
|
else { // no observer, so fallback to old iframe method
|
|
const canRender = useCanRender();
|
|
|
|
let curDocView;
|
|
|
|
function cleanup () {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
if (curDocView !== void 0) {
|
|
// iOS is fuzzy, need to check it first
|
|
if (curDocView.removeEventListener !== void 0) {
|
|
curDocView.removeEventListener('resize', trigger, listenOpts.passive);
|
|
}
|
|
curDocView = void 0;
|
|
}
|
|
}
|
|
|
|
function onObjLoad () {
|
|
cleanup();
|
|
|
|
if (targetEl && targetEl.contentDocument) {
|
|
curDocView = targetEl.contentDocument.defaultView;
|
|
curDocView.addEventListener('resize', trigger, listenOpts.passive);
|
|
emitEvent();
|
|
}
|
|
}
|
|
|
|
vue.onMounted(() => {
|
|
vue.nextTick(() => {
|
|
targetEl = proxy.$el;
|
|
targetEl && onObjLoad();
|
|
});
|
|
});
|
|
|
|
vue.onBeforeUnmount(cleanup);
|
|
|
|
// expose public method
|
|
proxy.trigger = trigger;
|
|
|
|
return () => {
|
|
if (canRender.value === true) {
|
|
return vue.h('object', {
|
|
style: resizeProps.style,
|
|
tabindex: -1, // fix for Firefox
|
|
type: 'text/html',
|
|
data: resizeProps.url,
|
|
'aria-hidden': 'true',
|
|
onLoad: onObjLoad
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let rtlHasScrollBug = false;
|
|
|
|
// mobile Chrome takes the crown for this
|
|
{
|
|
const scroller = document.createElement('div');
|
|
scroller.setAttribute('dir', 'rtl');
|
|
Object.assign(scroller.style, {
|
|
width: '1px',
|
|
height: '1px',
|
|
overflow: 'auto'
|
|
});
|
|
|
|
const spacer = document.createElement('div');
|
|
Object.assign(spacer.style, {
|
|
width: '1000px',
|
|
height: '1px'
|
|
});
|
|
|
|
document.body.appendChild(scroller);
|
|
scroller.appendChild(spacer);
|
|
scroller.scrollLeft = -1000;
|
|
|
|
rtlHasScrollBug = scroller.scrollLeft >= 0;
|
|
|
|
scroller.remove();
|
|
}
|
|
|
|
function getIndicatorClass (color, top, vertical) {
|
|
const pos = vertical === true
|
|
? [ 'left', 'right' ]
|
|
: [ 'top', 'bottom' ];
|
|
|
|
return `absolute-${ top === true ? pos[ 0 ] : pos[ 1 ] }${ color ? ` text-${ color }` : '' }`
|
|
}
|
|
|
|
const alignValues$1 = [ 'left', 'center', 'right', 'justify' ];
|
|
|
|
var QTabs = createComponent({
|
|
name: 'QTabs',
|
|
|
|
props: {
|
|
modelValue: [ Number, String ],
|
|
|
|
align: {
|
|
type: String,
|
|
default: 'center',
|
|
validator: v => alignValues$1.includes(v)
|
|
},
|
|
breakpoint: {
|
|
type: [ String, Number ],
|
|
default: 600
|
|
},
|
|
|
|
vertical: Boolean,
|
|
shrink: Boolean,
|
|
stretch: Boolean,
|
|
|
|
activeClass: String,
|
|
activeColor: String,
|
|
activeBgColor: String,
|
|
indicatorColor: String,
|
|
leftIcon: String,
|
|
rightIcon: String,
|
|
|
|
outsideArrows: Boolean,
|
|
mobileArrows: Boolean,
|
|
|
|
switchIndicator: Boolean,
|
|
|
|
narrowIndicator: Boolean,
|
|
inlineLabel: Boolean,
|
|
noCaps: Boolean,
|
|
|
|
dense: Boolean,
|
|
|
|
contentClass: String,
|
|
|
|
'onUpdate:modelValue': [ Function, Array ]
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const { registerTick: registerScrollTick } = useTick();
|
|
const { registerTick: registerUpdateArrowsTick } = useTick();
|
|
const { registerTick: registerAnimateTick } = useTick();
|
|
|
|
const { registerTimeout: registerFocusTimeout, removeTimeout: removeFocusTimeout } = useTimeout();
|
|
const { registerTimeout: registerScrollToTabTimeout, removeTimeout: removeScrollToTabTimeout } = useTimeout();
|
|
|
|
const rootRef = vue.ref(null);
|
|
const contentRef = vue.ref(null);
|
|
|
|
const currentModel = vue.ref(props.modelValue);
|
|
const scrollable = vue.ref(false);
|
|
const leftArrow = vue.ref(true);
|
|
const rightArrow = vue.ref(false);
|
|
const justify = vue.ref(false);
|
|
|
|
const tabDataList = [];
|
|
const tabDataListLen = vue.ref(0);
|
|
const hasFocus = vue.ref(false);
|
|
|
|
let animateTimer = null, scrollTimer = null, unwatchRoute;
|
|
|
|
const tabProps = vue.computed(() => ({
|
|
activeClass: props.activeClass,
|
|
activeColor: props.activeColor,
|
|
activeBgColor: props.activeBgColor,
|
|
indicatorClass: getIndicatorClass(
|
|
props.indicatorColor,
|
|
props.switchIndicator,
|
|
props.vertical
|
|
),
|
|
narrowIndicator: props.narrowIndicator,
|
|
inlineLabel: props.inlineLabel,
|
|
noCaps: props.noCaps
|
|
}));
|
|
|
|
const hasActiveTab = vue.computed(() => {
|
|
const len = tabDataListLen.value;
|
|
const val = currentModel.value;
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
if (tabDataList[ i ].name.value === val) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
});
|
|
|
|
const alignClass = vue.computed(() => {
|
|
const align = scrollable.value === true
|
|
? 'left'
|
|
: (justify.value === true ? 'justify' : props.align);
|
|
|
|
return `q-tabs__content--align-${ align }`
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-tabs row no-wrap items-center'
|
|
+ ` q-tabs--${ scrollable.value === true ? '' : 'not-' }scrollable`
|
|
+ ` q-tabs--${ props.vertical === true ? 'vertical' : 'horizontal' }`
|
|
+ ` q-tabs__arrows--${ props.outsideArrows === true ? 'outside' : 'inside' }`
|
|
+ ` q-tabs--mobile-with${ props.mobileArrows === true ? '' : 'out' }-arrows`
|
|
+ (props.dense === true ? ' q-tabs--dense' : '')
|
|
+ (props.shrink === true ? ' col-shrink' : '')
|
|
+ (props.stretch === true ? ' self-stretch' : '')
|
|
);
|
|
|
|
const innerClass = vue.computed(() =>
|
|
'q-tabs__content scroll--mobile row no-wrap items-center self-stretch hide-scrollbar relative-position '
|
|
+ alignClass.value
|
|
+ (props.contentClass !== void 0 ? ` ${ props.contentClass }` : '')
|
|
);
|
|
|
|
const domProps = vue.computed(() => (
|
|
props.vertical === true
|
|
? { container: 'height', content: 'offsetHeight', scroll: 'scrollHeight' }
|
|
: { container: 'width', content: 'offsetWidth', scroll: 'scrollWidth' }
|
|
));
|
|
|
|
const isRTL = vue.computed(() => props.vertical !== true && $q.lang.rtl === true);
|
|
const rtlPosCorrection = vue.computed(() => rtlHasScrollBug === false && isRTL.value === true);
|
|
|
|
vue.watch(isRTL, updateArrows);
|
|
|
|
vue.watch(() => props.modelValue, name => {
|
|
updateModel({ name, setCurrent: true, skipEmit: true });
|
|
});
|
|
|
|
vue.watch(() => props.outsideArrows, recalculateScroll);
|
|
|
|
function updateModel ({ name, setCurrent, skipEmit }) {
|
|
if (currentModel.value !== name) {
|
|
if (skipEmit !== true && props[ 'onUpdate:modelValue' ] !== void 0) {
|
|
emit('update:modelValue', name);
|
|
}
|
|
|
|
if (
|
|
setCurrent === true
|
|
|| props[ 'onUpdate:modelValue' ] === void 0
|
|
) {
|
|
animate(currentModel.value, name);
|
|
currentModel.value = name;
|
|
}
|
|
}
|
|
}
|
|
|
|
function recalculateScroll () {
|
|
registerScrollTick(() => {
|
|
updateContainer({
|
|
width: rootRef.value.offsetWidth,
|
|
height: rootRef.value.offsetHeight
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateContainer (domSize) {
|
|
// it can be called faster than component being initialized
|
|
// so we need to protect against that case
|
|
// (one example of such case is the docs release notes page)
|
|
if (domProps.value === void 0 || contentRef.value === null) { return }
|
|
|
|
const
|
|
size = domSize[ domProps.value.container ],
|
|
scrollSize = Math.min(
|
|
contentRef.value[ domProps.value.scroll ],
|
|
Array.prototype.reduce.call(
|
|
contentRef.value.children,
|
|
(acc, el) => acc + (el[ domProps.value.content ] || 0),
|
|
0
|
|
)
|
|
),
|
|
scroll = size > 0 && scrollSize > size; // when there is no tab, in Chrome, size === 0 and scrollSize === 1
|
|
|
|
scrollable.value = scroll;
|
|
|
|
// Arrows need to be updated even if the scroll status was already true
|
|
scroll === true && registerUpdateArrowsTick(updateArrows);
|
|
|
|
justify.value = size < parseInt(props.breakpoint, 10);
|
|
}
|
|
|
|
function animate (oldName, newName) {
|
|
const
|
|
oldTab = oldName !== void 0 && oldName !== null && oldName !== ''
|
|
? tabDataList.find(tab => tab.name.value === oldName)
|
|
: null,
|
|
newTab = newName !== void 0 && newName !== null && newName !== ''
|
|
? tabDataList.find(tab => tab.name.value === newName)
|
|
: null;
|
|
|
|
if (oldTab && newTab) {
|
|
const
|
|
oldEl = oldTab.tabIndicatorRef.value,
|
|
newEl = newTab.tabIndicatorRef.value;
|
|
|
|
if (animateTimer !== null) {
|
|
clearTimeout(animateTimer);
|
|
animateTimer = null;
|
|
}
|
|
|
|
oldEl.style.transition = 'none';
|
|
oldEl.style.transform = 'none';
|
|
newEl.style.transition = 'none';
|
|
newEl.style.transform = 'none';
|
|
|
|
const
|
|
oldPos = oldEl.getBoundingClientRect(),
|
|
newPos = newEl.getBoundingClientRect();
|
|
|
|
newEl.style.transform = props.vertical === true
|
|
? `translate3d(0,${ oldPos.top - newPos.top }px,0) scale3d(1,${ newPos.height ? oldPos.height / newPos.height : 1 },1)`
|
|
: `translate3d(${ oldPos.left - newPos.left }px,0,0) scale3d(${ newPos.width ? oldPos.width / newPos.width : 1 },1,1)`;
|
|
|
|
// allow scope updates to kick in (QRouteTab needs more time)
|
|
registerAnimateTick(() => {
|
|
animateTimer = setTimeout(() => {
|
|
animateTimer = null;
|
|
newEl.style.transition = 'transform .25s cubic-bezier(.4, 0, .2, 1)';
|
|
newEl.style.transform = 'none';
|
|
}, 70);
|
|
});
|
|
}
|
|
|
|
if (newTab && scrollable.value === true) {
|
|
scrollToTabEl(newTab.rootRef.value);
|
|
}
|
|
}
|
|
|
|
function scrollToTabEl (el) {
|
|
const
|
|
{ left, width, top, height } = contentRef.value.getBoundingClientRect(),
|
|
newPos = el.getBoundingClientRect();
|
|
|
|
let offset = props.vertical === true ? newPos.top - top : newPos.left - left;
|
|
|
|
if (offset < 0) {
|
|
contentRef.value[ props.vertical === true ? 'scrollTop' : 'scrollLeft' ] += Math.floor(offset);
|
|
updateArrows();
|
|
return
|
|
}
|
|
|
|
offset += props.vertical === true ? newPos.height - height : newPos.width - width;
|
|
if (offset > 0) {
|
|
contentRef.value[ props.vertical === true ? 'scrollTop' : 'scrollLeft' ] += Math.ceil(offset);
|
|
updateArrows();
|
|
}
|
|
}
|
|
|
|
function updateArrows () {
|
|
const content = contentRef.value;
|
|
if (content === null) { return }
|
|
|
|
const
|
|
rect = content.getBoundingClientRect(),
|
|
pos = props.vertical === true ? content.scrollTop : Math.abs(content.scrollLeft);
|
|
|
|
if (isRTL.value === true) {
|
|
leftArrow.value = Math.ceil(pos + rect.width) < content.scrollWidth - 1;
|
|
rightArrow.value = pos > 0;
|
|
}
|
|
else {
|
|
leftArrow.value = pos > 0;
|
|
rightArrow.value = props.vertical === true
|
|
? Math.ceil(pos + rect.height) < content.scrollHeight
|
|
: Math.ceil(pos + rect.width) < content.scrollWidth;
|
|
}
|
|
}
|
|
|
|
function animScrollTo (value) {
|
|
scrollTimer !== null && clearInterval(scrollTimer);
|
|
scrollTimer = setInterval(() => {
|
|
if (scrollTowards(value) === true) {
|
|
stopAnimScroll();
|
|
}
|
|
}, 5);
|
|
}
|
|
|
|
function scrollToStart () {
|
|
animScrollTo(rtlPosCorrection.value === true ? Number.MAX_SAFE_INTEGER : 0);
|
|
}
|
|
|
|
function scrollToEnd () {
|
|
animScrollTo(rtlPosCorrection.value === true ? 0 : Number.MAX_SAFE_INTEGER);
|
|
}
|
|
|
|
function stopAnimScroll () {
|
|
if (scrollTimer !== null) {
|
|
clearInterval(scrollTimer);
|
|
scrollTimer = null;
|
|
}
|
|
}
|
|
|
|
function onKbdNavigate (keyCode, fromEl) {
|
|
const tabs = Array.prototype.filter.call(
|
|
contentRef.value.children,
|
|
el => el === fromEl || (el.matches && el.matches('.q-tab.q-focusable') === true)
|
|
);
|
|
|
|
const len = tabs.length;
|
|
if (len === 0) { return }
|
|
|
|
if (keyCode === 36) { // Home
|
|
scrollToTabEl(tabs[ 0 ]);
|
|
tabs[ 0 ].focus();
|
|
return true
|
|
}
|
|
if (keyCode === 35) { // End
|
|
scrollToTabEl(tabs[ len - 1 ]);
|
|
tabs[ len - 1 ].focus();
|
|
return true
|
|
}
|
|
|
|
const dirPrev = keyCode === (props.vertical === true ? 38 /* ArrowUp */ : 37 /* ArrowLeft */);
|
|
const dirNext = keyCode === (props.vertical === true ? 40 /* ArrowDown */ : 39 /* ArrowRight */);
|
|
|
|
const dir = dirPrev === true ? -1 : (dirNext === true ? 1 : void 0);
|
|
|
|
if (dir !== void 0) {
|
|
const rtlDir = isRTL.value === true ? -1 : 1;
|
|
const index = tabs.indexOf(fromEl) + dir * rtlDir;
|
|
|
|
if (index >= 0 && index < len) {
|
|
scrollToTabEl(tabs[ index ]);
|
|
tabs[ index ].focus({ preventScroll: true });
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
// let's speed up execution of time-sensitive scrollTowards()
|
|
// with a computed variable by directly applying the minimal
|
|
// number of instructions on get/set functions
|
|
const posFn = vue.computed(() => (
|
|
rtlPosCorrection.value === true
|
|
? { get: content => Math.abs(content.scrollLeft), set: (content, pos) => { content.scrollLeft = -pos; } }
|
|
: (
|
|
props.vertical === true
|
|
? { get: content => content.scrollTop, set: (content, pos) => { content.scrollTop = pos; } }
|
|
: { get: content => content.scrollLeft, set: (content, pos) => { content.scrollLeft = pos; } }
|
|
)
|
|
));
|
|
|
|
function scrollTowards (value) {
|
|
const
|
|
content = contentRef.value,
|
|
{ get, set } = posFn.value;
|
|
|
|
let
|
|
done = false,
|
|
pos = get(content);
|
|
|
|
const direction = value < pos ? -1 : 1;
|
|
|
|
pos += direction * 5;
|
|
|
|
if (pos < 0) {
|
|
done = true;
|
|
pos = 0;
|
|
}
|
|
else if (
|
|
(direction === -1 && pos <= value)
|
|
|| (direction === 1 && pos >= value)
|
|
) {
|
|
done = true;
|
|
pos = value;
|
|
}
|
|
|
|
set(content, pos);
|
|
updateArrows();
|
|
|
|
return done
|
|
}
|
|
|
|
function hasQueryIncluded (targetQuery, matchingQuery) {
|
|
for (const key in targetQuery) {
|
|
if (targetQuery[ key ] !== matchingQuery[ key ]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// do not use directly; use verifyRouteModel() instead
|
|
function updateActiveRoute () {
|
|
let name = null, bestScore = { matchedLen: 0, queryDiff: 9999, hrefLen: 0 };
|
|
|
|
const list = tabDataList.filter(tab => tab.routeData !== void 0 && tab.routeData.hasRouterLink.value === true);
|
|
const { hash: currentHash, query: currentQuery } = proxy.$route;
|
|
const currentQueryLen = Object.keys(currentQuery).length;
|
|
|
|
// Vue Router does not keep account of hash & query when matching
|
|
// so we're doing this as well
|
|
|
|
for (const tab of list) {
|
|
const exact = tab.routeData.exact.value === true;
|
|
|
|
if (tab.routeData[ exact === true ? 'linkIsExactActive' : 'linkIsActive' ].value !== true) {
|
|
// it cannot match anything as it's not active nor exact-active
|
|
continue
|
|
}
|
|
|
|
const { hash, query, matched, href } = tab.routeData.resolvedLink.value;
|
|
const queryLen = Object.keys(query).length;
|
|
|
|
if (exact === true) {
|
|
if (hash !== currentHash) {
|
|
// it's set to exact but it doesn't matches the hash
|
|
continue
|
|
}
|
|
|
|
if (
|
|
queryLen !== currentQueryLen
|
|
|| hasQueryIncluded(currentQuery, query) === false
|
|
) {
|
|
// it's set to exact but it doesn't matches the query
|
|
continue
|
|
}
|
|
|
|
// yey, we found the perfect match (route + hash + query)
|
|
name = tab.name.value;
|
|
break
|
|
}
|
|
|
|
if (hash !== '' && hash !== currentHash) {
|
|
// it has hash and it doesn't matches
|
|
continue
|
|
}
|
|
|
|
if (
|
|
queryLen !== 0
|
|
&& hasQueryIncluded(query, currentQuery) === false
|
|
) {
|
|
// it has query and it doesn't includes the current one
|
|
continue
|
|
}
|
|
|
|
const newScore = {
|
|
matchedLen: matched.length,
|
|
queryDiff: currentQueryLen - queryLen,
|
|
hrefLen: href.length - hash.length
|
|
};
|
|
|
|
if (newScore.matchedLen > bestScore.matchedLen) {
|
|
// it matches more routes so it's more specific so we set it as current champion
|
|
name = tab.name.value;
|
|
bestScore = newScore;
|
|
continue
|
|
}
|
|
else if (newScore.matchedLen !== bestScore.matchedLen) {
|
|
// it matches less routes than the current champion so we discard it
|
|
continue
|
|
}
|
|
|
|
if (newScore.queryDiff < bestScore.queryDiff) {
|
|
// query is closer to the current one so we set it as current champion
|
|
name = tab.name.value;
|
|
bestScore = newScore;
|
|
}
|
|
else if (newScore.queryDiff !== bestScore.queryDiff) {
|
|
// it matches less routes than the current champion so we discard it
|
|
continue
|
|
}
|
|
|
|
if (newScore.hrefLen > bestScore.hrefLen) {
|
|
// href is lengthier so it's more specific so we set it as current champion
|
|
name = tab.name.value;
|
|
bestScore = newScore;
|
|
}
|
|
}
|
|
|
|
if (
|
|
name === null
|
|
&& tabDataList.some(tab => tab.routeData === void 0 && tab.name.value === currentModel.value) === true
|
|
) {
|
|
// we shouldn't interfere if non-route tab is active
|
|
return
|
|
}
|
|
|
|
updateModel({ name, setCurrent: true });
|
|
}
|
|
|
|
function onFocusin (e) {
|
|
removeFocusTimeout();
|
|
|
|
if (
|
|
hasFocus.value !== true
|
|
&& rootRef.value !== null
|
|
&& e.target
|
|
&& typeof e.target.closest === 'function'
|
|
) {
|
|
const tab = e.target.closest('.q-tab');
|
|
|
|
// if the target is contained by a QTab/QRouteTab
|
|
// (it might be other elements focused, like additional QBtn)
|
|
if (tab && rootRef.value.contains(tab) === true) {
|
|
hasFocus.value = true;
|
|
scrollable.value === true && scrollToTabEl(tab);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onFocusout () {
|
|
registerFocusTimeout(() => { hasFocus.value = false; }, 30);
|
|
}
|
|
|
|
function verifyRouteModel () {
|
|
if ($tabs.avoidRouteWatcher === false) {
|
|
registerScrollToTabTimeout(updateActiveRoute);
|
|
}
|
|
else {
|
|
removeScrollToTabTimeout();
|
|
}
|
|
}
|
|
|
|
function watchRoute () {
|
|
if (unwatchRoute === void 0) {
|
|
const unwatch = vue.watch(() => proxy.$route.fullPath, verifyRouteModel);
|
|
unwatchRoute = () => {
|
|
unwatch();
|
|
unwatchRoute = void 0;
|
|
};
|
|
}
|
|
}
|
|
|
|
function registerTab (tabData) {
|
|
tabDataList.push(tabData);
|
|
tabDataListLen.value++;
|
|
|
|
recalculateScroll();
|
|
|
|
// if it's a QTab or we don't have Vue Router
|
|
if (tabData.routeData === void 0 || proxy.$route === void 0) {
|
|
// we should position to the currently active tab (if any)
|
|
registerScrollToTabTimeout(() => {
|
|
if (scrollable.value === true) {
|
|
const value = currentModel.value;
|
|
const newTab = value !== void 0 && value !== null && value !== ''
|
|
? tabDataList.find(tab => tab.name.value === value)
|
|
: null;
|
|
|
|
newTab && scrollToTabEl(newTab.rootRef.value);
|
|
}
|
|
});
|
|
}
|
|
// else if it's a QRouteTab with a valid link
|
|
else {
|
|
// start watching route
|
|
watchRoute();
|
|
|
|
if (tabData.routeData.hasRouterLink.value === true) {
|
|
verifyRouteModel();
|
|
}
|
|
}
|
|
}
|
|
|
|
function unregisterTab (tabData) {
|
|
tabDataList.splice(tabDataList.indexOf(tabData), 1);
|
|
tabDataListLen.value--;
|
|
|
|
recalculateScroll();
|
|
|
|
if (unwatchRoute !== void 0 && tabData.routeData !== void 0) {
|
|
// unwatch route if we don't have any QRouteTabs left
|
|
if (tabDataList.every(tab => tab.routeData === void 0) === true) {
|
|
unwatchRoute();
|
|
}
|
|
|
|
// then update model
|
|
verifyRouteModel();
|
|
}
|
|
}
|
|
|
|
const $tabs = {
|
|
currentModel,
|
|
tabProps,
|
|
hasFocus,
|
|
hasActiveTab,
|
|
|
|
registerTab,
|
|
unregisterTab,
|
|
|
|
verifyRouteModel,
|
|
updateModel,
|
|
onKbdNavigate,
|
|
|
|
avoidRouteWatcher: false // false | string (uid)
|
|
};
|
|
|
|
vue.provide(tabsKey, $tabs);
|
|
|
|
function cleanup () {
|
|
animateTimer !== null && clearTimeout(animateTimer);
|
|
stopAnimScroll();
|
|
unwatchRoute !== void 0 && unwatchRoute();
|
|
}
|
|
|
|
let hadRouteWatcher;
|
|
|
|
vue.onBeforeUnmount(cleanup);
|
|
|
|
vue.onDeactivated(() => {
|
|
hadRouteWatcher = unwatchRoute !== void 0;
|
|
cleanup();
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
hadRouteWatcher === true && watchRoute();
|
|
recalculateScroll();
|
|
});
|
|
|
|
return () => {
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
role: 'tablist',
|
|
onFocusin,
|
|
onFocusout
|
|
}, [
|
|
vue.h(QResizeObserver, { onResize: updateContainer }),
|
|
|
|
vue.h('div', {
|
|
ref: contentRef,
|
|
class: innerClass.value,
|
|
onScroll: updateArrows
|
|
}, hSlot(slots.default)),
|
|
|
|
vue.h(QIcon, {
|
|
class: 'q-tabs__arrow q-tabs__arrow--left absolute q-tab__icon'
|
|
+ (leftArrow.value === true ? '' : ' q-tabs__arrow--faded'),
|
|
name: props.leftIcon || $q.iconSet.tabs[ props.vertical === true ? 'up' : 'left' ],
|
|
onMousedownPassive: scrollToStart,
|
|
onTouchstartPassive: scrollToStart,
|
|
onMouseupPassive: stopAnimScroll,
|
|
onMouseleavePassive: stopAnimScroll,
|
|
onTouchendPassive: stopAnimScroll
|
|
}),
|
|
|
|
vue.h(QIcon, {
|
|
class: 'q-tabs__arrow q-tabs__arrow--right absolute q-tab__icon'
|
|
+ (rightArrow.value === true ? '' : ' q-tabs__arrow--faded'),
|
|
name: props.rightIcon || $q.iconSet.tabs[ props.vertical === true ? 'down' : 'right' ],
|
|
onMousedownPassive: scrollToEnd,
|
|
onTouchstartPassive: scrollToEnd,
|
|
onMouseupPassive: stopAnimScroll,
|
|
onMouseleavePassive: stopAnimScroll,
|
|
onTouchendPassive: stopAnimScroll
|
|
})
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
let id$1 = 0;
|
|
|
|
const useTabEmits = [ 'click', 'keydown' ];
|
|
|
|
const useTabProps = {
|
|
icon: String,
|
|
label: [ Number, String ],
|
|
|
|
alert: [ Boolean, String ],
|
|
alertIcon: String,
|
|
|
|
name: {
|
|
type: [ Number, String ],
|
|
default: () => `t_${ id$1++ }`
|
|
},
|
|
|
|
noCaps: Boolean,
|
|
|
|
tabindex: [ String, Number ],
|
|
disable: Boolean,
|
|
|
|
contentClass: String,
|
|
|
|
ripple: {
|
|
type: [ Boolean, Object ],
|
|
default: true
|
|
}
|
|
};
|
|
|
|
function useTab (props, slots, emit, routeData) {
|
|
const $tabs = vue.inject(tabsKey, emptyRenderFn);
|
|
if ($tabs === emptyRenderFn) {
|
|
console.error('QTab/QRouteTab component needs to be child of QTabs');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const blurTargetRef = vue.ref(null);
|
|
const rootRef = vue.ref(null);
|
|
const tabIndicatorRef = vue.ref(null);
|
|
|
|
const ripple = vue.computed(() => (
|
|
props.disable === true || props.ripple === false
|
|
? false
|
|
: Object.assign(
|
|
{ keyCodes: [ 13, 32 ], early: true },
|
|
props.ripple === true ? {} : props.ripple
|
|
)
|
|
));
|
|
|
|
const isActive = vue.computed(() => $tabs.currentModel.value === props.name);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-tab relative-position self-stretch flex flex-center text-center'
|
|
+ (
|
|
isActive.value === true
|
|
? (
|
|
' q-tab--active'
|
|
+ ($tabs.tabProps.value.activeClass ? ' ' + $tabs.tabProps.value.activeClass : '')
|
|
+ ($tabs.tabProps.value.activeColor ? ` text-${ $tabs.tabProps.value.activeColor }` : '')
|
|
+ ($tabs.tabProps.value.activeBgColor ? ` bg-${ $tabs.tabProps.value.activeBgColor }` : '')
|
|
)
|
|
: ' q-tab--inactive'
|
|
)
|
|
+ (props.icon && props.label && $tabs.tabProps.value.inlineLabel === false ? ' q-tab--full' : '')
|
|
+ (props.noCaps === true || $tabs.tabProps.value.noCaps === true ? ' q-tab--no-caps' : '')
|
|
+ (props.disable === true ? ' disabled' : ' q-focusable q-hoverable cursor-pointer')
|
|
+ (routeData !== void 0 ? routeData.linkClass.value : '')
|
|
);
|
|
|
|
const innerClass = vue.computed(() =>
|
|
'q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable '
|
|
+ ($tabs.tabProps.value.inlineLabel === true ? 'row no-wrap q-tab__content--inline' : 'column')
|
|
+ (props.contentClass !== void 0 ? ` ${ props.contentClass }` : '')
|
|
);
|
|
|
|
const tabIndex = vue.computed(() => (
|
|
(
|
|
props.disable === true
|
|
|| $tabs.hasFocus.value === true
|
|
|| (isActive.value === false && $tabs.hasActiveTab.value === true)
|
|
)
|
|
? -1
|
|
: props.tabindex || 0
|
|
));
|
|
|
|
function onClick (e, keyboard) {
|
|
if (keyboard !== true && blurTargetRef.value !== null) {
|
|
blurTargetRef.value.focus();
|
|
}
|
|
|
|
if (props.disable === true) {
|
|
// we should hinder native navigation though
|
|
if (routeData !== void 0 && routeData.hasRouterLink.value === true) {
|
|
stopAndPrevent(e);
|
|
}
|
|
return
|
|
}
|
|
|
|
// do we have a QTab?
|
|
if (routeData === void 0) {
|
|
$tabs.updateModel({ name: props.name });
|
|
emit('click', e);
|
|
return
|
|
}
|
|
|
|
if (routeData.hasRouterLink.value === true) {
|
|
const go = (opts = {}) => {
|
|
// if requiring to go to another route, then we
|
|
// let the QTabs route watcher do its job,
|
|
// otherwise directly select this
|
|
let hardError;
|
|
const reqId = opts.to === void 0 || isDeepEqual(opts.to, props.to) === true
|
|
? ($tabs.avoidRouteWatcher = uid$3())
|
|
: null;
|
|
|
|
return routeData.navigateToRouterLink(e, { ...opts, returnRouterError: true })
|
|
.catch(err => { hardError = err; })
|
|
.then(softError => {
|
|
if (reqId === $tabs.avoidRouteWatcher) {
|
|
$tabs.avoidRouteWatcher = false;
|
|
|
|
// if we don't have any hard errors or any soft errors, except for
|
|
// when navigating to the same route (on all other soft errors,
|
|
// like when navigation was aborted in a nav guard, we don't activate this tab)
|
|
if (
|
|
hardError === void 0 && (
|
|
softError === void 0
|
|
|| softError.message.startsWith('Avoided redundant navigation') === true
|
|
)
|
|
) {
|
|
$tabs.updateModel({ name: props.name });
|
|
}
|
|
}
|
|
|
|
if (opts.returnRouterError === true) {
|
|
return hardError !== void 0 ? Promise.reject(hardError) : softError
|
|
}
|
|
})
|
|
};
|
|
|
|
emit('click', e, go);
|
|
e.defaultPrevented !== true && go();
|
|
|
|
return
|
|
}
|
|
|
|
emit('click', e);
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
if (isKeyCode(e, [ 13, 32 ])) {
|
|
onClick(e, true);
|
|
}
|
|
else if (
|
|
shouldIgnoreKey(e) !== true
|
|
&& e.keyCode >= 35
|
|
&& e.keyCode <= 40
|
|
&& e.altKey !== true
|
|
&& e.metaKey !== true
|
|
) {
|
|
$tabs.onKbdNavigate(e.keyCode, proxy.$el) === true && stopAndPrevent(e);
|
|
}
|
|
|
|
emit('keydown', e);
|
|
}
|
|
|
|
function getContent () {
|
|
const
|
|
narrow = $tabs.tabProps.value.narrowIndicator,
|
|
content = [],
|
|
indicator = vue.h('div', {
|
|
ref: tabIndicatorRef,
|
|
class: [
|
|
'q-tab__indicator',
|
|
$tabs.tabProps.value.indicatorClass
|
|
]
|
|
});
|
|
|
|
props.icon !== void 0 && content.push(
|
|
vue.h(QIcon, {
|
|
class: 'q-tab__icon',
|
|
name: props.icon
|
|
})
|
|
);
|
|
|
|
props.label !== void 0 && content.push(
|
|
vue.h('div', { class: 'q-tab__label' }, props.label)
|
|
);
|
|
|
|
props.alert !== false && content.push(
|
|
props.alertIcon !== void 0
|
|
? vue.h(QIcon, {
|
|
class: 'q-tab__alert-icon',
|
|
color: props.alert !== true
|
|
? props.alert
|
|
: void 0,
|
|
name: props.alertIcon
|
|
})
|
|
: vue.h('div', {
|
|
class: 'q-tab__alert'
|
|
+ (props.alert !== true ? ` text-${ props.alert }` : '')
|
|
})
|
|
);
|
|
|
|
narrow === true && content.push(indicator);
|
|
|
|
const node = [
|
|
vue.h('div', { class: 'q-focus-helper', tabindex: -1, ref: blurTargetRef }),
|
|
vue.h('div', { class: innerClass.value }, hMergeSlot(slots.default, content))
|
|
];
|
|
|
|
narrow === false && node.push(indicator);
|
|
|
|
return node
|
|
}
|
|
|
|
const tabData = {
|
|
name: vue.computed(() => props.name),
|
|
rootRef,
|
|
tabIndicatorRef,
|
|
routeData
|
|
};
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
$tabs.unregisterTab(tabData);
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
$tabs.registerTab(tabData);
|
|
});
|
|
|
|
function renderTab (tag, customData) {
|
|
const data = {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
tabindex: tabIndex.value,
|
|
role: 'tab',
|
|
'aria-selected': isActive.value === true ? 'true' : 'false',
|
|
'aria-disabled': props.disable === true ? 'true' : void 0,
|
|
onClick,
|
|
onKeydown,
|
|
...customData
|
|
};
|
|
|
|
return vue.withDirectives(
|
|
vue.h(tag, data, getContent()),
|
|
[ [ Ripple, ripple.value ] ]
|
|
)
|
|
}
|
|
|
|
return { renderTab, $tabs }
|
|
}
|
|
|
|
var QTab = createComponent({
|
|
name: 'QTab',
|
|
|
|
props: useTabProps,
|
|
|
|
emits: useTabEmits,
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { renderTab } = useTab(props, slots, emit);
|
|
return () => renderTab('div')
|
|
}
|
|
});
|
|
|
|
var QTabPanels = createComponent({
|
|
name: 'QTabPanels',
|
|
|
|
props: {
|
|
...usePanelProps,
|
|
...useDarkProps
|
|
},
|
|
|
|
emits: usePanelEmits,
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const { updatePanelsList, getPanelContent, panelDirectives } = usePanel();
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-tab-panels q-panel-parent'
|
|
+ (isDark.value === true ? ' q-tab-panels--dark q-dark' : '')
|
|
);
|
|
|
|
return () => {
|
|
updatePanelsList(slots);
|
|
|
|
return hDir(
|
|
'div',
|
|
{ class: classes.value },
|
|
getPanelContent(),
|
|
'pan',
|
|
props.swipeable,
|
|
() => panelDirectives.value
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QTabPanel = createComponent({
|
|
name: 'QTabPanel',
|
|
|
|
props: usePanelChildProps,
|
|
|
|
setup (_, { slots }) {
|
|
return () => vue.h('div', { class: 'q-tab-panel', role: 'tabpanel' }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
// file referenced from docs
|
|
|
|
const
|
|
hex = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/,
|
|
hexa = /^#[0-9a-fA-F]{4}([0-9a-fA-F]{4})?$/,
|
|
hexOrHexa = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/,
|
|
rgb = /^rgb\(((0|[1-9][\d]?|1[\d]{0,2}|2[\d]?|2[0-4][\d]|25[0-5]),){2}(0|[1-9][\d]?|1[\d]{0,2}|2[\d]?|2[0-4][\d]|25[0-5])\)$/,
|
|
rgba = /^rgba\(((0|[1-9][\d]?|1[\d]{0,2}|2[\d]?|2[0-4][\d]|25[0-5]),){2}(0|[1-9][\d]?|1[\d]{0,2}|2[\d]?|2[0-4][\d]|25[0-5]),(0|0\.[0-9]+[1-9]|0\.[1-9]+|1)\)$/;
|
|
|
|
// Keep in sync with ui/types/api/validation.d.ts
|
|
const testPattern = {
|
|
date: v => /^-?[\d]+\/[0-1]\d\/[0-3]\d$/.test(v),
|
|
time: v => /^([0-1]?\d|2[0-3]):[0-5]\d$/.test(v),
|
|
fulltime: v => /^([0-1]?\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(v),
|
|
timeOrFulltime: v => /^([0-1]?\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/.test(v),
|
|
|
|
// -- RFC 5322 --
|
|
// -- Added in v2.6.6 --
|
|
// This is a basic helper validation.
|
|
// For something more complex (like RFC 822) you should write and use your own rule.
|
|
// We won't be accepting PRs to enhance the one below because of the reason above.
|
|
// eslint-disable-next-line
|
|
email: v => /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(v),
|
|
|
|
hexColor: v => hex.test(v),
|
|
hexaColor: v => hexa.test(v),
|
|
hexOrHexaColor: v => hexOrHexa.test(v),
|
|
|
|
rgbColor: v => rgb.test(v),
|
|
rgbaColor: v => rgba.test(v),
|
|
rgbOrRgbaColor: v => rgb.test(v) || rgba.test(v),
|
|
|
|
hexOrRgbColor: v => hex.test(v) || rgb.test(v),
|
|
hexaOrRgbaColor: v => hexa.test(v) || rgba.test(v),
|
|
anyColor: v => hexOrHexa.test(v) || rgb.test(v) || rgba.test(v)
|
|
};
|
|
|
|
var patterns = {
|
|
testPattern
|
|
};
|
|
|
|
const reRGBA = /^rgb(a)?\((\d{1,3}),(\d{1,3}),(\d{1,3}),?([01]?\.?\d*?)?\)$/;
|
|
|
|
function rgbToHex ({ r, g, b, a }) {
|
|
const alpha = a !== void 0;
|
|
|
|
r = Math.round(r);
|
|
g = Math.round(g);
|
|
b = Math.round(b);
|
|
|
|
if (
|
|
r > 255
|
|
|| g > 255
|
|
|| b > 255
|
|
|| (alpha && a > 100)
|
|
) {
|
|
throw new TypeError('Expected 3 numbers below 256 (and optionally one below 100)')
|
|
}
|
|
|
|
a = alpha
|
|
? (Math.round(255 * a / 100) | 1 << 8).toString(16).slice(1)
|
|
: '';
|
|
|
|
return '#' + ((b | g << 8 | r << 16) | 1 << 24).toString(16).slice(1) + a
|
|
}
|
|
|
|
function rgbToString ({ r, g, b, a }) {
|
|
return `rgb${ a !== void 0 ? 'a' : '' }(${ r },${ g },${ b }${ a !== void 0 ? ',' + (a / 100) : '' })`
|
|
}
|
|
|
|
function hexToRgb (hex) {
|
|
if (typeof hex !== 'string') {
|
|
throw new TypeError('Expected a string')
|
|
}
|
|
|
|
hex = hex.replace(/^#/, '');
|
|
|
|
if (hex.length === 3) {
|
|
hex = hex[ 0 ] + hex[ 0 ] + hex[ 1 ] + hex[ 1 ] + hex[ 2 ] + hex[ 2 ];
|
|
}
|
|
else if (hex.length === 4) {
|
|
hex = hex[ 0 ] + hex[ 0 ] + hex[ 1 ] + hex[ 1 ] + hex[ 2 ] + hex[ 2 ] + hex[ 3 ] + hex[ 3 ];
|
|
}
|
|
|
|
const num = parseInt(hex, 16);
|
|
|
|
return hex.length > 6
|
|
? { r: num >> 24 & 255, g: num >> 16 & 255, b: num >> 8 & 255, a: Math.round((num & 255) / 2.55) }
|
|
: { r: num >> 16, g: num >> 8 & 255, b: num & 255 }
|
|
}
|
|
|
|
function hsvToRgb ({ h, s, v, a }) {
|
|
let r, g, b;
|
|
s = s / 100;
|
|
v = v / 100;
|
|
|
|
h = h / 360;
|
|
const
|
|
i = Math.floor(h * 6),
|
|
f = h * 6 - i,
|
|
p = v * (1 - s),
|
|
q = v * (1 - f * s),
|
|
t = v * (1 - (1 - f) * s);
|
|
|
|
switch (i % 6) {
|
|
case 0:
|
|
r = v;
|
|
g = t;
|
|
b = p;
|
|
break
|
|
case 1:
|
|
r = q;
|
|
g = v;
|
|
b = p;
|
|
break
|
|
case 2:
|
|
r = p;
|
|
g = v;
|
|
b = t;
|
|
break
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
b = v;
|
|
break
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
b = v;
|
|
break
|
|
case 5:
|
|
r = v;
|
|
g = p;
|
|
b = q;
|
|
break
|
|
}
|
|
|
|
return {
|
|
r: Math.round(r * 255),
|
|
g: Math.round(g * 255),
|
|
b: Math.round(b * 255),
|
|
a
|
|
}
|
|
}
|
|
|
|
function rgbToHsv ({ r, g, b, a }) {
|
|
const
|
|
max = Math.max(r, g, b),
|
|
min = Math.min(r, g, b),
|
|
d = max - min,
|
|
s = (max === 0 ? 0 : d / max),
|
|
v = max / 255;
|
|
let h;
|
|
|
|
switch (max) {
|
|
case min:
|
|
h = 0;
|
|
break
|
|
case r:
|
|
h = (g - b) + d * (g < b ? 6 : 0);
|
|
h /= 6 * d;
|
|
break
|
|
case g:
|
|
h = (b - r) + d * 2;
|
|
h /= 6 * d;
|
|
break
|
|
case b:
|
|
h = (r - g) + d * 4;
|
|
h /= 6 * d;
|
|
break
|
|
}
|
|
|
|
return {
|
|
h: Math.round(h * 360),
|
|
s: Math.round(s * 100),
|
|
v: Math.round(v * 100),
|
|
a
|
|
}
|
|
}
|
|
|
|
function textToRgb (str) {
|
|
if (typeof str !== 'string') {
|
|
throw new TypeError('Expected a string')
|
|
}
|
|
|
|
const color = str.replace(/ /g, '');
|
|
|
|
const m = reRGBA.exec(color);
|
|
|
|
if (m === null) {
|
|
return hexToRgb(color)
|
|
}
|
|
|
|
const rgb = {
|
|
r: Math.min(255, parseInt(m[ 2 ], 10)),
|
|
g: Math.min(255, parseInt(m[ 3 ], 10)),
|
|
b: Math.min(255, parseInt(m[ 4 ], 10))
|
|
};
|
|
|
|
if (m[ 1 ]) {
|
|
const alpha = parseFloat(m[ 5 ]);
|
|
rgb.a = Math.min(1, isNaN(alpha) === true ? 1 : alpha) * 100;
|
|
}
|
|
|
|
return rgb
|
|
}
|
|
|
|
/* works as darken if percent < 0 */
|
|
function lighten (color, percent) {
|
|
if (typeof color !== 'string') {
|
|
throw new TypeError('Expected a string as color')
|
|
}
|
|
if (typeof percent !== 'number') {
|
|
throw new TypeError('Expected a numeric percent')
|
|
}
|
|
|
|
const rgb = textToRgb(color),
|
|
t = percent < 0 ? 0 : 255,
|
|
p = Math.abs(percent) / 100,
|
|
R = rgb.r,
|
|
G = rgb.g,
|
|
B = rgb.b;
|
|
|
|
return '#' + (
|
|
0x1000000 + (Math.round((t - R) * p) + R) * 0x10000
|
|
+ (Math.round((t - G) * p) + G) * 0x100
|
|
+ (Math.round((t - B) * p) + B)
|
|
).toString(16).slice(1)
|
|
}
|
|
|
|
function luminosity (color) {
|
|
if (typeof color !== 'string' && (!color || color.r === void 0)) {
|
|
throw new TypeError('Expected a string or a {r, g, b} object as color')
|
|
}
|
|
|
|
const
|
|
rgb = typeof color === 'string' ? textToRgb(color) : color,
|
|
r = rgb.r / 255,
|
|
g = rgb.g / 255,
|
|
b = rgb.b / 255,
|
|
R = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4),
|
|
G = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4),
|
|
B = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
|
|
return 0.2126 * R + 0.7152 * G + 0.0722 * B
|
|
}
|
|
|
|
function brightness (color) {
|
|
if (typeof color !== 'string' && (!color || color.r === void 0)) {
|
|
throw new TypeError('Expected a string or a {r, g, b} object as color')
|
|
}
|
|
|
|
const rgb = typeof color === 'string'
|
|
? textToRgb(color)
|
|
: color;
|
|
|
|
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000
|
|
}
|
|
|
|
function blend (fgColor, bgColor) {
|
|
if (typeof fgColor !== 'string' && (!fgColor || fgColor.r === void 0)) {
|
|
throw new TypeError('Expected a string or a {r, g, b[, a]} object as fgColor')
|
|
}
|
|
|
|
if (typeof bgColor !== 'string' && (!bgColor || bgColor.r === void 0)) {
|
|
throw new TypeError('Expected a string or a {r, g, b[, a]} object as bgColor')
|
|
}
|
|
|
|
const
|
|
rgb1 = typeof fgColor === 'string' ? textToRgb(fgColor) : fgColor,
|
|
r1 = rgb1.r / 255,
|
|
g1 = rgb1.g / 255,
|
|
b1 = rgb1.b / 255,
|
|
a1 = rgb1.a !== void 0 ? rgb1.a / 100 : 1,
|
|
rgb2 = typeof bgColor === 'string' ? textToRgb(bgColor) : bgColor,
|
|
r2 = rgb2.r / 255,
|
|
g2 = rgb2.g / 255,
|
|
b2 = rgb2.b / 255,
|
|
a2 = rgb2.a !== void 0 ? rgb2.a / 100 : 1,
|
|
a = a1 + a2 * (1 - a1),
|
|
r = Math.round(((r1 * a1 + r2 * a2 * (1 - a1)) / a) * 255),
|
|
g = Math.round(((g1 * a1 + g2 * a2 * (1 - a1)) / a) * 255),
|
|
b = Math.round(((b1 * a1 + b2 * a2 * (1 - a1)) / a) * 255);
|
|
|
|
const ret = { r, g, b, a: Math.round(a * 100) };
|
|
return typeof fgColor === 'string'
|
|
? rgbToHex(ret)
|
|
: ret
|
|
}
|
|
|
|
function changeAlpha (color, offset) {
|
|
if (typeof color !== 'string') {
|
|
throw new TypeError('Expected a string as color')
|
|
}
|
|
|
|
if (offset === void 0 || offset < -1 || offset > 1) {
|
|
throw new TypeError('Expected offset to be between -1 and 1')
|
|
}
|
|
|
|
const { r, g, b, a } = textToRgb(color);
|
|
const alpha = a !== void 0 ? a / 100 : 0;
|
|
|
|
return rgbToHex({
|
|
r, g, b, a: Math.round(Math.min(1, Math.max(0, alpha + offset)) * 100)
|
|
})
|
|
}
|
|
|
|
function getPaletteColor (colorName) {
|
|
if (typeof colorName !== 'string') {
|
|
throw new TypeError('Expected a string as color')
|
|
}
|
|
|
|
const el = document.createElement('div');
|
|
|
|
el.className = `text-${ colorName } invisible fixed no-pointer-events`;
|
|
document.body.appendChild(el);
|
|
|
|
const result = getComputedStyle(el).getPropertyValue('color');
|
|
|
|
el.remove();
|
|
|
|
return rgbToHex(textToRgb(result))
|
|
}
|
|
|
|
var colors = {
|
|
rgbToHex,
|
|
hexToRgb,
|
|
hsvToRgb,
|
|
rgbToHsv,
|
|
textToRgb,
|
|
lighten,
|
|
luminosity,
|
|
brightness,
|
|
blend,
|
|
changeAlpha,
|
|
getPaletteColor
|
|
};
|
|
|
|
const palette = [
|
|
'rgb(255,204,204)', 'rgb(255,230,204)', 'rgb(255,255,204)', 'rgb(204,255,204)', 'rgb(204,255,230)', 'rgb(204,255,255)', 'rgb(204,230,255)', 'rgb(204,204,255)', 'rgb(230,204,255)', 'rgb(255,204,255)',
|
|
'rgb(255,153,153)', 'rgb(255,204,153)', 'rgb(255,255,153)', 'rgb(153,255,153)', 'rgb(153,255,204)', 'rgb(153,255,255)', 'rgb(153,204,255)', 'rgb(153,153,255)', 'rgb(204,153,255)', 'rgb(255,153,255)',
|
|
'rgb(255,102,102)', 'rgb(255,179,102)', 'rgb(255,255,102)', 'rgb(102,255,102)', 'rgb(102,255,179)', 'rgb(102,255,255)', 'rgb(102,179,255)', 'rgb(102,102,255)', 'rgb(179,102,255)', 'rgb(255,102,255)',
|
|
'rgb(255,51,51)', 'rgb(255,153,51)', 'rgb(255,255,51)', 'rgb(51,255,51)', 'rgb(51,255,153)', 'rgb(51,255,255)', 'rgb(51,153,255)', 'rgb(51,51,255)', 'rgb(153,51,255)', 'rgb(255,51,255)',
|
|
'rgb(255,0,0)', 'rgb(255,128,0)', 'rgb(255,255,0)', 'rgb(0,255,0)', 'rgb(0,255,128)', 'rgb(0,255,255)', 'rgb(0,128,255)', 'rgb(0,0,255)', 'rgb(128,0,255)', 'rgb(255,0,255)',
|
|
'rgb(245,0,0)', 'rgb(245,123,0)', 'rgb(245,245,0)', 'rgb(0,245,0)', 'rgb(0,245,123)', 'rgb(0,245,245)', 'rgb(0,123,245)', 'rgb(0,0,245)', 'rgb(123,0,245)', 'rgb(245,0,245)',
|
|
'rgb(214,0,0)', 'rgb(214,108,0)', 'rgb(214,214,0)', 'rgb(0,214,0)', 'rgb(0,214,108)', 'rgb(0,214,214)', 'rgb(0,108,214)', 'rgb(0,0,214)', 'rgb(108,0,214)', 'rgb(214,0,214)',
|
|
'rgb(163,0,0)', 'rgb(163,82,0)', 'rgb(163,163,0)', 'rgb(0,163,0)', 'rgb(0,163,82)', 'rgb(0,163,163)', 'rgb(0,82,163)', 'rgb(0,0,163)', 'rgb(82,0,163)', 'rgb(163,0,163)',
|
|
'rgb(92,0,0)', 'rgb(92,46,0)', 'rgb(92,92,0)', 'rgb(0,92,0)', 'rgb(0,92,46)', 'rgb(0,92,92)', 'rgb(0,46,92)', 'rgb(0,0,92)', 'rgb(46,0,92)', 'rgb(92,0,92)',
|
|
'rgb(255,255,255)', 'rgb(205,205,205)', 'rgb(178,178,178)', 'rgb(153,153,153)', 'rgb(127,127,127)', 'rgb(102,102,102)', 'rgb(76,76,76)', 'rgb(51,51,51)', 'rgb(25,25,25)', 'rgb(0,0,0)'
|
|
];
|
|
|
|
const thumbPath = 'M5 5 h10 v10 h-10 v-10 z';
|
|
const alphaTrackImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAH0lEQVQoU2NkYGAwZkAFZ5G5jPRRgOYEVDeB3EBjBQBOZwTVugIGyAAAAABJRU5ErkJggg==';
|
|
|
|
var QColor = createComponent({
|
|
name: 'QColor',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useFormProps,
|
|
|
|
modelValue: String,
|
|
|
|
defaultValue: String,
|
|
defaultView: {
|
|
type: String,
|
|
default: 'spectrum',
|
|
validator: v => [ 'spectrum', 'tune', 'palette' ].includes(v)
|
|
},
|
|
|
|
formatModel: {
|
|
type: String,
|
|
default: 'auto',
|
|
validator: v => [ 'auto', 'hex', 'rgb', 'hexa', 'rgba' ].includes(v)
|
|
},
|
|
|
|
palette: Array,
|
|
|
|
noHeader: Boolean,
|
|
noHeaderTabs: Boolean,
|
|
noFooter: Boolean,
|
|
|
|
square: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
|
|
disable: Boolean,
|
|
readonly: Boolean
|
|
},
|
|
|
|
emits: [ 'update:modelValue', 'change' ],
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { getCache } = useCache();
|
|
|
|
const spectrumRef = vue.ref(null);
|
|
const errorIconRef = vue.ref(null);
|
|
|
|
const forceHex = vue.computed(() => (
|
|
props.formatModel === 'auto'
|
|
? null
|
|
: props.formatModel.indexOf('hex') > -1
|
|
));
|
|
|
|
const forceAlpha = vue.computed(() => (
|
|
props.formatModel === 'auto'
|
|
? null
|
|
: props.formatModel.indexOf('a') > -1
|
|
));
|
|
|
|
const topView = vue.ref(
|
|
props.formatModel === 'auto'
|
|
? (
|
|
(props.modelValue === void 0 || props.modelValue === null || props.modelValue === '' || props.modelValue.startsWith('#'))
|
|
? 'hex'
|
|
: 'rgb'
|
|
)
|
|
: (props.formatModel.startsWith('hex') ? 'hex' : 'rgb')
|
|
);
|
|
|
|
const view = vue.ref(props.defaultView);
|
|
const model = vue.ref(parseModel(props.modelValue || props.defaultValue));
|
|
|
|
const editable = vue.computed(() => props.disable !== true && props.readonly !== true);
|
|
|
|
const isHex = vue.computed(() =>
|
|
props.modelValue === void 0
|
|
|| props.modelValue === null
|
|
|| props.modelValue === ''
|
|
|| props.modelValue.startsWith('#')
|
|
);
|
|
|
|
const isOutputHex = vue.computed(() => (
|
|
forceHex.value !== null
|
|
? forceHex.value
|
|
: isHex.value
|
|
));
|
|
|
|
const formAttrs = vue.computed(() => ({
|
|
type: 'hidden',
|
|
name: props.name,
|
|
value: model.value[ isOutputHex.value === true ? 'hex' : 'rgb' ]
|
|
}));
|
|
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const hasAlpha = vue.computed(() => (
|
|
forceAlpha.value !== null
|
|
? forceAlpha.value
|
|
: model.value.a !== void 0
|
|
));
|
|
|
|
const currentBgColor = vue.computed(() => ({
|
|
backgroundColor: model.value.rgb || '#000'
|
|
}));
|
|
|
|
const headerClass = vue.computed(() => {
|
|
const light = model.value.a !== void 0 && model.value.a < 65
|
|
? true
|
|
: luminosity(model.value) > 0.4;
|
|
|
|
return 'q-color-picker__header-content'
|
|
+ ` q-color-picker__header-content--${ light ? 'light' : 'dark' }`
|
|
});
|
|
|
|
const spectrumStyle = vue.computed(() => ({
|
|
background: `hsl(${ model.value.h },100%,50%)`
|
|
}));
|
|
|
|
const spectrumPointerStyle = vue.computed(() => ({
|
|
top: `${ 100 - model.value.v }%`,
|
|
[ $q.lang.rtl === true ? 'right' : 'left' ]: `${ model.value.s }%`
|
|
}));
|
|
|
|
const computedPalette = vue.computed(() => (
|
|
props.palette !== void 0 && props.palette.length !== 0
|
|
? props.palette
|
|
: palette
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-color-picker'
|
|
+ (props.bordered === true ? ' q-color-picker--bordered' : '')
|
|
+ (props.square === true ? ' q-color-picker--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-color-picker--flat no-shadow' : '')
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (isDark.value === true ? ' q-color-picker--dark q-dark' : '')
|
|
);
|
|
|
|
const attributes = vue.computed(() => {
|
|
if (props.disable === true) {
|
|
return { 'aria-disabled': 'true' }
|
|
}
|
|
if (props.readonly === true) {
|
|
return { 'aria-readonly': 'true' }
|
|
}
|
|
return {}
|
|
});
|
|
|
|
const spectrumDirective = vue.computed(() => {
|
|
// if editable.value === true
|
|
return [ [
|
|
TouchPan,
|
|
onSpectrumPan,
|
|
void 0,
|
|
{ prevent: true, stop: true, mouse: true }
|
|
] ]
|
|
});
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
const localModel = parseModel(v || props.defaultValue);
|
|
if (localModel.hex !== model.value.hex) {
|
|
model.value = localModel;
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.defaultValue, v => {
|
|
if (!props.modelValue && v) {
|
|
const localModel = parseModel(v);
|
|
if (localModel.hex !== model.value.hex) {
|
|
model.value = localModel;
|
|
}
|
|
}
|
|
});
|
|
|
|
function updateModel (rgb, change) {
|
|
// update internally
|
|
model.value.hex = rgbToHex(rgb);
|
|
model.value.rgb = rgbToString(rgb);
|
|
model.value.r = rgb.r;
|
|
model.value.g = rgb.g;
|
|
model.value.b = rgb.b;
|
|
model.value.a = rgb.a;
|
|
|
|
const value = model.value[ isOutputHex.value === true ? 'hex' : 'rgb' ];
|
|
|
|
// emit new value
|
|
emit('update:modelValue', value);
|
|
change === true && emit('change', value);
|
|
}
|
|
|
|
function parseModel (v) {
|
|
const alpha = forceAlpha.value !== void 0
|
|
? forceAlpha.value
|
|
: (
|
|
props.formatModel === 'auto'
|
|
? null
|
|
: props.formatModel.indexOf('a') > -1
|
|
);
|
|
|
|
if (typeof v !== 'string' || v.length === 0 || testPattern.anyColor(v.replace(/ /g, '')) !== true) {
|
|
return {
|
|
h: 0,
|
|
s: 0,
|
|
v: 0,
|
|
r: 0,
|
|
g: 0,
|
|
b: 0,
|
|
a: alpha === true ? 100 : void 0,
|
|
hex: void 0,
|
|
rgb: void 0
|
|
}
|
|
}
|
|
|
|
const model = textToRgb(v);
|
|
|
|
if (alpha === true && model.a === void 0) {
|
|
model.a = 100;
|
|
}
|
|
|
|
model.hex = rgbToHex(model);
|
|
model.rgb = rgbToString(model);
|
|
|
|
return Object.assign(model, rgbToHsv(model))
|
|
}
|
|
|
|
function changeSpectrum (left, top, change) {
|
|
const panel = spectrumRef.value;
|
|
if (panel === null) { return }
|
|
|
|
const
|
|
width = panel.clientWidth,
|
|
height = panel.clientHeight,
|
|
rect = panel.getBoundingClientRect();
|
|
|
|
let x = Math.min(width, Math.max(0, left - rect.left));
|
|
|
|
if ($q.lang.rtl === true) {
|
|
x = width - x;
|
|
}
|
|
|
|
const
|
|
y = Math.min(height, Math.max(0, top - rect.top)),
|
|
s = Math.round(100 * x / width),
|
|
v = Math.round(100 * Math.max(0, Math.min(1, -(y / height) + 1))),
|
|
rgb = hsvToRgb({
|
|
h: model.value.h,
|
|
s,
|
|
v,
|
|
a: hasAlpha.value === true ? model.value.a : void 0
|
|
});
|
|
|
|
model.value.s = s;
|
|
model.value.v = v;
|
|
updateModel(rgb, change);
|
|
}
|
|
|
|
function onHueChange (val, change) {
|
|
const h = Math.round(val);
|
|
const rgb = hsvToRgb({
|
|
h,
|
|
s: model.value.s,
|
|
v: model.value.v,
|
|
a: hasAlpha.value === true ? model.value.a : void 0
|
|
});
|
|
|
|
model.value.h = h;
|
|
updateModel(rgb, change);
|
|
}
|
|
|
|
function onNumericChange (value, formatModel, max, evt, change) {
|
|
evt !== void 0 && stop(evt);
|
|
|
|
if (!/^[0-9]+$/.test(value)) {
|
|
change === true && proxy.$forceUpdate();
|
|
return
|
|
}
|
|
|
|
const val = Math.floor(Number(value));
|
|
|
|
if (val < 0 || val > max) {
|
|
change === true && proxy.$forceUpdate();
|
|
return
|
|
}
|
|
|
|
const rgb = {
|
|
r: formatModel === 'r' ? val : model.value.r,
|
|
g: formatModel === 'g' ? val : model.value.g,
|
|
b: formatModel === 'b' ? val : model.value.b,
|
|
a: hasAlpha.value === true
|
|
? (formatModel === 'a' ? val : model.value.a)
|
|
: void 0
|
|
};
|
|
|
|
if (formatModel !== 'a') {
|
|
const hsv = rgbToHsv(rgb);
|
|
model.value.h = hsv.h;
|
|
model.value.s = hsv.s;
|
|
model.value.v = hsv.v;
|
|
}
|
|
|
|
updateModel(rgb, change);
|
|
|
|
if (evt !== void 0 && change !== true && evt.target.selectionEnd !== void 0) {
|
|
const index = evt.target.selectionEnd;
|
|
vue.nextTick(() => {
|
|
evt.target.setSelectionRange(index, index);
|
|
});
|
|
}
|
|
}
|
|
|
|
function onEditorChange (evt, change) {
|
|
let rgb;
|
|
const inp = evt.target.value;
|
|
|
|
stop(evt);
|
|
|
|
if (topView.value === 'hex') {
|
|
if (
|
|
inp.length !== (hasAlpha.value === true ? 9 : 7)
|
|
|| !/^#[0-9A-Fa-f]+$/.test(inp)
|
|
) {
|
|
return true
|
|
}
|
|
|
|
rgb = hexToRgb(inp);
|
|
}
|
|
else {
|
|
let model;
|
|
|
|
if (!inp.endsWith(')')) {
|
|
return true
|
|
}
|
|
else if (hasAlpha.value !== true && inp.startsWith('rgb(')) {
|
|
model = inp.substring(4, inp.length - 1).split(',').map(n => parseInt(n, 10));
|
|
|
|
if (
|
|
model.length !== 3
|
|
|| !/^rgb\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3}\)$/.test(inp)
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
else if (hasAlpha.value === true && inp.startsWith('rgba(')) {
|
|
model = inp.substring(5, inp.length - 1).split(',');
|
|
|
|
if (
|
|
model.length !== 4
|
|
|| !/^rgba\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(0|0\.[0-9]+[1-9]|0\.[1-9]+|1)\)$/.test(inp)
|
|
) {
|
|
return true
|
|
}
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
const v = parseInt(model[ i ], 10);
|
|
if (v < 0 || v > 255) {
|
|
return true
|
|
}
|
|
model[ i ] = v;
|
|
}
|
|
|
|
const v = parseFloat(model[ 3 ]);
|
|
if (v < 0 || v > 1) {
|
|
return true
|
|
}
|
|
model[ 3 ] = v;
|
|
}
|
|
else {
|
|
return true
|
|
}
|
|
|
|
if (
|
|
model[ 0 ] < 0 || model[ 0 ] > 255
|
|
|| model[ 1 ] < 0 || model[ 1 ] > 255
|
|
|| model[ 2 ] < 0 || model[ 2 ] > 255
|
|
|| (hasAlpha.value === true && (model[ 3 ] < 0 || model[ 3 ] > 1))
|
|
) {
|
|
return true
|
|
}
|
|
|
|
rgb = {
|
|
r: model[ 0 ],
|
|
g: model[ 1 ],
|
|
b: model[ 2 ],
|
|
a: hasAlpha.value === true
|
|
? model[ 3 ] * 100
|
|
: void 0
|
|
};
|
|
}
|
|
|
|
const hsv = rgbToHsv(rgb);
|
|
model.value.h = hsv.h;
|
|
model.value.s = hsv.s;
|
|
model.value.v = hsv.v;
|
|
|
|
updateModel(rgb, change);
|
|
|
|
if (change !== true) {
|
|
const index = evt.target.selectionEnd;
|
|
vue.nextTick(() => {
|
|
evt.target.setSelectionRange(index, index);
|
|
});
|
|
}
|
|
}
|
|
|
|
function onPalettePick (color) {
|
|
const def = parseModel(color);
|
|
const rgb = { r: def.r, g: def.g, b: def.b, a: def.a };
|
|
|
|
if (rgb.a === void 0) {
|
|
rgb.a = model.value.a;
|
|
}
|
|
|
|
model.value.h = def.h;
|
|
model.value.s = def.s;
|
|
model.value.v = def.v;
|
|
|
|
updateModel(rgb, true);
|
|
}
|
|
|
|
function onSpectrumPan (evt) {
|
|
if (evt.isFinal) {
|
|
changeSpectrum(
|
|
evt.position.left,
|
|
evt.position.top,
|
|
true
|
|
);
|
|
}
|
|
else {
|
|
onSpectrumChange(evt);
|
|
}
|
|
}
|
|
|
|
const onSpectrumChange = throttle(
|
|
evt => { changeSpectrum(evt.position.left, evt.position.top); },
|
|
20
|
|
);
|
|
|
|
function onSpectrumClick (evt) {
|
|
changeSpectrum(
|
|
evt.pageX - window.pageXOffset,
|
|
evt.pageY - window.pageYOffset,
|
|
true
|
|
);
|
|
}
|
|
|
|
function onActivate (evt) {
|
|
changeSpectrum(
|
|
evt.pageX - window.pageXOffset,
|
|
evt.pageY - window.pageYOffset
|
|
);
|
|
}
|
|
|
|
function updateErrorIcon (val) {
|
|
// we MUST avoid vue triggering a render,
|
|
// so manually changing this
|
|
if (errorIconRef.value !== null) {
|
|
errorIconRef.value.$el.style.opacity = val ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
function getHeader () {
|
|
const child = [];
|
|
|
|
props.noHeaderTabs !== true && child.push(
|
|
vue.h(QTabs, {
|
|
class: 'q-color-picker__header-tabs',
|
|
modelValue: topView.value,
|
|
dense: true,
|
|
align: 'justify',
|
|
...getCache('topVTab', {
|
|
'onUpdate:modelValue': val => { topView.value = val; }
|
|
})
|
|
}, () => [
|
|
vue.h(QTab, {
|
|
label: 'HEX' + (hasAlpha.value === true ? 'A' : ''),
|
|
name: 'hex',
|
|
ripple: false
|
|
}),
|
|
|
|
vue.h(QTab, {
|
|
label: 'RGB' + (hasAlpha.value === true ? 'A' : ''),
|
|
name: 'rgb',
|
|
ripple: false
|
|
})
|
|
])
|
|
);
|
|
|
|
child.push(
|
|
vue.h('div', {
|
|
class: 'q-color-picker__header-banner row flex-center no-wrap'
|
|
}, [
|
|
vue.h('input', {
|
|
class: 'fit',
|
|
value: model.value[ topView.value ],
|
|
...(editable.value !== true
|
|
? { readonly: true }
|
|
: {}
|
|
),
|
|
...getCache('topIn', {
|
|
onInput: evt => {
|
|
updateErrorIcon(onEditorChange(evt) === true);
|
|
},
|
|
onChange: stop,
|
|
onBlur: evt => {
|
|
onEditorChange(evt, true) === true && proxy.$forceUpdate();
|
|
updateErrorIcon(false);
|
|
}
|
|
})
|
|
}),
|
|
|
|
vue.h(QIcon, {
|
|
ref: errorIconRef,
|
|
class: 'q-color-picker__error-icon absolute no-pointer-events',
|
|
name: $q.iconSet.type.negative
|
|
})
|
|
])
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: 'q-color-picker__header relative-position overflow-hidden'
|
|
}, [
|
|
vue.h('div', { class: 'q-color-picker__header-bg absolute-full' }),
|
|
|
|
vue.h('div', {
|
|
class: headerClass.value,
|
|
style: currentBgColor.value
|
|
}, child)
|
|
])
|
|
}
|
|
|
|
function getContent () {
|
|
return vue.h(QTabPanels, {
|
|
modelValue: view.value,
|
|
animated: true
|
|
}, () => [
|
|
vue.h(QTabPanel, {
|
|
class: 'q-color-picker__spectrum-tab overflow-hidden',
|
|
name: 'spectrum'
|
|
}, getSpectrumTab),
|
|
|
|
vue.h(QTabPanel, {
|
|
class: 'q-pa-md q-color-picker__tune-tab',
|
|
name: 'tune'
|
|
}, getTuneTab),
|
|
|
|
vue.h(QTabPanel, {
|
|
class: 'q-color-picker__palette-tab',
|
|
name: 'palette'
|
|
}, getPaletteTab)
|
|
])
|
|
}
|
|
|
|
function getFooter () {
|
|
return vue.h('div', {
|
|
class: 'q-color-picker__footer relative-position overflow-hidden'
|
|
}, [
|
|
vue.h(QTabs, {
|
|
class: 'absolute-full',
|
|
modelValue: view.value,
|
|
dense: true,
|
|
align: 'justify',
|
|
...getCache('ftIn', {
|
|
'onUpdate:modelValue': val => { view.value = val; }
|
|
})
|
|
}, () => [
|
|
vue.h(QTab, {
|
|
icon: $q.iconSet.colorPicker.spectrum,
|
|
name: 'spectrum',
|
|
ripple: false
|
|
}),
|
|
|
|
vue.h(QTab, {
|
|
icon: $q.iconSet.colorPicker.tune,
|
|
name: 'tune',
|
|
ripple: false
|
|
}),
|
|
|
|
vue.h(QTab, {
|
|
icon: $q.iconSet.colorPicker.palette,
|
|
name: 'palette',
|
|
ripple: false
|
|
})
|
|
])
|
|
])
|
|
}
|
|
|
|
function getSpectrumTab () {
|
|
const data = {
|
|
ref: spectrumRef,
|
|
class: 'q-color-picker__spectrum non-selectable relative-position cursor-pointer'
|
|
+ (editable.value !== true ? ' readonly' : ''),
|
|
style: spectrumStyle.value,
|
|
...(editable.value === true
|
|
? {
|
|
onClick: onSpectrumClick,
|
|
onMousedown: onActivate
|
|
}
|
|
: {}
|
|
)
|
|
};
|
|
|
|
const child = [
|
|
vue.h('div', { style: { paddingBottom: '100%' } }),
|
|
vue.h('div', { class: 'q-color-picker__spectrum-white absolute-full' }),
|
|
vue.h('div', { class: 'q-color-picker__spectrum-black absolute-full' }),
|
|
vue.h('div', {
|
|
class: 'absolute',
|
|
style: spectrumPointerStyle.value
|
|
}, [
|
|
model.value.hex !== void 0
|
|
? vue.h('div', { class: 'q-color-picker__spectrum-circle' })
|
|
: null
|
|
])
|
|
];
|
|
|
|
const sliders = [
|
|
vue.h(QSlider, {
|
|
class: 'q-color-picker__hue non-selectable',
|
|
modelValue: model.value.h,
|
|
min: 0,
|
|
max: 360,
|
|
trackSize: '8px',
|
|
innerTrackColor: 'transparent',
|
|
selectionColor: 'transparent',
|
|
readonly: editable.value !== true,
|
|
thumbPath,
|
|
'onUpdate:modelValue': onHueChange,
|
|
...getCache('lazyhue', {
|
|
onChange: val => onHueChange(val, true)
|
|
})
|
|
})
|
|
];
|
|
|
|
hasAlpha.value === true && sliders.push(
|
|
vue.h(QSlider, {
|
|
class: 'q-color-picker__alpha non-selectable',
|
|
modelValue: model.value.a,
|
|
min: 0,
|
|
max: 100,
|
|
trackSize: '8px',
|
|
trackColor: 'white',
|
|
innerTrackColor: 'transparent',
|
|
selectionColor: 'transparent',
|
|
trackImg: alphaTrackImg,
|
|
readonly: editable.value !== true,
|
|
hideSelection: true,
|
|
thumbPath,
|
|
...getCache('alphaSlide', {
|
|
'onUpdate:modelValue': value => onNumericChange(value, 'a', 100),
|
|
onChange: value => onNumericChange(value, 'a', 100, void 0, true)
|
|
})
|
|
})
|
|
);
|
|
|
|
return [
|
|
hDir('div', data, child, 'spec', editable.value, () => spectrumDirective.value),
|
|
vue.h('div', { class: 'q-color-picker__sliders' }, sliders)
|
|
]
|
|
}
|
|
|
|
function getTuneTab () {
|
|
return [
|
|
vue.h('div', { class: 'row items-center no-wrap' }, [
|
|
vue.h('div', 'R'),
|
|
vue.h(QSlider, {
|
|
modelValue: model.value.r,
|
|
min: 0,
|
|
max: 255,
|
|
color: 'red',
|
|
dark: isDark.value,
|
|
readonly: editable.value !== true,
|
|
...getCache('rSlide', {
|
|
'onUpdate:modelValue': value => onNumericChange(value, 'r', 255),
|
|
onChange: value => onNumericChange(value, 'r', 255, void 0, true)
|
|
})
|
|
}),
|
|
vue.h('input', {
|
|
value: model.value.r,
|
|
maxlength: 3,
|
|
readonly: editable.value !== true,
|
|
onChange: stop,
|
|
...getCache('rIn', {
|
|
onInput: evt => onNumericChange(evt.target.value, 'r', 255, evt),
|
|
onBlur: evt => onNumericChange(evt.target.value, 'r', 255, evt, true)
|
|
})
|
|
})
|
|
]),
|
|
|
|
vue.h('div', { class: 'row items-center no-wrap' }, [
|
|
vue.h('div', 'G'),
|
|
vue.h(QSlider, {
|
|
modelValue: model.value.g,
|
|
min: 0,
|
|
max: 255,
|
|
color: 'green',
|
|
dark: isDark.value,
|
|
readonly: editable.value !== true,
|
|
...getCache('gSlide', {
|
|
'onUpdate:modelValue': value => onNumericChange(value, 'g', 255),
|
|
onChange: value => onNumericChange(value, 'g', 255, void 0, true)
|
|
})
|
|
}),
|
|
vue.h('input', {
|
|
value: model.value.g,
|
|
maxlength: 3,
|
|
readonly: editable.value !== true,
|
|
onChange: stop,
|
|
...getCache('gIn', {
|
|
onInput: evt => onNumericChange(evt.target.value, 'g', 255, evt),
|
|
onBlur: evt => onNumericChange(evt.target.value, 'g', 255, evt, true)
|
|
})
|
|
})
|
|
]),
|
|
|
|
vue.h('div', { class: 'row items-center no-wrap' }, [
|
|
vue.h('div', 'B'),
|
|
vue.h(QSlider, {
|
|
modelValue: model.value.b,
|
|
min: 0,
|
|
max: 255,
|
|
color: 'blue',
|
|
readonly: editable.value !== true,
|
|
dark: isDark.value,
|
|
...getCache('bSlide', {
|
|
'onUpdate:modelValue': value => onNumericChange(value, 'b', 255),
|
|
onChange: value => onNumericChange(value, 'b', 255, void 0, true)
|
|
})
|
|
}),
|
|
vue.h('input', {
|
|
value: model.value.b,
|
|
maxlength: 3,
|
|
readonly: editable.value !== true,
|
|
onChange: stop,
|
|
...getCache('bIn', {
|
|
onInput: evt => onNumericChange(evt.target.value, 'b', 255, evt),
|
|
onBlur: evt => onNumericChange(evt.target.value, 'b', 255, evt, true)
|
|
})
|
|
})
|
|
]),
|
|
|
|
hasAlpha.value === true ? vue.h('div', { class: 'row items-center no-wrap' }, [
|
|
vue.h('div', 'A'),
|
|
vue.h(QSlider, {
|
|
modelValue: model.value.a,
|
|
color: 'grey',
|
|
readonly: editable.value !== true,
|
|
dark: isDark.value,
|
|
...getCache('aSlide', {
|
|
'onUpdate:modelValue': value => onNumericChange(value, 'a', 100),
|
|
onChange: value => onNumericChange(value, 'a', 100, void 0, true)
|
|
})
|
|
}),
|
|
vue.h('input', {
|
|
value: model.value.a,
|
|
maxlength: 3,
|
|
readonly: editable.value !== true,
|
|
onChange: stop,
|
|
...getCache('aIn', {
|
|
onInput: evt => onNumericChange(evt.target.value, 'a', 100, evt),
|
|
onBlur: evt => onNumericChange(evt.target.value, 'a', 100, evt, true)
|
|
})
|
|
})
|
|
]) : null
|
|
]
|
|
}
|
|
|
|
function getPaletteTab () {
|
|
const fn = color => vue.h('div', {
|
|
class: 'q-color-picker__cube col-auto',
|
|
style: { backgroundColor: color },
|
|
...(
|
|
editable.value === true
|
|
? getCache('palette#' + color, {
|
|
onClick: () => { onPalettePick(color); }
|
|
})
|
|
: {}
|
|
)
|
|
});
|
|
|
|
return [
|
|
vue.h('div', {
|
|
class: 'row items-center q-color-picker__palette-rows'
|
|
+ (editable.value === true ? ' q-color-picker__palette-rows--editable' : '')
|
|
}, computedPalette.value.map(fn))
|
|
]
|
|
}
|
|
|
|
return () => {
|
|
const child = [ getContent() ];
|
|
|
|
if (props.name !== void 0 && props.disable !== true) {
|
|
injectFormInput(child, 'push');
|
|
}
|
|
|
|
props.noHeader !== true && child.unshift(
|
|
getHeader()
|
|
);
|
|
|
|
props.noFooter !== true && child.push(
|
|
getFooter()
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
...attributes.value
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
// taken from https://github.com/jalaali/jalaali-js
|
|
|
|
/*
|
|
Jalaali years starting the 33-year rule.
|
|
*/
|
|
const breaks = [
|
|
-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210,
|
|
1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
|
|
];
|
|
|
|
/*
|
|
Converts a Gregorian date to Jalaali.
|
|
*/
|
|
function toJalaali (gy, gm, gd) {
|
|
if (Object.prototype.toString.call(gy) === '[object Date]') {
|
|
gd = gy.getDate();
|
|
gm = gy.getMonth() + 1;
|
|
gy = gy.getFullYear();
|
|
}
|
|
return d2j(g2d(gy, gm, gd))
|
|
}
|
|
|
|
/*
|
|
Converts a Jalaali date to Gregorian.
|
|
*/
|
|
function toGregorian (jy, jm, jd) {
|
|
return d2g(j2d(jy, jm, jd))
|
|
}
|
|
|
|
/*
|
|
Is this a leap year or not?
|
|
*/
|
|
function isLeapJalaaliYear (jy) {
|
|
return jalCalLeap(jy) === 0
|
|
}
|
|
|
|
/*
|
|
Number of days in a given month in a Jalaali year.
|
|
*/
|
|
function jalaaliMonthLength (jy, jm) {
|
|
if (jm <= 6) return 31
|
|
if (jm <= 11) return 30
|
|
if (isLeapJalaaliYear(jy)) return 30
|
|
return 29
|
|
}
|
|
|
|
/*
|
|
This function determines if the Jalaali (Persian) year is
|
|
leap (366-day long) or is the common year (365 days)
|
|
|
|
@param jy Jalaali calendar year (-61 to 3177)
|
|
@returns number of years since the last leap year (0 to 4)
|
|
*/
|
|
function jalCalLeap (jy) {
|
|
const bl = breaks.length;
|
|
let
|
|
jp = breaks[ 0 ],
|
|
jm,
|
|
jump,
|
|
leap,
|
|
n,
|
|
i;
|
|
|
|
if (jy < jp || jy >= breaks[ bl - 1 ]) { throw new Error('Invalid Jalaali year ' + jy) }
|
|
|
|
for (i = 1; i < bl; i += 1) {
|
|
jm = breaks[ i ];
|
|
jump = jm - jp;
|
|
if (jy < jm) { break }
|
|
jp = jm;
|
|
}
|
|
n = jy - jp;
|
|
|
|
if (jump - n < 6) { n = n - jump + div(jump + 4, 33) * 33; }
|
|
leap = mod(mod(n + 1, 33) - 1, 4);
|
|
if (leap === -1) {
|
|
leap = 4;
|
|
}
|
|
|
|
return leap
|
|
}
|
|
|
|
/*
|
|
This function determines if the Jalaali (Persian) year is
|
|
leap (366-day long) or is the common year (365 days), and
|
|
finds the day in March (Gregorian calendar) of the first
|
|
day of the Jalaali year (jy).
|
|
|
|
@param jy Jalaali calendar year (-61 to 3177)
|
|
@param withoutLeap when don't need leap (true or false) default is false
|
|
@return
|
|
leap: number of years since the last leap year (0 to 4)
|
|
gy: Gregorian year of the beginning of Jalaali year
|
|
march: the March day of Farvardin the 1st (1st day of jy)
|
|
@see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm
|
|
@see: http://www.fourmilab.ch/documents/calendar/
|
|
*/
|
|
function jalCal (jy, withoutLeap) {
|
|
const
|
|
bl = breaks.length,
|
|
gy = jy + 621;
|
|
let
|
|
leapJ = -14,
|
|
jp = breaks[ 0 ],
|
|
jm,
|
|
jump,
|
|
leap,
|
|
n,
|
|
i;
|
|
|
|
if (jy < jp || jy >= breaks[ bl - 1 ]) { throw new Error('Invalid Jalaali year ' + jy) }
|
|
|
|
// Find the limiting years for the Jalaali year jy.
|
|
for (i = 1; i < bl; i += 1) {
|
|
jm = breaks[ i ];
|
|
jump = jm - jp;
|
|
if (jy < jm) { break }
|
|
leapJ = leapJ + div(jump, 33) * 8 + div(mod(jump, 33), 4);
|
|
jp = jm;
|
|
}
|
|
n = jy - jp;
|
|
|
|
// Find the number of leap years from AD 621 to the beginning
|
|
// of the current Jalaali year in the Persian calendar.
|
|
leapJ = leapJ + div(n, 33) * 8 + div(mod(n, 33) + 3, 4);
|
|
if (mod(jump, 33) === 4 && jump - n === 4) { leapJ += 1; }
|
|
|
|
// And the same in the Gregorian calendar (until the year gy).
|
|
const leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150;
|
|
|
|
// Determine the Gregorian date of Farvardin the 1st.
|
|
const march = 20 + leapJ - leapG;
|
|
|
|
// Find how many years have passed since the last leap year.
|
|
if (!withoutLeap) {
|
|
if (jump - n < 6) { n = n - jump + div(jump + 4, 33) * 33; }
|
|
leap = mod(mod(n + 1, 33) - 1, 4);
|
|
if (leap === -1) {
|
|
leap = 4;
|
|
}
|
|
}
|
|
|
|
return {
|
|
leap,
|
|
gy,
|
|
march
|
|
}
|
|
}
|
|
|
|
/*
|
|
Converts a date of the Jalaali calendar to the Julian Day number.
|
|
|
|
@param jy Jalaali year (1 to 3100)
|
|
@param jm Jalaali month (1 to 12)
|
|
@param jd Jalaali day (1 to 29/31)
|
|
@return Julian Day number
|
|
*/
|
|
function j2d (jy, jm, jd) {
|
|
const r = jalCal(jy, true);
|
|
return g2d(r.gy, 3, r.march) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1
|
|
}
|
|
|
|
/*
|
|
Converts the Julian Day number to a date in the Jalaali calendar.
|
|
|
|
@param jdn Julian Day number
|
|
@return
|
|
jy: Jalaali year (1 to 3100)
|
|
jm: Jalaali month (1 to 12)
|
|
jd: Jalaali day (1 to 29/31)
|
|
*/
|
|
function d2j (jdn) {
|
|
const gy = d2g(jdn).gy; // Calculate Gregorian year (gy).
|
|
let
|
|
jy = gy - 621,
|
|
jd,
|
|
jm,
|
|
k;
|
|
const
|
|
r = jalCal(jy, false),
|
|
jdn1f = g2d(gy, 3, r.march);
|
|
|
|
// Find number of days that passed since 1 Farvardin.
|
|
k = jdn - jdn1f;
|
|
if (k >= 0) {
|
|
if (k <= 185) {
|
|
// The first 6 months.
|
|
jm = 1 + div(k, 31);
|
|
jd = mod(k, 31) + 1;
|
|
return {
|
|
jy,
|
|
jm,
|
|
jd
|
|
}
|
|
}
|
|
else {
|
|
// The remaining months.
|
|
k -= 186;
|
|
}
|
|
}
|
|
else {
|
|
// Previous Jalaali year.
|
|
jy -= 1;
|
|
k += 179;
|
|
if (r.leap === 1) { k += 1; }
|
|
}
|
|
jm = 7 + div(k, 30);
|
|
jd = mod(k, 30) + 1;
|
|
return {
|
|
jy,
|
|
jm,
|
|
jd
|
|
}
|
|
}
|
|
|
|
/*
|
|
Calculates the Julian Day number from Gregorian or Julian
|
|
calendar dates. This integer number corresponds to the noon of
|
|
the date (i.e. 12 hours of Universal Time).
|
|
The procedure was tested to be good since 1 March, -100100 (of both
|
|
calendars) up to a few million years into the future.
|
|
|
|
@param gy Calendar year (years BC numbered 0, -1, -2, ...)
|
|
@param gm Calendar month (1 to 12)
|
|
@param gd Calendar day of the month (1 to 28/29/30/31)
|
|
@return Julian Day number
|
|
*/
|
|
function g2d (gy, gm, gd) {
|
|
let d = div((gy + div(gm - 8, 6) + 100100) * 1461, 4)
|
|
+ div(153 * mod(gm + 9, 12) + 2, 5)
|
|
+ gd - 34840408;
|
|
d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752;
|
|
return d
|
|
}
|
|
|
|
/*
|
|
Calculates Gregorian and Julian calendar dates from the Julian Day number
|
|
(jdn) for the period since jdn=-34839655 (i.e. the year -100100 of both
|
|
calendars) to some millions years ahead of the present.
|
|
|
|
@param jdn Julian Day number
|
|
@return
|
|
gy: Calendar year (years BC numbered 0, -1, -2, ...)
|
|
gm: Calendar month (1 to 12)
|
|
gd: Calendar day of the month M (1 to 28/29/30/31)
|
|
*/
|
|
function d2g (jdn) {
|
|
let j = 4 * jdn + 139361631;
|
|
j = j + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
|
|
const
|
|
i = div(mod(j, 1461), 4) * 5 + 308,
|
|
gd = div(mod(i, 153), 5) + 1,
|
|
gm = mod(div(i, 153), 12) + 1,
|
|
gy = div(j, 1461) - 100100 + div(8 - gm, 6);
|
|
return {
|
|
gy,
|
|
gm,
|
|
gd
|
|
}
|
|
}
|
|
|
|
/*
|
|
Utility helper functions.
|
|
*/
|
|
|
|
function div (a, b) {
|
|
return ~~(a / b)
|
|
}
|
|
|
|
function mod (a, b) {
|
|
return a - ~~(a / b) * b
|
|
}
|
|
|
|
const calendars = [ 'gregorian', 'persian' ];
|
|
|
|
const useDatetimeProps = {
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
|
|
mask: {
|
|
type: String
|
|
},
|
|
locale: Object,
|
|
|
|
calendar: {
|
|
type: String,
|
|
validator: v => calendars.includes(v),
|
|
default: 'gregorian'
|
|
},
|
|
|
|
landscape: Boolean,
|
|
|
|
color: String,
|
|
textColor: String,
|
|
|
|
square: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
|
|
readonly: Boolean,
|
|
disable: Boolean
|
|
};
|
|
|
|
const useDatetimeEmits = [ 'update:modelValue' ];
|
|
|
|
function getDayHash (date) {
|
|
return date.year + '/' + pad(date.month) + '/' + pad(date.day)
|
|
}
|
|
|
|
function useDatetime (props, $q) {
|
|
const editable = vue.computed(() => {
|
|
return props.disable !== true && props.readonly !== true
|
|
});
|
|
|
|
const tabindex = vue.computed(() => {
|
|
return editable.value === true ? 0 : -1
|
|
});
|
|
|
|
const headerClass = vue.computed(() => {
|
|
const cls = [];
|
|
props.color !== void 0 && cls.push(`bg-${ props.color }`);
|
|
props.textColor !== void 0 && cls.push(`text-${ props.textColor }`);
|
|
return cls.join(' ')
|
|
});
|
|
|
|
function getLocale () {
|
|
return props.locale !== void 0
|
|
? { ...$q.lang.date, ...props.locale }
|
|
: $q.lang.date
|
|
}
|
|
|
|
function getCurrentDate (dateOnly) {
|
|
const d = new Date();
|
|
const timeFill = dateOnly === true ? null : 0;
|
|
|
|
if (props.calendar === 'persian') {
|
|
const jDate = toJalaali(d);
|
|
return {
|
|
year: jDate.jy,
|
|
month: jDate.jm,
|
|
day: jDate.jd
|
|
}
|
|
}
|
|
|
|
return {
|
|
year: d.getFullYear(),
|
|
month: d.getMonth() + 1,
|
|
day: d.getDate(),
|
|
hour: timeFill,
|
|
minute: timeFill,
|
|
second: timeFill,
|
|
millisecond: timeFill
|
|
}
|
|
}
|
|
|
|
return {
|
|
editable,
|
|
tabindex,
|
|
headerClass,
|
|
|
|
getLocale,
|
|
getCurrentDate
|
|
}
|
|
}
|
|
|
|
/* eslint no-fallthrough: 0 */
|
|
|
|
const
|
|
MILLISECONDS_IN_DAY = 86400000,
|
|
MILLISECONDS_IN_HOUR = 3600000,
|
|
MILLISECONDS_IN_MINUTE = 60000,
|
|
defaultMask = 'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
|
token = /\[((?:[^\]\\]|\\]|\\)*)\]|d{1,4}|M{1,4}|m{1,2}|w{1,2}|Qo|Do|D{1,4}|YY(?:YY)?|H{1,2}|h{1,2}|s{1,2}|S{1,3}|Z{1,2}|a{1,2}|[AQExX]/g,
|
|
reverseToken = /(\[[^\]]*\])|d{1,4}|M{1,4}|m{1,2}|w{1,2}|Qo|Do|D{1,4}|YY(?:YY)?|H{1,2}|h{1,2}|s{1,2}|S{1,3}|Z{1,2}|a{1,2}|[AQExX]|([.*+:?^,\s${}()|\\]+)/g,
|
|
regexStore = {};
|
|
|
|
function getRegexData (mask, dateLocale) {
|
|
const
|
|
days = '(' + dateLocale.days.join('|') + ')',
|
|
key = mask + days;
|
|
|
|
if (regexStore[ key ] !== void 0) {
|
|
return regexStore[ key ]
|
|
}
|
|
|
|
const
|
|
daysShort = '(' + dateLocale.daysShort.join('|') + ')',
|
|
months = '(' + dateLocale.months.join('|') + ')',
|
|
monthsShort = '(' + dateLocale.monthsShort.join('|') + ')';
|
|
|
|
const map = {};
|
|
let index = 0;
|
|
|
|
const regexText = mask.replace(reverseToken, match => {
|
|
index++;
|
|
switch (match) {
|
|
case 'YY':
|
|
map.YY = index;
|
|
return '(-?\\d{1,2})'
|
|
case 'YYYY':
|
|
map.YYYY = index;
|
|
return '(-?\\d{1,4})'
|
|
case 'M':
|
|
map.M = index;
|
|
return '(\\d{1,2})'
|
|
case 'MM':
|
|
map.M = index; // bumping to M
|
|
return '(\\d{2})'
|
|
case 'MMM':
|
|
map.MMM = index;
|
|
return monthsShort
|
|
case 'MMMM':
|
|
map.MMMM = index;
|
|
return months
|
|
case 'D':
|
|
map.D = index;
|
|
return '(\\d{1,2})'
|
|
case 'Do':
|
|
map.D = index++; // bumping to D
|
|
return '(\\d{1,2}(st|nd|rd|th))'
|
|
case 'DD':
|
|
map.D = index; // bumping to D
|
|
return '(\\d{2})'
|
|
case 'H':
|
|
map.H = index;
|
|
return '(\\d{1,2})'
|
|
case 'HH':
|
|
map.H = index; // bumping to H
|
|
return '(\\d{2})'
|
|
case 'h':
|
|
map.h = index;
|
|
return '(\\d{1,2})'
|
|
case 'hh':
|
|
map.h = index; // bumping to h
|
|
return '(\\d{2})'
|
|
case 'm':
|
|
map.m = index;
|
|
return '(\\d{1,2})'
|
|
case 'mm':
|
|
map.m = index; // bumping to m
|
|
return '(\\d{2})'
|
|
case 's':
|
|
map.s = index;
|
|
return '(\\d{1,2})'
|
|
case 'ss':
|
|
map.s = index; // bumping to s
|
|
return '(\\d{2})'
|
|
case 'S':
|
|
map.S = index;
|
|
return '(\\d{1})'
|
|
case 'SS':
|
|
map.S = index; // bump to S
|
|
return '(\\d{2})'
|
|
case 'SSS':
|
|
map.S = index; // bump to S
|
|
return '(\\d{3})'
|
|
case 'A':
|
|
map.A = index;
|
|
return '(AM|PM)'
|
|
case 'a':
|
|
map.a = index;
|
|
return '(am|pm)'
|
|
case 'aa':
|
|
map.aa = index;
|
|
return '(a\\.m\\.|p\\.m\\.)'
|
|
|
|
case 'ddd':
|
|
return daysShort
|
|
case 'dddd':
|
|
return days
|
|
case 'Q':
|
|
case 'd':
|
|
case 'E':
|
|
return '(\\d{1})'
|
|
case 'Qo':
|
|
return '(1st|2nd|3rd|4th)'
|
|
case 'DDD':
|
|
case 'DDDD':
|
|
return '(\\d{1,3})'
|
|
case 'w':
|
|
return '(\\d{1,2})'
|
|
case 'ww':
|
|
return '(\\d{2})'
|
|
|
|
case 'Z': // to split: (?:(Z)()()|([+-])?(\\d{2}):?(\\d{2}))
|
|
map.Z = index;
|
|
return '(Z|[+-]\\d{2}:\\d{2})'
|
|
case 'ZZ':
|
|
map.ZZ = index;
|
|
return '(Z|[+-]\\d{2}\\d{2})'
|
|
|
|
case 'X':
|
|
map.X = index;
|
|
return '(-?\\d+)'
|
|
case 'x':
|
|
map.x = index;
|
|
return '(-?\\d{4,})'
|
|
|
|
default:
|
|
index--;
|
|
if (match[ 0 ] === '[') {
|
|
match = match.substring(1, match.length - 1);
|
|
}
|
|
return match.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|
|
});
|
|
|
|
const res = { map, regex: new RegExp('^' + regexText) };
|
|
regexStore[ key ] = res;
|
|
|
|
return res
|
|
}
|
|
|
|
function getDateLocale (paramDateLocale, langProps) {
|
|
return paramDateLocale !== void 0
|
|
? paramDateLocale
|
|
: (
|
|
langProps !== void 0
|
|
? langProps.date
|
|
: defaultLang.date
|
|
)
|
|
}
|
|
|
|
function formatTimezone (offset, delimeter = '') {
|
|
const
|
|
sign = offset > 0 ? '-' : '+',
|
|
absOffset = Math.abs(offset),
|
|
hours = Math.floor(absOffset / 60),
|
|
minutes = absOffset % 60;
|
|
|
|
return sign + pad(hours) + delimeter + pad(minutes)
|
|
}
|
|
|
|
function applyYearMonthDayChange (date, mod, sign) {
|
|
let
|
|
year = date.getFullYear(),
|
|
month = date.getMonth();
|
|
|
|
const day = date.getDate();
|
|
|
|
if (mod.year !== void 0) {
|
|
year += sign * mod.year;
|
|
delete mod.year;
|
|
}
|
|
|
|
if (mod.month !== void 0) {
|
|
month += sign * mod.month;
|
|
delete mod.month;
|
|
}
|
|
|
|
date.setDate(1);
|
|
date.setMonth(2);
|
|
|
|
date.setFullYear(year);
|
|
date.setMonth(month);
|
|
date.setDate(Math.min(day, daysInMonth(date)));
|
|
|
|
if (mod.date !== void 0) {
|
|
date.setDate(date.getDate() + sign * mod.date);
|
|
delete mod.date;
|
|
}
|
|
|
|
return date
|
|
}
|
|
|
|
function applyYearMonthDay (date, mod, middle) {
|
|
const
|
|
year = mod.year !== void 0 ? mod.year : date[ `get${ middle }FullYear` ](),
|
|
month = mod.month !== void 0 ? mod.month - 1 : date[ `get${ middle }Month` ](),
|
|
maxDay = (new Date(year, month + 1, 0)).getDate(),
|
|
day = Math.min(maxDay, mod.date !== void 0 ? mod.date : date[ `get${ middle }Date` ]());
|
|
|
|
date[ `set${ middle }Date` ](1);
|
|
date[ `set${ middle }Month` ](2);
|
|
|
|
date[ `set${ middle }FullYear` ](year);
|
|
date[ `set${ middle }Month` ](month);
|
|
date[ `set${ middle }Date` ](day);
|
|
|
|
delete mod.year;
|
|
delete mod.month;
|
|
delete mod.date;
|
|
|
|
return date
|
|
}
|
|
|
|
function getChange (date, rawMod, sign) {
|
|
const
|
|
mod = normalizeMod(rawMod),
|
|
d = new Date(date),
|
|
t = mod.year !== void 0 || mod.month !== void 0 || mod.date !== void 0
|
|
? applyYearMonthDayChange(d, mod, sign) // removes year/month/day
|
|
: d;
|
|
|
|
for (const key in mod) {
|
|
const op = capitalize(key);
|
|
t[ `set${ op }` ](t[ `get${ op }` ]() + sign * mod[ key ]);
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
function normalizeMod (mod) {
|
|
const acc = { ...mod };
|
|
|
|
if (mod.years !== void 0) {
|
|
acc.year = mod.years;
|
|
delete acc.years;
|
|
}
|
|
|
|
if (mod.months !== void 0) {
|
|
acc.month = mod.months;
|
|
delete acc.months;
|
|
}
|
|
|
|
if (mod.days !== void 0) {
|
|
acc.date = mod.days;
|
|
delete acc.days;
|
|
}
|
|
if (mod.day !== void 0) {
|
|
acc.date = mod.day;
|
|
delete acc.day;
|
|
}
|
|
|
|
if (mod.hour !== void 0) {
|
|
acc.hours = mod.hour;
|
|
delete acc.hour;
|
|
}
|
|
|
|
if (mod.minute !== void 0) {
|
|
acc.minutes = mod.minute;
|
|
delete acc.minute;
|
|
}
|
|
|
|
if (mod.second !== void 0) {
|
|
acc.seconds = mod.second;
|
|
delete acc.second;
|
|
}
|
|
|
|
if (mod.millisecond !== void 0) {
|
|
acc.milliseconds = mod.millisecond;
|
|
delete acc.millisecond;
|
|
}
|
|
|
|
return acc
|
|
}
|
|
|
|
function adjustDate (date, rawMod, utc) {
|
|
const
|
|
mod = normalizeMod(rawMod),
|
|
middle = utc === true ? 'UTC' : '',
|
|
d = new Date(date),
|
|
t = mod.year !== void 0 || mod.month !== void 0 || mod.date !== void 0
|
|
? applyYearMonthDay(d, mod, middle) // removes year/month/day
|
|
: d;
|
|
|
|
for (const key in mod) {
|
|
const op = key.charAt(0).toUpperCase() + key.slice(1);
|
|
t[ `set${ middle }${ op }` ](mod[ key ]);
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
function extractDate (str, mask, dateLocale) {
|
|
const d = __splitDate(str, mask, dateLocale);
|
|
|
|
const date = new Date(
|
|
d.year,
|
|
d.month === null ? null : d.month - 1,
|
|
d.day === null ? 1 : d.day,
|
|
d.hour,
|
|
d.minute,
|
|
d.second,
|
|
d.millisecond
|
|
);
|
|
|
|
const tzOffset = date.getTimezoneOffset();
|
|
|
|
return d.timezoneOffset === null || d.timezoneOffset === tzOffset
|
|
? date
|
|
: getChange(date, { minutes: d.timezoneOffset - tzOffset }, 1)
|
|
}
|
|
|
|
function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
|
|
const date = {
|
|
year: null,
|
|
month: null,
|
|
day: null,
|
|
hour: null,
|
|
minute: null,
|
|
second: null,
|
|
millisecond: null,
|
|
timezoneOffset: null,
|
|
dateHash: null,
|
|
timeHash: null
|
|
};
|
|
|
|
defaultModel !== void 0 && Object.assign(date, defaultModel);
|
|
|
|
if (
|
|
str === void 0
|
|
|| str === null
|
|
|| str === ''
|
|
|| typeof str !== 'string'
|
|
) {
|
|
return date
|
|
}
|
|
|
|
if (mask === void 0) {
|
|
mask = defaultMask;
|
|
}
|
|
|
|
const
|
|
langOpts = getDateLocale(dateLocale, Plugin$8.props),
|
|
months = langOpts.months,
|
|
monthsShort = langOpts.monthsShort;
|
|
|
|
const { regex, map } = getRegexData(mask, langOpts);
|
|
|
|
const match = str.match(regex);
|
|
|
|
if (match === null) {
|
|
return date
|
|
}
|
|
|
|
let tzString = '';
|
|
|
|
if (map.X !== void 0 || map.x !== void 0) {
|
|
const stamp = parseInt(match[ map.X !== void 0 ? map.X : map.x ], 10);
|
|
|
|
if (isNaN(stamp) === true || stamp < 0) {
|
|
return date
|
|
}
|
|
|
|
const d = new Date(stamp * (map.X !== void 0 ? 1000 : 1));
|
|
|
|
date.year = d.getFullYear();
|
|
date.month = d.getMonth() + 1;
|
|
date.day = d.getDate();
|
|
date.hour = d.getHours();
|
|
date.minute = d.getMinutes();
|
|
date.second = d.getSeconds();
|
|
date.millisecond = d.getMilliseconds();
|
|
}
|
|
else {
|
|
if (map.YYYY !== void 0) {
|
|
date.year = parseInt(match[ map.YYYY ], 10);
|
|
}
|
|
else if (map.YY !== void 0) {
|
|
const y = parseInt(match[ map.YY ], 10);
|
|
date.year = y < 0 ? y : 2000 + y;
|
|
}
|
|
|
|
if (map.M !== void 0) {
|
|
date.month = parseInt(match[ map.M ], 10);
|
|
if (date.month < 1 || date.month > 12) {
|
|
return date
|
|
}
|
|
}
|
|
else if (map.MMM !== void 0) {
|
|
date.month = monthsShort.indexOf(match[ map.MMM ]) + 1;
|
|
}
|
|
else if (map.MMMM !== void 0) {
|
|
date.month = months.indexOf(match[ map.MMMM ]) + 1;
|
|
}
|
|
|
|
if (map.D !== void 0) {
|
|
date.day = parseInt(match[ map.D ], 10);
|
|
|
|
if (date.year === null || date.month === null || date.day < 1) {
|
|
return date
|
|
}
|
|
|
|
const maxDay = calendar !== 'persian'
|
|
? (new Date(date.year, date.month, 0)).getDate()
|
|
: jalaaliMonthLength(date.year, date.month);
|
|
|
|
if (date.day > maxDay) {
|
|
return date
|
|
}
|
|
}
|
|
|
|
if (map.H !== void 0) {
|
|
date.hour = parseInt(match[ map.H ], 10) % 24;
|
|
}
|
|
else if (map.h !== void 0) {
|
|
date.hour = parseInt(match[ map.h ], 10) % 12;
|
|
if (
|
|
(map.A && match[ map.A ] === 'PM')
|
|
|| (map.a && match[ map.a ] === 'pm')
|
|
|| (map.aa && match[ map.aa ] === 'p.m.')
|
|
) {
|
|
date.hour += 12;
|
|
}
|
|
date.hour = date.hour % 24;
|
|
}
|
|
|
|
if (map.m !== void 0) {
|
|
date.minute = parseInt(match[ map.m ], 10) % 60;
|
|
}
|
|
|
|
if (map.s !== void 0) {
|
|
date.second = parseInt(match[ map.s ], 10) % 60;
|
|
}
|
|
|
|
if (map.S !== void 0) {
|
|
date.millisecond = parseInt(match[ map.S ], 10) * 10 ** (3 - match[ map.S ].length);
|
|
}
|
|
|
|
if (map.Z !== void 0 || map.ZZ !== void 0) {
|
|
tzString = (map.Z !== void 0 ? match[ map.Z ].replace(':', '') : match[ map.ZZ ]);
|
|
date.timezoneOffset = (tzString[ 0 ] === '+' ? -1 : 1) * (60 * tzString.slice(1, 3) + 1 * tzString.slice(3, 5));
|
|
}
|
|
}
|
|
|
|
date.dateHash = pad(date.year, 6) + '/' + pad(date.month) + '/' + pad(date.day);
|
|
date.timeHash = pad(date.hour) + ':' + pad(date.minute) + ':' + pad(date.second) + tzString;
|
|
|
|
return date
|
|
}
|
|
|
|
function isValid (date) {
|
|
return typeof date === 'number'
|
|
? true
|
|
: isNaN(Date.parse(date)) === false
|
|
}
|
|
|
|
function buildDate (mod, utc) {
|
|
return adjustDate(new Date(), mod, utc)
|
|
}
|
|
|
|
function getDayOfWeek (date) {
|
|
const dow = new Date(date).getDay();
|
|
return dow === 0 ? 7 : dow
|
|
}
|
|
|
|
function getWeekOfYear (date) {
|
|
// Remove time components of date
|
|
const thursday = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
// Change date to Thursday same week
|
|
thursday.setDate(thursday.getDate() - ((thursday.getDay() + 6) % 7) + 3);
|
|
|
|
// Take January 4th as it is always in week 1 (see ISO 8601)
|
|
const firstThursday = new Date(thursday.getFullYear(), 0, 4);
|
|
|
|
// Change date to Thursday same week
|
|
firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3);
|
|
|
|
// Check if daylight-saving-time-switch occurred and correct for it
|
|
const ds = thursday.getTimezoneOffset() - firstThursday.getTimezoneOffset();
|
|
thursday.setHours(thursday.getHours() - ds);
|
|
|
|
// Number of weeks between target Thursday and first Thursday
|
|
const weekDiff = (thursday - firstThursday) / (MILLISECONDS_IN_DAY * 7);
|
|
return 1 + Math.floor(weekDiff)
|
|
}
|
|
|
|
function getDayIdentifier (date) {
|
|
return date.getFullYear() * 10000 + date.getMonth() * 100 + date.getDate()
|
|
}
|
|
|
|
function getDateIdentifier (date, onlyDate /* = false */) {
|
|
const d = new Date(date);
|
|
return onlyDate === true ? getDayIdentifier(d) : d.getTime()
|
|
}
|
|
|
|
function isBetweenDates (date, from, to, opts = {}) {
|
|
const
|
|
d1 = getDateIdentifier(from, opts.onlyDate),
|
|
d2 = getDateIdentifier(to, opts.onlyDate),
|
|
cur = getDateIdentifier(date, opts.onlyDate);
|
|
|
|
return (cur > d1 || (opts.inclusiveFrom === true && cur === d1))
|
|
&& (cur < d2 || (opts.inclusiveTo === true && cur === d2))
|
|
}
|
|
|
|
function addToDate (date, mod) {
|
|
return getChange(date, mod, 1)
|
|
}
|
|
function subtractFromDate (date, mod) {
|
|
return getChange(date, mod, -1)
|
|
}
|
|
|
|
function startOfDate (date, unit, utc) {
|
|
const
|
|
t = new Date(date),
|
|
prefix = `set${ utc === true ? 'UTC' : '' }`;
|
|
|
|
switch (unit) {
|
|
case 'year':
|
|
case 'years':
|
|
t[ `${ prefix }Month` ](0);
|
|
case 'month':
|
|
case 'months':
|
|
t[ `${ prefix }Date` ](1);
|
|
case 'day':
|
|
case 'days':
|
|
case 'date':
|
|
t[ `${ prefix }Hours` ](0);
|
|
case 'hour':
|
|
case 'hours':
|
|
t[ `${ prefix }Minutes` ](0);
|
|
case 'minute':
|
|
case 'minutes':
|
|
t[ `${ prefix }Seconds` ](0);
|
|
case 'second':
|
|
case 'seconds':
|
|
t[ `${ prefix }Milliseconds` ](0);
|
|
}
|
|
return t
|
|
}
|
|
|
|
function endOfDate (date, unit, utc) {
|
|
const
|
|
t = new Date(date),
|
|
prefix = `set${ utc === true ? 'UTC' : '' }`;
|
|
|
|
switch (unit) {
|
|
case 'year':
|
|
case 'years':
|
|
t[ `${ prefix }Month` ](11);
|
|
case 'month':
|
|
case 'months':
|
|
t[ `${ prefix }Date` ](daysInMonth(t));
|
|
case 'day':
|
|
case 'days':
|
|
case 'date':
|
|
t[ `${ prefix }Hours` ](23);
|
|
case 'hour':
|
|
case 'hours':
|
|
t[ `${ prefix }Minutes` ](59);
|
|
case 'minute':
|
|
case 'minutes':
|
|
t[ `${ prefix }Seconds` ](59);
|
|
case 'second':
|
|
case 'seconds':
|
|
t[ `${ prefix }Milliseconds` ](999);
|
|
}
|
|
return t
|
|
}
|
|
|
|
function getMaxDate (date /* , ...args */) {
|
|
let t = new Date(date);
|
|
Array.prototype.slice.call(arguments, 1).forEach(d => {
|
|
t = Math.max(t, new Date(d));
|
|
});
|
|
return t
|
|
}
|
|
|
|
function getMinDate (date /*, ...args */) {
|
|
let t = new Date(date);
|
|
Array.prototype.slice.call(arguments, 1).forEach(d => {
|
|
t = Math.min(t, new Date(d));
|
|
});
|
|
return t
|
|
}
|
|
|
|
function getDiff (t, sub, interval) {
|
|
return (
|
|
(t.getTime() - t.getTimezoneOffset() * MILLISECONDS_IN_MINUTE)
|
|
- (sub.getTime() - sub.getTimezoneOffset() * MILLISECONDS_IN_MINUTE)
|
|
) / interval
|
|
}
|
|
|
|
function getDateDiff (date, subtract, unit = 'days') {
|
|
const
|
|
t = new Date(date),
|
|
sub = new Date(subtract);
|
|
|
|
switch (unit) {
|
|
case 'years':
|
|
case 'year':
|
|
return (t.getFullYear() - sub.getFullYear())
|
|
|
|
case 'months':
|
|
case 'month':
|
|
return (t.getFullYear() - sub.getFullYear()) * 12 + t.getMonth() - sub.getMonth()
|
|
|
|
case 'days':
|
|
case 'day':
|
|
case 'date':
|
|
return getDiff(startOfDate(t, 'day'), startOfDate(sub, 'day'), MILLISECONDS_IN_DAY)
|
|
|
|
case 'hours':
|
|
case 'hour':
|
|
return getDiff(startOfDate(t, 'hour'), startOfDate(sub, 'hour'), MILLISECONDS_IN_HOUR)
|
|
|
|
case 'minutes':
|
|
case 'minute':
|
|
return getDiff(startOfDate(t, 'minute'), startOfDate(sub, 'minute'), MILLISECONDS_IN_MINUTE)
|
|
|
|
case 'seconds':
|
|
case 'second':
|
|
return getDiff(startOfDate(t, 'second'), startOfDate(sub, 'second'), 1000)
|
|
}
|
|
}
|
|
|
|
function getDayOfYear (date) {
|
|
return getDateDiff(date, startOfDate(date, 'year'), 'days') + 1
|
|
}
|
|
|
|
function inferDateFormat (date) {
|
|
return isDate(date) === true
|
|
? 'date'
|
|
: (typeof date === 'number' ? 'number' : 'string')
|
|
}
|
|
|
|
function getDateBetween (date, min, max) {
|
|
const t = new Date(date);
|
|
|
|
if (min) {
|
|
const low = new Date(min);
|
|
if (t < low) {
|
|
return low
|
|
}
|
|
}
|
|
|
|
if (max) {
|
|
const high = new Date(max);
|
|
if (t > high) {
|
|
return high
|
|
}
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
function isSameDate (date, date2, unit) {
|
|
const
|
|
t = new Date(date),
|
|
d = new Date(date2);
|
|
|
|
if (unit === void 0) {
|
|
return t.getTime() === d.getTime()
|
|
}
|
|
|
|
switch (unit) {
|
|
case 'second':
|
|
case 'seconds':
|
|
if (t.getSeconds() !== d.getSeconds()) {
|
|
return false
|
|
}
|
|
case 'minute': // intentional fall-through
|
|
case 'minutes':
|
|
if (t.getMinutes() !== d.getMinutes()) {
|
|
return false
|
|
}
|
|
case 'hour': // intentional fall-through
|
|
case 'hours':
|
|
if (t.getHours() !== d.getHours()) {
|
|
return false
|
|
}
|
|
case 'day': // intentional fall-through
|
|
case 'days':
|
|
case 'date':
|
|
if (t.getDate() !== d.getDate()) {
|
|
return false
|
|
}
|
|
case 'month': // intentional fall-through
|
|
case 'months':
|
|
if (t.getMonth() !== d.getMonth()) {
|
|
return false
|
|
}
|
|
case 'year': // intentional fall-through
|
|
case 'years':
|
|
if (t.getFullYear() !== d.getFullYear()) {
|
|
return false
|
|
}
|
|
break
|
|
default:
|
|
throw new Error(`date isSameDate unknown unit ${ unit }`)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
function daysInMonth (date) {
|
|
return (new Date(date.getFullYear(), date.getMonth() + 1, 0)).getDate()
|
|
}
|
|
|
|
function getOrdinal (n) {
|
|
if (n >= 11 && n <= 13) {
|
|
return `${ n }th`
|
|
}
|
|
switch (n % 10) {
|
|
case 1: return `${ n }st`
|
|
case 2: return `${ n }nd`
|
|
case 3: return `${ n }rd`
|
|
}
|
|
return `${ n }th`
|
|
}
|
|
|
|
const formatter = {
|
|
// Year: 00, 01, ..., 99
|
|
YY (date, dateLocale, forcedYear) {
|
|
// workaround for < 1900 with new Date()
|
|
const y = this.YYYY(date, dateLocale, forcedYear) % 100;
|
|
return y >= 0
|
|
? pad(y)
|
|
: '-' + pad(Math.abs(y))
|
|
},
|
|
|
|
// Year: 1900, 1901, ..., 2099
|
|
YYYY (date, _dateLocale, forcedYear) {
|
|
// workaround for < 1900 with new Date()
|
|
return forcedYear !== void 0 && forcedYear !== null
|
|
? forcedYear
|
|
: date.getFullYear()
|
|
},
|
|
|
|
// Month: 1, 2, ..., 12
|
|
M (date) {
|
|
return date.getMonth() + 1
|
|
},
|
|
|
|
// Month: 01, 02, ..., 12
|
|
MM (date) {
|
|
return pad(date.getMonth() + 1)
|
|
},
|
|
|
|
// Month Short Name: Jan, Feb, ...
|
|
MMM (date, dateLocale) {
|
|
return dateLocale.monthsShort[ date.getMonth() ]
|
|
},
|
|
|
|
// Month Name: January, February, ...
|
|
MMMM (date, dateLocale) {
|
|
return dateLocale.months[ date.getMonth() ]
|
|
},
|
|
|
|
// Quarter: 1, 2, 3, 4
|
|
Q (date) {
|
|
return Math.ceil((date.getMonth() + 1) / 3)
|
|
},
|
|
|
|
// Quarter: 1st, 2nd, 3rd, 4th
|
|
Qo (date) {
|
|
return getOrdinal(this.Q(date))
|
|
},
|
|
|
|
// Day of month: 1, 2, ..., 31
|
|
D (date) {
|
|
return date.getDate()
|
|
},
|
|
|
|
// Day of month: 1st, 2nd, ..., 31st
|
|
Do (date) {
|
|
return getOrdinal(date.getDate())
|
|
},
|
|
|
|
// Day of month: 01, 02, ..., 31
|
|
DD (date) {
|
|
return pad(date.getDate())
|
|
},
|
|
|
|
// Day of year: 1, 2, ..., 366
|
|
DDD (date) {
|
|
return getDayOfYear(date)
|
|
},
|
|
|
|
// Day of year: 001, 002, ..., 366
|
|
DDDD (date) {
|
|
return pad(getDayOfYear(date), 3)
|
|
},
|
|
|
|
// Day of week: 0, 1, ..., 6
|
|
d (date) {
|
|
return date.getDay()
|
|
},
|
|
|
|
// Day of week: Su, Mo, ...
|
|
dd (date, dateLocale) {
|
|
return this.dddd(date, dateLocale).slice(0, 2)
|
|
},
|
|
|
|
// Day of week: Sun, Mon, ...
|
|
ddd (date, dateLocale) {
|
|
return dateLocale.daysShort[ date.getDay() ]
|
|
},
|
|
|
|
// Day of week: Sunday, Monday, ...
|
|
dddd (date, dateLocale) {
|
|
return dateLocale.days[ date.getDay() ]
|
|
},
|
|
|
|
// Day of ISO week: 1, 2, ..., 7
|
|
E (date) {
|
|
return date.getDay() || 7
|
|
},
|
|
|
|
// Week of Year: 1 2 ... 52 53
|
|
w (date) {
|
|
return getWeekOfYear(date)
|
|
},
|
|
|
|
// Week of Year: 01 02 ... 52 53
|
|
ww (date) {
|
|
return pad(getWeekOfYear(date))
|
|
},
|
|
|
|
// Hour: 0, 1, ... 23
|
|
H (date) {
|
|
return date.getHours()
|
|
},
|
|
|
|
// Hour: 00, 01, ..., 23
|
|
HH (date) {
|
|
return pad(date.getHours())
|
|
},
|
|
|
|
// Hour: 1, 2, ..., 12
|
|
h (date) {
|
|
const hours = date.getHours();
|
|
return hours === 0
|
|
? 12
|
|
: (hours > 12 ? hours % 12 : hours)
|
|
},
|
|
|
|
// Hour: 01, 02, ..., 12
|
|
hh (date) {
|
|
return pad(this.h(date))
|
|
},
|
|
|
|
// Minute: 0, 1, ..., 59
|
|
m (date) {
|
|
return date.getMinutes()
|
|
},
|
|
|
|
// Minute: 00, 01, ..., 59
|
|
mm (date) {
|
|
return pad(date.getMinutes())
|
|
},
|
|
|
|
// Second: 0, 1, ..., 59
|
|
s (date) {
|
|
return date.getSeconds()
|
|
},
|
|
|
|
// Second: 00, 01, ..., 59
|
|
ss (date) {
|
|
return pad(date.getSeconds())
|
|
},
|
|
|
|
// 1/10 of second: 0, 1, ..., 9
|
|
S (date) {
|
|
return Math.floor(date.getMilliseconds() / 100)
|
|
},
|
|
|
|
// 1/100 of second: 00, 01, ..., 99
|
|
SS (date) {
|
|
return pad(Math.floor(date.getMilliseconds() / 10))
|
|
},
|
|
|
|
// Millisecond: 000, 001, ..., 999
|
|
SSS (date) {
|
|
return pad(date.getMilliseconds(), 3)
|
|
},
|
|
|
|
// Meridiem: AM, PM
|
|
A (date) {
|
|
return this.H(date) < 12 ? 'AM' : 'PM'
|
|
},
|
|
|
|
// Meridiem: am, pm
|
|
a (date) {
|
|
return this.H(date) < 12 ? 'am' : 'pm'
|
|
},
|
|
|
|
// Meridiem: a.m., p.m.
|
|
aa (date) {
|
|
return this.H(date) < 12 ? 'a.m.' : 'p.m.'
|
|
},
|
|
|
|
// Timezone: -01:00, +00:00, ... +12:00
|
|
Z (date, _dateLocale, _forcedYear, forcedTimezoneOffset) {
|
|
const tzOffset = forcedTimezoneOffset === void 0 || forcedTimezoneOffset === null
|
|
? date.getTimezoneOffset()
|
|
: forcedTimezoneOffset;
|
|
|
|
return formatTimezone(tzOffset, ':')
|
|
},
|
|
|
|
// Timezone: -0100, +0000, ... +1200
|
|
ZZ (date, _dateLocale, _forcedYear, forcedTimezoneOffset) {
|
|
const tzOffset = forcedTimezoneOffset === void 0 || forcedTimezoneOffset === null
|
|
? date.getTimezoneOffset()
|
|
: forcedTimezoneOffset;
|
|
|
|
return formatTimezone(tzOffset)
|
|
},
|
|
|
|
// Seconds timestamp: 512969520
|
|
X (date) {
|
|
return Math.floor(date.getTime() / 1000)
|
|
},
|
|
|
|
// Milliseconds timestamp: 512969520900
|
|
x (date) {
|
|
return date.getTime()
|
|
}
|
|
};
|
|
|
|
function formatDate (val, mask, dateLocale, __forcedYear, __forcedTimezoneOffset) {
|
|
if (
|
|
(val !== 0 && !val)
|
|
|| val === Infinity
|
|
|| val === -Infinity
|
|
) {
|
|
return
|
|
}
|
|
|
|
const date = new Date(val);
|
|
|
|
if (isNaN(date)) {
|
|
return
|
|
}
|
|
|
|
if (mask === void 0) {
|
|
mask = defaultMask;
|
|
}
|
|
|
|
const locale = getDateLocale(dateLocale, Plugin$8.props);
|
|
|
|
return mask.replace(
|
|
token,
|
|
(match, text) => (
|
|
match in formatter
|
|
? formatter[ match ](date, locale, __forcedYear, __forcedTimezoneOffset)
|
|
: (text === void 0 ? match : text.split('\\]').join(']'))
|
|
)
|
|
)
|
|
}
|
|
|
|
function clone (date) {
|
|
return isDate(date) === true
|
|
? new Date(date.getTime())
|
|
: date
|
|
}
|
|
|
|
var date = {
|
|
isValid,
|
|
extractDate,
|
|
buildDate,
|
|
getDayOfWeek,
|
|
getWeekOfYear,
|
|
isBetweenDates,
|
|
addToDate,
|
|
subtractFromDate,
|
|
adjustDate,
|
|
startOfDate,
|
|
endOfDate,
|
|
getMaxDate,
|
|
getMinDate,
|
|
getDateDiff,
|
|
getDayOfYear,
|
|
inferDateFormat,
|
|
getDateBetween,
|
|
isSameDate,
|
|
daysInMonth,
|
|
formatDate,
|
|
clone
|
|
};
|
|
|
|
const yearsInterval = 20;
|
|
const views = [ 'Calendar', 'Years', 'Months' ];
|
|
const viewIsValid = v => views.includes(v);
|
|
const yearMonthValidator = v => /^-?[\d]+\/[0-1]\d$/.test(v);
|
|
const lineStr = ' \u2014 ';
|
|
|
|
function getMonthHash (date) {
|
|
return date.year + '/' + pad(date.month)
|
|
}
|
|
|
|
var QDate = createComponent({
|
|
name: 'QDate',
|
|
|
|
props: {
|
|
...useDatetimeProps,
|
|
...useFormProps,
|
|
...useDarkProps,
|
|
|
|
multiple: Boolean,
|
|
range: Boolean,
|
|
|
|
title: String,
|
|
subtitle: String,
|
|
|
|
mask: {
|
|
// this mask is forced
|
|
// when using persian calendar
|
|
default: 'YYYY/MM/DD'
|
|
},
|
|
|
|
defaultYearMonth: {
|
|
type: String,
|
|
validator: yearMonthValidator
|
|
},
|
|
|
|
yearsInMonthView: Boolean,
|
|
|
|
events: [ Array, Function ],
|
|
eventColor: [ String, Function ],
|
|
|
|
emitImmediately: Boolean,
|
|
|
|
options: [ Array, Function ],
|
|
|
|
navigationMinYearMonth: {
|
|
type: String,
|
|
validator: yearMonthValidator
|
|
},
|
|
|
|
navigationMaxYearMonth: {
|
|
type: String,
|
|
validator: yearMonthValidator
|
|
},
|
|
|
|
noUnset: Boolean,
|
|
|
|
firstDayOfWeek: [ String, Number ],
|
|
todayBtn: Boolean,
|
|
minimal: Boolean,
|
|
defaultView: {
|
|
type: String,
|
|
default: 'Calendar',
|
|
validator: viewIsValid
|
|
}
|
|
},
|
|
|
|
emits: [
|
|
...useDatetimeEmits,
|
|
'rangeStart', 'rangeEnd', 'navigation'
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { getCache } = useCache();
|
|
const { tabindex, headerClass, getLocale, getCurrentDate } = useDatetime(props, $q);
|
|
|
|
let lastEmitValue;
|
|
|
|
const formAttrs = useFormAttrs(props);
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const blurTargetRef = vue.ref(null);
|
|
const innerMask = vue.ref(getMask());
|
|
const innerLocale = vue.ref(getLocale());
|
|
|
|
const mask = vue.computed(() => getMask());
|
|
const locale = vue.computed(() => getLocale());
|
|
|
|
const today = vue.computed(() => getCurrentDate());
|
|
|
|
// model of current calendar view:
|
|
const viewModel = vue.ref(getViewModel(innerMask.value, innerLocale.value));
|
|
|
|
const view = vue.ref(props.defaultView);
|
|
|
|
const direction = $q.lang.rtl === true ? 'right' : 'left';
|
|
const monthDirection = vue.ref(direction.value);
|
|
const yearDirection = vue.ref(direction.value);
|
|
|
|
const year = viewModel.value.year;
|
|
const startYear = vue.ref(year - (year % yearsInterval) - (year < 0 ? yearsInterval : 0));
|
|
const editRange = vue.ref(null);
|
|
|
|
const classes = vue.computed(() => {
|
|
const type = props.landscape === true ? 'landscape' : 'portrait';
|
|
return `q-date q-date--${ type } q-date--${ type }-${ props.minimal === true ? 'minimal' : 'standard' }`
|
|
+ (isDark.value === true ? ' q-date--dark q-dark' : '')
|
|
+ (props.bordered === true ? ' q-date--bordered' : '')
|
|
+ (props.square === true ? ' q-date--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-date--flat no-shadow' : '')
|
|
+ (props.disable === true ? ' disabled' : (props.readonly === true ? ' q-date--readonly' : ''))
|
|
});
|
|
|
|
const computedColor = vue.computed(() => {
|
|
return props.color || 'primary'
|
|
});
|
|
|
|
const computedTextColor = vue.computed(() => {
|
|
return props.textColor || 'white'
|
|
});
|
|
|
|
const isImmediate = vue.computed(() =>
|
|
props.emitImmediately === true
|
|
&& props.multiple !== true
|
|
&& props.range !== true
|
|
);
|
|
|
|
const normalizedModel = vue.computed(() => (
|
|
Array.isArray(props.modelValue) === true
|
|
? props.modelValue
|
|
: (props.modelValue !== null && props.modelValue !== void 0 ? [ props.modelValue ] : [])
|
|
));
|
|
|
|
const daysModel = vue.computed(() =>
|
|
normalizedModel.value
|
|
.filter(date => typeof date === 'string')
|
|
.map(date => decodeString(date, innerMask.value, innerLocale.value))
|
|
.filter(date =>
|
|
date.dateHash !== null
|
|
&& date.day !== null
|
|
&& date.month !== null
|
|
&& date.year !== null
|
|
)
|
|
);
|
|
|
|
const rangeModel = vue.computed(() => {
|
|
const fn = date => decodeString(date, innerMask.value, innerLocale.value);
|
|
return normalizedModel.value
|
|
.filter(date => isObject(date) === true && date.from !== void 0 && date.to !== void 0)
|
|
.map(range => ({ from: fn(range.from), to: fn(range.to) }))
|
|
.filter(range => range.from.dateHash !== null && range.to.dateHash !== null && range.from.dateHash < range.to.dateHash)
|
|
});
|
|
|
|
const getNativeDateFn = vue.computed(() => (
|
|
props.calendar !== 'persian'
|
|
? model => new Date(model.year, model.month - 1, model.day)
|
|
: model => {
|
|
const gDate = toGregorian(model.year, model.month, model.day);
|
|
return new Date(gDate.gy, gDate.gm - 1, gDate.gd)
|
|
}
|
|
));
|
|
|
|
const encodeObjectFn = vue.computed(() => (
|
|
props.calendar === 'persian'
|
|
? getDayHash
|
|
: (date, mask, locale) => formatDate(
|
|
new Date(
|
|
date.year,
|
|
date.month - 1,
|
|
date.day,
|
|
date.hour,
|
|
date.minute,
|
|
date.second,
|
|
date.millisecond
|
|
),
|
|
mask === void 0 ? innerMask.value : mask,
|
|
locale === void 0 ? innerLocale.value : locale,
|
|
date.year,
|
|
date.timezoneOffset
|
|
)
|
|
));
|
|
|
|
const daysInModel = vue.computed(() =>
|
|
daysModel.value.length + rangeModel.value.reduce(
|
|
(acc, range) => acc + 1 + getDateDiff(
|
|
getNativeDateFn.value(range.to),
|
|
getNativeDateFn.value(range.from)
|
|
),
|
|
0
|
|
)
|
|
);
|
|
|
|
const headerTitle = vue.computed(() => {
|
|
if (props.title !== void 0 && props.title !== null && props.title.length !== 0) {
|
|
return props.title
|
|
}
|
|
|
|
if (editRange.value !== null) {
|
|
const model = editRange.value.init;
|
|
const date = getNativeDateFn.value(model);
|
|
|
|
return innerLocale.value.daysShort[ date.getDay() ] + ', '
|
|
+ innerLocale.value.monthsShort[ model.month - 1 ] + ' '
|
|
+ model.day + lineStr + '?'
|
|
}
|
|
|
|
if (daysInModel.value === 0) {
|
|
return lineStr
|
|
}
|
|
|
|
if (daysInModel.value > 1) {
|
|
return `${ daysInModel.value } ${ innerLocale.value.pluralDay }`
|
|
}
|
|
|
|
const model = daysModel.value[ 0 ];
|
|
const date = getNativeDateFn.value(model);
|
|
|
|
if (isNaN(date.valueOf()) === true) {
|
|
return lineStr
|
|
}
|
|
|
|
if (innerLocale.value.headerTitle !== void 0) {
|
|
return innerLocale.value.headerTitle(date, model)
|
|
}
|
|
|
|
return innerLocale.value.daysShort[ date.getDay() ] + ', '
|
|
+ innerLocale.value.monthsShort[ model.month - 1 ] + ' '
|
|
+ model.day
|
|
});
|
|
|
|
const minSelectedModel = vue.computed(() => {
|
|
const model = daysModel.value.concat(rangeModel.value.map(range => range.from))
|
|
.sort((a, b) => a.year - b.year || a.month - b.month);
|
|
|
|
return model[ 0 ]
|
|
});
|
|
|
|
const maxSelectedModel = vue.computed(() => {
|
|
const model = daysModel.value.concat(rangeModel.value.map(range => range.to))
|
|
.sort((a, b) => b.year - a.year || b.month - a.month);
|
|
|
|
return model[ 0 ]
|
|
});
|
|
|
|
const headerSubtitle = vue.computed(() => {
|
|
if (props.subtitle !== void 0 && props.subtitle !== null && props.subtitle.length !== 0) {
|
|
return props.subtitle
|
|
}
|
|
|
|
if (daysInModel.value === 0) {
|
|
return lineStr
|
|
}
|
|
|
|
if (daysInModel.value > 1) {
|
|
const from = minSelectedModel.value;
|
|
const to = maxSelectedModel.value;
|
|
const month = innerLocale.value.monthsShort;
|
|
|
|
return month[ from.month - 1 ] + (
|
|
from.year !== to.year
|
|
? ' ' + from.year + lineStr + month[ to.month - 1 ] + ' '
|
|
: (
|
|
from.month !== to.month
|
|
? lineStr + month[ to.month - 1 ]
|
|
: ''
|
|
)
|
|
) + ' ' + to.year
|
|
}
|
|
|
|
return daysModel.value[ 0 ].year
|
|
});
|
|
|
|
const dateArrow = vue.computed(() => {
|
|
const val = [ $q.iconSet.datetime.arrowLeft, $q.iconSet.datetime.arrowRight ];
|
|
return $q.lang.rtl === true ? val.reverse() : val
|
|
});
|
|
|
|
const computedFirstDayOfWeek = vue.computed(() => (
|
|
props.firstDayOfWeek !== void 0
|
|
? Number(props.firstDayOfWeek)
|
|
: innerLocale.value.firstDayOfWeek
|
|
));
|
|
|
|
const daysOfWeek = vue.computed(() => {
|
|
const
|
|
days = innerLocale.value.daysShort,
|
|
first = computedFirstDayOfWeek.value;
|
|
|
|
return first > 0
|
|
? days.slice(first, 7).concat(days.slice(0, first))
|
|
: days
|
|
});
|
|
|
|
const daysInMonth = vue.computed(() => {
|
|
const date = viewModel.value;
|
|
return props.calendar !== 'persian'
|
|
? (new Date(date.year, date.month, 0)).getDate()
|
|
: jalaaliMonthLength(date.year, date.month)
|
|
});
|
|
|
|
const evtColor = vue.computed(() => (
|
|
typeof props.eventColor === 'function'
|
|
? props.eventColor
|
|
: () => props.eventColor
|
|
));
|
|
|
|
const minNav = vue.computed(() => {
|
|
if (props.navigationMinYearMonth === void 0) {
|
|
return null
|
|
}
|
|
|
|
const data = props.navigationMinYearMonth.split('/');
|
|
return { year: parseInt(data[ 0 ], 10), month: parseInt(data[ 1 ], 10) }
|
|
});
|
|
|
|
const maxNav = vue.computed(() => {
|
|
if (props.navigationMaxYearMonth === void 0) {
|
|
return null
|
|
}
|
|
|
|
const data = props.navigationMaxYearMonth.split('/');
|
|
return { year: parseInt(data[ 0 ], 10), month: parseInt(data[ 1 ], 10) }
|
|
});
|
|
|
|
const navBoundaries = vue.computed(() => {
|
|
const data = {
|
|
month: { prev: true, next: true },
|
|
year: { prev: true, next: true }
|
|
};
|
|
|
|
if (minNav.value !== null && minNav.value.year >= viewModel.value.year) {
|
|
data.year.prev = false;
|
|
if (minNav.value.year === viewModel.value.year && minNav.value.month >= viewModel.value.month) {
|
|
data.month.prev = false;
|
|
}
|
|
}
|
|
|
|
if (maxNav.value !== null && maxNav.value.year <= viewModel.value.year) {
|
|
data.year.next = false;
|
|
if (maxNav.value.year === viewModel.value.year && maxNav.value.month <= viewModel.value.month) {
|
|
data.month.next = false;
|
|
}
|
|
}
|
|
|
|
return data
|
|
});
|
|
|
|
const daysMap = vue.computed(() => {
|
|
const map = {};
|
|
|
|
daysModel.value.forEach(entry => {
|
|
const hash = getMonthHash(entry);
|
|
|
|
if (map[ hash ] === void 0) {
|
|
map[ hash ] = [];
|
|
}
|
|
|
|
map[ hash ].push(entry.day);
|
|
});
|
|
|
|
return map
|
|
});
|
|
|
|
const rangeMap = vue.computed(() => {
|
|
const map = {};
|
|
|
|
rangeModel.value.forEach(entry => {
|
|
const hashFrom = getMonthHash(entry.from);
|
|
const hashTo = getMonthHash(entry.to);
|
|
|
|
if (map[ hashFrom ] === void 0) {
|
|
map[ hashFrom ] = [];
|
|
}
|
|
|
|
map[ hashFrom ].push({
|
|
from: entry.from.day,
|
|
to: hashFrom === hashTo ? entry.to.day : void 0,
|
|
range: entry
|
|
});
|
|
|
|
if (hashFrom < hashTo) {
|
|
let hash;
|
|
const { year, month } = entry.from;
|
|
const cur = month < 12
|
|
? { year, month: month + 1 }
|
|
: { year: year + 1, month: 1 };
|
|
|
|
while ((hash = getMonthHash(cur)) <= hashTo) {
|
|
if (map[ hash ] === void 0) {
|
|
map[ hash ] = [];
|
|
}
|
|
|
|
map[ hash ].push({
|
|
from: void 0,
|
|
to: hash === hashTo ? entry.to.day : void 0,
|
|
range: entry
|
|
});
|
|
|
|
cur.month++;
|
|
if (cur.month > 12) {
|
|
cur.year++;
|
|
cur.month = 1;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return map
|
|
});
|
|
|
|
const rangeView = vue.computed(() => {
|
|
if (editRange.value === null) {
|
|
return
|
|
}
|
|
|
|
const { init, initHash, final, finalHash } = editRange.value;
|
|
|
|
const [ from, to ] = initHash <= finalHash
|
|
? [ init, final ]
|
|
: [ final, init ];
|
|
|
|
const fromHash = getMonthHash(from);
|
|
const toHash = getMonthHash(to);
|
|
|
|
if (fromHash !== viewMonthHash.value && toHash !== viewMonthHash.value) {
|
|
return
|
|
}
|
|
|
|
const view = {};
|
|
|
|
if (fromHash === viewMonthHash.value) {
|
|
view.from = from.day;
|
|
view.includeFrom = true;
|
|
}
|
|
else {
|
|
view.from = 1;
|
|
}
|
|
|
|
if (toHash === viewMonthHash.value) {
|
|
view.to = to.day;
|
|
view.includeTo = true;
|
|
}
|
|
else {
|
|
view.to = daysInMonth.value;
|
|
}
|
|
|
|
return view
|
|
});
|
|
|
|
const viewMonthHash = vue.computed(() => getMonthHash(viewModel.value));
|
|
|
|
const selectionDaysMap = vue.computed(() => {
|
|
const map = {};
|
|
|
|
if (props.options === void 0) {
|
|
for (let i = 1; i <= daysInMonth.value; i++) {
|
|
map[ i ] = true;
|
|
}
|
|
|
|
return map
|
|
}
|
|
|
|
const fn = typeof props.options === 'function'
|
|
? props.options
|
|
: date => props.options.includes(date);
|
|
|
|
for (let i = 1; i <= daysInMonth.value; i++) {
|
|
const dayHash = viewMonthHash.value + '/' + pad(i);
|
|
map[ i ] = fn(dayHash);
|
|
}
|
|
|
|
return map
|
|
});
|
|
|
|
const eventDaysMap = vue.computed(() => {
|
|
const map = {};
|
|
|
|
if (props.events === void 0) {
|
|
for (let i = 1; i <= daysInMonth.value; i++) {
|
|
map[ i ] = false;
|
|
}
|
|
}
|
|
else {
|
|
const fn = typeof props.events === 'function'
|
|
? props.events
|
|
: date => props.events.includes(date);
|
|
|
|
for (let i = 1; i <= daysInMonth.value; i++) {
|
|
const dayHash = viewMonthHash.value + '/' + pad(i);
|
|
map[ i ] = fn(dayHash) === true && evtColor.value(dayHash);
|
|
}
|
|
}
|
|
|
|
return map
|
|
});
|
|
|
|
const viewDays = vue.computed(() => {
|
|
let date, endDay;
|
|
const { year, month } = viewModel.value;
|
|
|
|
if (props.calendar !== 'persian') {
|
|
date = new Date(year, month - 1, 1);
|
|
endDay = (new Date(year, month - 1, 0)).getDate();
|
|
}
|
|
else {
|
|
const gDate = toGregorian(year, month, 1);
|
|
date = new Date(gDate.gy, gDate.gm - 1, gDate.gd);
|
|
let prevJM = month - 1;
|
|
let prevJY = year;
|
|
if (prevJM === 0) {
|
|
prevJM = 12;
|
|
prevJY--;
|
|
}
|
|
endDay = jalaaliMonthLength(prevJY, prevJM);
|
|
}
|
|
|
|
return {
|
|
days: date.getDay() - computedFirstDayOfWeek.value - 1,
|
|
endDay
|
|
}
|
|
});
|
|
|
|
const days = vue.computed(() => {
|
|
const res = [];
|
|
const { days, endDay } = viewDays.value;
|
|
|
|
const len = days < 0 ? days + 7 : days;
|
|
if (len < 6) {
|
|
for (let i = endDay - len; i <= endDay; i++) {
|
|
res.push({ i, fill: true });
|
|
}
|
|
}
|
|
|
|
const index = res.length;
|
|
|
|
for (let i = 1; i <= daysInMonth.value; i++) {
|
|
const day = { i, event: eventDaysMap.value[ i ], classes: [] };
|
|
|
|
if (selectionDaysMap.value[ i ] === true) {
|
|
day.in = true;
|
|
day.flat = true;
|
|
}
|
|
|
|
res.push(day);
|
|
}
|
|
|
|
// if current view has days in model
|
|
if (daysMap.value[ viewMonthHash.value ] !== void 0) {
|
|
daysMap.value[ viewMonthHash.value ].forEach(day => {
|
|
const i = index + day - 1;
|
|
Object.assign(res[ i ], {
|
|
selected: true,
|
|
unelevated: true,
|
|
flat: false,
|
|
color: computedColor.value,
|
|
textColor: computedTextColor.value
|
|
});
|
|
});
|
|
}
|
|
|
|
// if current view has ranges in model
|
|
if (rangeMap.value[ viewMonthHash.value ] !== void 0) {
|
|
rangeMap.value[ viewMonthHash.value ].forEach(entry => {
|
|
if (entry.from !== void 0) {
|
|
const from = index + entry.from - 1;
|
|
const to = index + (entry.to || daysInMonth.value) - 1;
|
|
|
|
for (let day = from; day <= to; day++) {
|
|
Object.assign(res[ day ], {
|
|
range: entry.range,
|
|
unelevated: true,
|
|
color: computedColor.value,
|
|
textColor: computedTextColor.value
|
|
});
|
|
}
|
|
|
|
Object.assign(res[ from ], {
|
|
rangeFrom: true,
|
|
flat: false
|
|
});
|
|
|
|
entry.to !== void 0 && Object.assign(res[ to ], {
|
|
rangeTo: true,
|
|
flat: false
|
|
});
|
|
}
|
|
else if (entry.to !== void 0) {
|
|
const to = index + entry.to - 1;
|
|
|
|
for (let day = index; day <= to; day++) {
|
|
Object.assign(res[ day ], {
|
|
range: entry.range,
|
|
unelevated: true,
|
|
color: computedColor.value,
|
|
textColor: computedTextColor.value
|
|
});
|
|
}
|
|
|
|
Object.assign(res[ to ], {
|
|
flat: false,
|
|
rangeTo: true
|
|
});
|
|
}
|
|
else {
|
|
const to = index + daysInMonth.value - 1;
|
|
for (let day = index; day <= to; day++) {
|
|
Object.assign(res[ day ], {
|
|
range: entry.range,
|
|
unelevated: true,
|
|
color: computedColor.value,
|
|
textColor: computedTextColor.value
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (rangeView.value !== void 0) {
|
|
const from = index + rangeView.value.from - 1;
|
|
const to = index + rangeView.value.to - 1;
|
|
|
|
for (let day = from; day <= to; day++) {
|
|
res[ day ].color = computedColor.value;
|
|
res[ day ].editRange = true;
|
|
}
|
|
|
|
if (rangeView.value.includeFrom === true) {
|
|
res[ from ].editRangeFrom = true;
|
|
}
|
|
if (rangeView.value.includeTo === true) {
|
|
res[ to ].editRangeTo = true;
|
|
}
|
|
}
|
|
|
|
if (viewModel.value.year === today.value.year && viewModel.value.month === today.value.month) {
|
|
res[ index + today.value.day - 1 ].today = true;
|
|
}
|
|
|
|
const left = res.length % 7;
|
|
if (left > 0) {
|
|
const afterDays = 7 - left;
|
|
for (let i = 1; i <= afterDays; i++) {
|
|
res.push({ i, fill: true });
|
|
}
|
|
}
|
|
|
|
res.forEach(day => {
|
|
let cls = 'q-date__calendar-item ';
|
|
|
|
if (day.fill === true) {
|
|
cls += 'q-date__calendar-item--fill';
|
|
}
|
|
else {
|
|
cls += `q-date__calendar-item--${ day.in === true ? 'in' : 'out' }`;
|
|
|
|
if (day.range !== void 0) {
|
|
cls += ` q-date__range${ day.rangeTo === true ? '-to' : (day.rangeFrom === true ? '-from' : '') }`;
|
|
}
|
|
|
|
if (day.editRange === true) {
|
|
cls += ` q-date__edit-range${ day.editRangeFrom === true ? '-from' : '' }${ day.editRangeTo === true ? '-to' : '' }`;
|
|
}
|
|
|
|
if (day.range !== void 0 || day.editRange === true) {
|
|
cls += ` text-${ day.color }`;
|
|
}
|
|
}
|
|
|
|
day.classes = cls;
|
|
});
|
|
|
|
return res
|
|
});
|
|
|
|
const attributes = vue.computed(() => (
|
|
props.disable === true
|
|
? { 'aria-disabled': 'true' }
|
|
: (props.readonly === true ? { 'aria-readonly': 'true' } : {})
|
|
));
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
if (lastEmitValue === v) {
|
|
lastEmitValue = 0;
|
|
}
|
|
else {
|
|
const model = getViewModel(innerMask.value, innerLocale.value);
|
|
updateViewModel(model.year, model.month, model);
|
|
}
|
|
});
|
|
|
|
vue.watch(view, () => {
|
|
if (blurTargetRef.value !== null && proxy.$el.contains(document.activeElement) === true) {
|
|
blurTargetRef.value.focus();
|
|
}
|
|
});
|
|
|
|
vue.watch(() => viewModel.value.year + '|' + viewModel.value.month, () => {
|
|
emit('navigation', { year: viewModel.value.year, month: viewModel.value.month });
|
|
});
|
|
|
|
vue.watch(mask, val => {
|
|
updateValue(val, innerLocale.value, 'mask');
|
|
innerMask.value = val;
|
|
});
|
|
|
|
vue.watch(locale, val => {
|
|
updateValue(innerMask.value, val, 'locale');
|
|
innerLocale.value = val;
|
|
});
|
|
|
|
function setToday () {
|
|
const date = today.value;
|
|
const month = daysMap.value[ getMonthHash(date) ];
|
|
|
|
if (month === void 0 || month.includes(date.day) === false) {
|
|
addToModel(date);
|
|
}
|
|
|
|
setCalendarTo(date.year, date.month);
|
|
}
|
|
|
|
function setView (viewMode) {
|
|
if (viewIsValid(viewMode) === true) {
|
|
view.value = viewMode;
|
|
}
|
|
}
|
|
|
|
function offsetCalendar (type, descending) {
|
|
if ([ 'month', 'year' ].includes(type)) {
|
|
const fn = type === 'month' ? goToMonth : goToYear;
|
|
fn(descending === true ? -1 : 1);
|
|
}
|
|
}
|
|
|
|
function setCalendarTo (year, month) {
|
|
view.value = 'Calendar';
|
|
updateViewModel(year, month);
|
|
}
|
|
|
|
function setEditingRange (from, to) {
|
|
if (props.range === false || !from) {
|
|
editRange.value = null;
|
|
return
|
|
}
|
|
|
|
const init = Object.assign({ ...viewModel.value }, from);
|
|
const final = to !== void 0
|
|
? Object.assign({ ...viewModel.value }, to)
|
|
: init;
|
|
|
|
editRange.value = {
|
|
init,
|
|
initHash: getDayHash(init),
|
|
final,
|
|
finalHash: getDayHash(final)
|
|
};
|
|
|
|
setCalendarTo(init.year, init.month);
|
|
}
|
|
|
|
function getMask () {
|
|
return props.calendar === 'persian' ? 'YYYY/MM/DD' : props.mask
|
|
}
|
|
|
|
function decodeString (date, mask, locale) {
|
|
return __splitDate(
|
|
date,
|
|
mask,
|
|
locale,
|
|
props.calendar,
|
|
{
|
|
hour: 0,
|
|
minute: 0,
|
|
second: 0,
|
|
millisecond: 0
|
|
}
|
|
)
|
|
}
|
|
|
|
function getViewModel (mask, locale) {
|
|
const model = Array.isArray(props.modelValue) === true
|
|
? props.modelValue
|
|
: (props.modelValue ? [ props.modelValue ] : []);
|
|
|
|
if (model.length === 0) {
|
|
return getDefaultViewModel()
|
|
}
|
|
|
|
const target = model[ model.length - 1 ];
|
|
const decoded = decodeString(
|
|
target.from !== void 0 ? target.from : target,
|
|
mask,
|
|
locale
|
|
);
|
|
|
|
return decoded.dateHash === null
|
|
? getDefaultViewModel()
|
|
: decoded
|
|
}
|
|
|
|
function getDefaultViewModel () {
|
|
let year, month;
|
|
|
|
if (props.defaultYearMonth !== void 0) {
|
|
const d = props.defaultYearMonth.split('/');
|
|
year = parseInt(d[ 0 ], 10);
|
|
month = parseInt(d[ 1 ], 10);
|
|
}
|
|
else {
|
|
// may come from data() where computed
|
|
// props are not yet available
|
|
const d = today.value !== void 0
|
|
? today.value
|
|
: getCurrentDate();
|
|
|
|
year = d.year;
|
|
month = d.month;
|
|
}
|
|
|
|
return {
|
|
year,
|
|
month,
|
|
day: 1,
|
|
hour: 0,
|
|
minute: 0,
|
|
second: 0,
|
|
millisecond: 0,
|
|
dateHash: year + '/' + pad(month) + '/01'
|
|
}
|
|
}
|
|
|
|
function goToMonth (offset) {
|
|
let year = viewModel.value.year;
|
|
let month = Number(viewModel.value.month) + offset;
|
|
|
|
if (month === 13) {
|
|
month = 1;
|
|
year++;
|
|
}
|
|
else if (month === 0) {
|
|
month = 12;
|
|
year--;
|
|
}
|
|
|
|
updateViewModel(year, month);
|
|
isImmediate.value === true && emitImmediately('month');
|
|
}
|
|
|
|
function goToYear (offset) {
|
|
const year = Number(viewModel.value.year) + offset;
|
|
updateViewModel(year, viewModel.value.month);
|
|
isImmediate.value === true && emitImmediately('year');
|
|
}
|
|
|
|
function setYear (year) {
|
|
updateViewModel(year, viewModel.value.month);
|
|
view.value = props.defaultView === 'Years' ? 'Months' : 'Calendar';
|
|
isImmediate.value === true && emitImmediately('year');
|
|
}
|
|
|
|
function setMonth (month) {
|
|
updateViewModel(viewModel.value.year, month);
|
|
view.value = 'Calendar';
|
|
isImmediate.value === true && emitImmediately('month');
|
|
}
|
|
|
|
function toggleDate (date, monthHash) {
|
|
const month = daysMap.value[ monthHash ];
|
|
const fn = month !== void 0 && month.includes(date.day) === true
|
|
? removeFromModel
|
|
: addToModel;
|
|
|
|
fn(date);
|
|
}
|
|
|
|
function getShortDate (date) {
|
|
return { year: date.year, month: date.month, day: date.day }
|
|
}
|
|
|
|
function updateViewModel (year, month, time) {
|
|
if (minNav.value !== null && year <= minNav.value.year) {
|
|
year = minNav.value.year;
|
|
if (month < minNav.value.month) {
|
|
month = minNav.value.month;
|
|
}
|
|
}
|
|
|
|
if (maxNav.value !== null && year >= maxNav.value.year) {
|
|
year = maxNav.value.year;
|
|
if (month > maxNav.value.month) {
|
|
month = maxNav.value.month;
|
|
}
|
|
}
|
|
|
|
if (time !== void 0) {
|
|
const { hour, minute, second, millisecond, timezoneOffset, timeHash } = time;
|
|
Object.assign(viewModel.value, { hour, minute, second, millisecond, timezoneOffset, timeHash });
|
|
}
|
|
|
|
const newHash = year + '/' + pad(month) + '/01';
|
|
|
|
if (newHash !== viewModel.value.dateHash) {
|
|
monthDirection.value = (viewModel.value.dateHash < newHash) === ($q.lang.rtl !== true) ? 'left' : 'right';
|
|
if (year !== viewModel.value.year) {
|
|
yearDirection.value = monthDirection.value;
|
|
}
|
|
|
|
vue.nextTick(() => {
|
|
startYear.value = year - year % yearsInterval - (year < 0 ? yearsInterval : 0);
|
|
Object.assign(viewModel.value, {
|
|
year,
|
|
month,
|
|
day: 1,
|
|
dateHash: newHash
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function emitValue (val, action, date) {
|
|
const value = val !== null && val.length === 1 && props.multiple === false
|
|
? val[ 0 ]
|
|
: val;
|
|
|
|
lastEmitValue = value;
|
|
|
|
const { reason, details } = getEmitParams(action, date);
|
|
emit('update:modelValue', value, reason, details);
|
|
}
|
|
|
|
function emitImmediately (reason) {
|
|
const date = daysModel.value[ 0 ] !== void 0 && daysModel.value[ 0 ].dateHash !== null
|
|
? { ...daysModel.value[ 0 ] }
|
|
: { ...viewModel.value }; // inherit day, hours, minutes, milliseconds...
|
|
|
|
// nextTick required because of animation delay in viewModel
|
|
vue.nextTick(() => {
|
|
date.year = viewModel.value.year;
|
|
date.month = viewModel.value.month;
|
|
|
|
const maxDay = props.calendar !== 'persian'
|
|
? (new Date(date.year, date.month, 0)).getDate()
|
|
: jalaaliMonthLength(date.year, date.month);
|
|
|
|
date.day = Math.min(Math.max(1, date.day), maxDay);
|
|
|
|
const value = encodeEntry(date);
|
|
lastEmitValue = value;
|
|
|
|
const { details } = getEmitParams('', date);
|
|
emit('update:modelValue', value, reason, details);
|
|
});
|
|
}
|
|
|
|
function getEmitParams (action, date) {
|
|
return date.from !== void 0
|
|
? {
|
|
reason: `${ action }-range`,
|
|
details: {
|
|
...getShortDate(date.target),
|
|
from: getShortDate(date.from),
|
|
to: getShortDate(date.to)
|
|
}
|
|
}
|
|
: {
|
|
reason: `${ action }-day`,
|
|
details: getShortDate(date)
|
|
}
|
|
}
|
|
|
|
function encodeEntry (date, mask, locale) {
|
|
return date.from !== void 0
|
|
? { from: encodeObjectFn.value(date.from, mask, locale), to: encodeObjectFn.value(date.to, mask, locale) }
|
|
: encodeObjectFn.value(date, mask, locale)
|
|
}
|
|
|
|
function addToModel (date) {
|
|
let value;
|
|
|
|
if (props.multiple === true) {
|
|
if (date.from !== void 0) {
|
|
// we also need to filter out intersections
|
|
|
|
const fromHash = getDayHash(date.from);
|
|
const toHash = getDayHash(date.to);
|
|
|
|
const days = daysModel.value
|
|
.filter(day => day.dateHash < fromHash || day.dateHash > toHash);
|
|
|
|
const ranges = rangeModel.value
|
|
.filter(({ from, to }) => to.dateHash < fromHash || from.dateHash > toHash);
|
|
|
|
value = days.concat(ranges).concat(date).map(entry => encodeEntry(entry));
|
|
}
|
|
else {
|
|
const model = normalizedModel.value.slice();
|
|
model.push(encodeEntry(date));
|
|
value = model;
|
|
}
|
|
}
|
|
else {
|
|
value = encodeEntry(date);
|
|
}
|
|
|
|
emitValue(value, 'add', date);
|
|
}
|
|
|
|
function removeFromModel (date) {
|
|
if (props.noUnset === true) {
|
|
return
|
|
}
|
|
|
|
let model = null;
|
|
|
|
if (props.multiple === true && Array.isArray(props.modelValue) === true) {
|
|
const val = encodeEntry(date);
|
|
|
|
if (date.from !== void 0) {
|
|
model = props.modelValue.filter(
|
|
date => (
|
|
date.from !== void 0
|
|
? (date.from !== val.from && date.to !== val.to)
|
|
: true
|
|
)
|
|
);
|
|
}
|
|
else {
|
|
model = props.modelValue.filter(date => date !== val);
|
|
}
|
|
|
|
if (model.length === 0) {
|
|
model = null;
|
|
}
|
|
}
|
|
|
|
emitValue(model, 'remove', date);
|
|
}
|
|
|
|
function updateValue (mask, locale, reason) {
|
|
const model = daysModel.value
|
|
.concat(rangeModel.value)
|
|
.map(entry => encodeEntry(entry, mask, locale))
|
|
.filter(entry => {
|
|
return entry.from !== void 0
|
|
? entry.from.dateHash !== null && entry.to.dateHash !== null
|
|
: entry.dateHash !== null
|
|
});
|
|
|
|
emit('update:modelValue', (props.multiple === true ? model : model[ 0 ]) || null, reason);
|
|
}
|
|
|
|
function getHeader () {
|
|
if (props.minimal === true) { return }
|
|
|
|
return vue.h('div', {
|
|
class: 'q-date__header ' + headerClass.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'relative-position'
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--fade'
|
|
}, () => vue.h('div', {
|
|
key: 'h-yr-' + headerSubtitle.value,
|
|
class: 'q-date__header-subtitle q-date__header-link '
|
|
+ (view.value === 'Years' ? 'q-date__header-link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
...getCache('vY', {
|
|
onClick () { view.value = 'Years'; },
|
|
onKeyup (e) { e.keyCode === 13 && (view.value = 'Years'); }
|
|
})
|
|
}, [ headerSubtitle.value ]))
|
|
]),
|
|
|
|
vue.h('div', {
|
|
class: 'q-date__header-title relative-position flex no-wrap'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'relative-position col'
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--fade'
|
|
}, () => vue.h('div', {
|
|
key: 'h-sub' + headerTitle.value,
|
|
class: 'q-date__header-title-label q-date__header-link '
|
|
+ (view.value === 'Calendar' ? 'q-date__header-link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
...getCache('vC', {
|
|
onClick () { view.value = 'Calendar'; },
|
|
onKeyup (e) { e.keyCode === 13 && (view.value = 'Calendar'); }
|
|
})
|
|
}, [ headerTitle.value ]))
|
|
]),
|
|
|
|
props.todayBtn === true ? vue.h(QBtn, {
|
|
class: 'q-date__header-today self-start',
|
|
icon: $q.iconSet.datetime.today,
|
|
flat: true,
|
|
size: 'sm',
|
|
round: true,
|
|
tabindex: tabindex.value,
|
|
onClick: setToday
|
|
}) : null
|
|
])
|
|
])
|
|
}
|
|
|
|
function getNavigation ({ label, type, key, dir, goTo, boundaries, cls }) {
|
|
return [
|
|
vue.h('div', {
|
|
class: 'row items-center q-date__arrow'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
round: true,
|
|
dense: true,
|
|
size: 'sm',
|
|
flat: true,
|
|
icon: dateArrow.value[ 0 ],
|
|
tabindex: tabindex.value,
|
|
disable: boundaries.prev === false,
|
|
...getCache('go-#' + type, { onClick () { goTo(-1); } })
|
|
})
|
|
]),
|
|
|
|
vue.h('div', {
|
|
class: 'relative-position overflow-hidden flex flex-center' + cls
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--jump-' + dir
|
|
}, () => vue.h('div', { key }, [
|
|
vue.h(QBtn, {
|
|
flat: true,
|
|
dense: true,
|
|
noCaps: true,
|
|
label,
|
|
tabindex: tabindex.value,
|
|
...getCache('view#' + type, { onClick: () => { view.value = type; } })
|
|
})
|
|
]))
|
|
]),
|
|
|
|
vue.h('div', {
|
|
class: 'row items-center q-date__arrow'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
round: true,
|
|
dense: true,
|
|
size: 'sm',
|
|
flat: true,
|
|
icon: dateArrow.value[ 1 ],
|
|
tabindex: tabindex.value,
|
|
disable: boundaries.next === false,
|
|
...getCache('go+#' + type, { onClick () { goTo(1); } })
|
|
})
|
|
])
|
|
]
|
|
}
|
|
|
|
const renderViews = {
|
|
Calendar: () => ([
|
|
vue.h('div', {
|
|
key: 'calendar-view',
|
|
class: 'q-date__view q-date__calendar'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-date__navigation row items-center no-wrap'
|
|
}, getNavigation({
|
|
label: innerLocale.value.months[ viewModel.value.month - 1 ],
|
|
type: 'Months',
|
|
key: viewModel.value.month,
|
|
dir: monthDirection.value,
|
|
goTo: goToMonth,
|
|
boundaries: navBoundaries.value.month,
|
|
cls: ' col'
|
|
}).concat(getNavigation({
|
|
label: viewModel.value.year,
|
|
type: 'Years',
|
|
key: viewModel.value.year,
|
|
dir: yearDirection.value,
|
|
goTo: goToYear,
|
|
boundaries: navBoundaries.value.year,
|
|
cls: ''
|
|
}))),
|
|
|
|
vue.h('div', {
|
|
class: 'q-date__calendar-weekdays row items-center no-wrap'
|
|
}, daysOfWeek.value.map(day => vue.h('div', { class: 'q-date__calendar-item' }, [ vue.h('div', day) ]))),
|
|
|
|
vue.h('div', {
|
|
class: 'q-date__calendar-days-container relative-position overflow-hidden'
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--slide-' + monthDirection.value
|
|
}, () => vue.h('div', {
|
|
key: viewMonthHash.value,
|
|
class: 'q-date__calendar-days fit'
|
|
}, days.value.map(day => vue.h('div', { class: day.classes }, [
|
|
day.in === true
|
|
? vue.h(
|
|
QBtn, {
|
|
class: day.today === true ? 'q-date__today' : '',
|
|
dense: true,
|
|
flat: day.flat,
|
|
unelevated: day.unelevated,
|
|
color: day.color,
|
|
textColor: day.textColor,
|
|
label: day.i,
|
|
tabindex: tabindex.value,
|
|
...getCache('day#' + day.i, {
|
|
onClick: () => { onDayClick(day.i); },
|
|
onMouseover: () => { onDayMouseover(day.i); }
|
|
})
|
|
},
|
|
day.event !== false
|
|
? () => vue.h('div', { class: 'q-date__event bg-' + day.event })
|
|
: null
|
|
)
|
|
: vue.h('div', '' + day.i)
|
|
]))))
|
|
])
|
|
])
|
|
]),
|
|
|
|
Months () {
|
|
const currentYear = viewModel.value.year === today.value.year;
|
|
const isDisabled = month => {
|
|
return (
|
|
(minNav.value !== null && viewModel.value.year === minNav.value.year && minNav.value.month > month)
|
|
|| (maxNav.value !== null && viewModel.value.year === maxNav.value.year && maxNav.value.month < month)
|
|
)
|
|
};
|
|
|
|
const content = innerLocale.value.monthsShort.map((month, i) => {
|
|
const active = viewModel.value.month === i + 1;
|
|
|
|
return vue.h('div', {
|
|
class: 'q-date__months-item flex flex-center'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
class: currentYear === true && today.value.month === i + 1 ? 'q-date__today' : null,
|
|
flat: active !== true,
|
|
label: month,
|
|
unelevated: active,
|
|
color: active === true ? computedColor.value : null,
|
|
textColor: active === true ? computedTextColor.value : null,
|
|
tabindex: tabindex.value,
|
|
disable: isDisabled(i + 1),
|
|
...getCache('month#' + i, { onClick: () => { setMonth(i + 1); } })
|
|
})
|
|
])
|
|
});
|
|
|
|
props.yearsInMonthView === true && content.unshift(
|
|
vue.h('div', { class: 'row no-wrap full-width' }, [
|
|
getNavigation({
|
|
label: viewModel.value.year,
|
|
type: 'Years',
|
|
key: viewModel.value.year,
|
|
dir: yearDirection.value,
|
|
goTo: goToYear,
|
|
boundaries: navBoundaries.value.year,
|
|
cls: ' col'
|
|
})
|
|
])
|
|
);
|
|
|
|
return vue.h('div', {
|
|
key: 'months-view',
|
|
class: 'q-date__view q-date__months flex flex-center'
|
|
}, content)
|
|
},
|
|
|
|
Years () {
|
|
const
|
|
start = startYear.value,
|
|
stop = start + yearsInterval,
|
|
years = [];
|
|
|
|
const isDisabled = year => {
|
|
return (
|
|
(minNav.value !== null && minNav.value.year > year)
|
|
|| (maxNav.value !== null && maxNav.value.year < year)
|
|
)
|
|
};
|
|
|
|
for (let i = start; i <= stop; i++) {
|
|
const active = viewModel.value.year === i;
|
|
|
|
years.push(
|
|
vue.h('div', {
|
|
class: 'q-date__years-item flex flex-center'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
key: 'yr' + i,
|
|
class: today.value.year === i ? 'q-date__today' : null,
|
|
flat: !active,
|
|
label: i,
|
|
dense: true,
|
|
unelevated: active,
|
|
color: active === true ? computedColor.value : null,
|
|
textColor: active === true ? computedTextColor.value : null,
|
|
tabindex: tabindex.value,
|
|
disable: isDisabled(i),
|
|
...getCache('yr#' + i, { onClick: () => { setYear(i); } })
|
|
})
|
|
])
|
|
);
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: 'q-date__view q-date__years flex flex-center'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'col-auto'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
round: true,
|
|
dense: true,
|
|
flat: true,
|
|
icon: dateArrow.value[ 0 ],
|
|
tabindex: tabindex.value,
|
|
disable: isDisabled(start),
|
|
...getCache('y-', { onClick: () => { startYear.value -= yearsInterval; } })
|
|
})
|
|
]),
|
|
|
|
vue.h('div', {
|
|
class: 'q-date__years-content col self-stretch row items-center'
|
|
}, years),
|
|
|
|
vue.h('div', {
|
|
class: 'col-auto'
|
|
}, [
|
|
vue.h(QBtn, {
|
|
round: true,
|
|
dense: true,
|
|
flat: true,
|
|
icon: dateArrow.value[ 1 ],
|
|
tabindex: tabindex.value,
|
|
disable: isDisabled(stop),
|
|
...getCache('y+', { onClick: () => { startYear.value += yearsInterval; } })
|
|
})
|
|
])
|
|
])
|
|
}
|
|
};
|
|
|
|
function onDayClick (dayIndex) {
|
|
const day = { ...viewModel.value, day: dayIndex };
|
|
|
|
if (props.range === false) {
|
|
toggleDate(day, viewMonthHash.value);
|
|
return
|
|
}
|
|
|
|
if (editRange.value === null) {
|
|
const dayProps = days.value.find(day => day.fill !== true && day.i === dayIndex);
|
|
|
|
if (props.noUnset !== true && dayProps.range !== void 0) {
|
|
removeFromModel({ target: day, from: dayProps.range.from, to: dayProps.range.to });
|
|
return
|
|
}
|
|
|
|
if (dayProps.selected === true) {
|
|
removeFromModel(day);
|
|
return
|
|
}
|
|
|
|
const initHash = getDayHash(day);
|
|
|
|
editRange.value = {
|
|
init: day,
|
|
initHash,
|
|
final: day,
|
|
finalHash: initHash
|
|
};
|
|
|
|
emit('rangeStart', getShortDate(day));
|
|
}
|
|
else {
|
|
const
|
|
initHash = editRange.value.initHash,
|
|
finalHash = getDayHash(day),
|
|
payload = initHash <= finalHash
|
|
? { from: editRange.value.init, to: day }
|
|
: { from: day, to: editRange.value.init };
|
|
|
|
editRange.value = null;
|
|
addToModel(initHash === finalHash ? day : { target: day, ...payload });
|
|
|
|
emit('rangeEnd', {
|
|
from: getShortDate(payload.from),
|
|
to: getShortDate(payload.to)
|
|
});
|
|
}
|
|
}
|
|
|
|
function onDayMouseover (dayIndex) {
|
|
if (editRange.value !== null) {
|
|
const final = { ...viewModel.value, day: dayIndex };
|
|
|
|
Object.assign(editRange.value, {
|
|
final,
|
|
finalHash: getDayHash(final)
|
|
});
|
|
}
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
setToday, setView, offsetCalendar, setCalendarTo, setEditingRange
|
|
});
|
|
|
|
return () => {
|
|
const content = [
|
|
vue.h('div', {
|
|
class: 'q-date__content col relative-position'
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--fade'
|
|
}, renderViews[ view.value ])
|
|
])
|
|
];
|
|
|
|
const def = hSlot(slots.default);
|
|
def !== void 0 && content.push(
|
|
vue.h('div', { class: 'q-date__actions' }, def)
|
|
);
|
|
|
|
if (props.name !== void 0 && props.disable !== true) {
|
|
injectFormInput(content, 'push');
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
...attributes.value
|
|
}, [
|
|
getHeader(),
|
|
|
|
vue.h('div', {
|
|
ref: blurTargetRef,
|
|
class: 'q-date__main col column',
|
|
tabindex: -1
|
|
}, content)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
function useHistory (showing, hide, hideOnRouteChange) {
|
|
let historyEntry;
|
|
|
|
function removeFromHistory () {
|
|
if (historyEntry !== void 0) {
|
|
History.remove(historyEntry);
|
|
historyEntry = void 0;
|
|
}
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
showing.value === true && removeFromHistory();
|
|
});
|
|
|
|
return {
|
|
removeFromHistory,
|
|
|
|
addToHistory () {
|
|
historyEntry = {
|
|
condition: () => hideOnRouteChange.value === true,
|
|
handler: hide
|
|
};
|
|
|
|
History.add(historyEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
let
|
|
registered = 0,
|
|
scrollPositionX,
|
|
scrollPositionY,
|
|
maxScrollTop,
|
|
vpPendingUpdate = false,
|
|
bodyLeft,
|
|
bodyTop,
|
|
href,
|
|
closeTimer = null;
|
|
|
|
function onWheel (e) {
|
|
if (shouldPreventScroll(e)) {
|
|
stopAndPrevent(e);
|
|
}
|
|
}
|
|
|
|
function shouldPreventScroll (e) {
|
|
if (e.target === document.body || e.target.classList.contains('q-layout__backdrop')) {
|
|
return true
|
|
}
|
|
|
|
const
|
|
path = getEventPath(e),
|
|
shift = e.shiftKey && !e.deltaX,
|
|
scrollY = !shift && Math.abs(e.deltaX) <= Math.abs(e.deltaY),
|
|
delta = shift || scrollY ? e.deltaY : e.deltaX;
|
|
|
|
for (let index = 0; index < path.length; index++) {
|
|
const el = path[ index ];
|
|
|
|
if (hasScrollbar(el, scrollY)) {
|
|
return scrollY
|
|
? (
|
|
delta < 0 && el.scrollTop === 0
|
|
? true
|
|
: delta > 0 && el.scrollTop + el.clientHeight === el.scrollHeight
|
|
)
|
|
: (
|
|
delta < 0 && el.scrollLeft === 0
|
|
? true
|
|
: delta > 0 && el.scrollLeft + el.clientWidth === el.scrollWidth
|
|
)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
function onAppleScroll (e) {
|
|
if (e.target === document) {
|
|
// required, otherwise iOS blocks further scrolling
|
|
// until the mobile scrollbar dissappears
|
|
document.scrollingElement.scrollTop = document.scrollingElement.scrollTop; // eslint-disable-line
|
|
}
|
|
}
|
|
|
|
function onAppleResize (evt) {
|
|
if (vpPendingUpdate === true) {
|
|
return
|
|
}
|
|
|
|
vpPendingUpdate = true;
|
|
|
|
requestAnimationFrame(() => {
|
|
vpPendingUpdate = false;
|
|
|
|
const
|
|
{ height } = evt.target,
|
|
{ clientHeight, scrollTop } = document.scrollingElement;
|
|
|
|
if (maxScrollTop === void 0 || height !== window.innerHeight) {
|
|
maxScrollTop = clientHeight - height;
|
|
document.scrollingElement.scrollTop = scrollTop;
|
|
}
|
|
|
|
if (scrollTop > maxScrollTop) {
|
|
document.scrollingElement.scrollTop -= Math.ceil((scrollTop - maxScrollTop) / 8);
|
|
}
|
|
});
|
|
}
|
|
|
|
function apply$1 (action) {
|
|
const
|
|
body = document.body,
|
|
hasViewport = window.visualViewport !== void 0;
|
|
|
|
if (action === 'add') {
|
|
const { overflowY, overflowX } = window.getComputedStyle(body);
|
|
|
|
scrollPositionX = getHorizontalScrollPosition(window);
|
|
scrollPositionY = getVerticalScrollPosition(window);
|
|
bodyLeft = body.style.left;
|
|
bodyTop = body.style.top;
|
|
|
|
href = window.location.href;
|
|
|
|
body.style.left = `-${ scrollPositionX }px`;
|
|
body.style.top = `-${ scrollPositionY }px`;
|
|
|
|
if (overflowX !== 'hidden' && (overflowX === 'scroll' || body.scrollWidth > window.innerWidth)) {
|
|
body.classList.add('q-body--force-scrollbar-x');
|
|
}
|
|
if (overflowY !== 'hidden' && (overflowY === 'scroll' || body.scrollHeight > window.innerHeight)) {
|
|
body.classList.add('q-body--force-scrollbar-y');
|
|
}
|
|
|
|
body.classList.add('q-body--prevent-scroll');
|
|
document.qScrollPrevented = true;
|
|
|
|
if (client.is.ios === true) {
|
|
if (hasViewport === true) {
|
|
window.scrollTo(0, 0);
|
|
window.visualViewport.addEventListener('resize', onAppleResize, listenOpts.passiveCapture);
|
|
window.visualViewport.addEventListener('scroll', onAppleResize, listenOpts.passiveCapture);
|
|
window.scrollTo(0, 0);
|
|
}
|
|
else {
|
|
window.addEventListener('scroll', onAppleScroll, listenOpts.passiveCapture);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client.is.desktop === true && client.is.mac === true) {
|
|
// ref. https://developers.google.com/web/updates/2017/01/scrolling-intervention
|
|
window[ `${ action }EventListener` ]('wheel', onWheel, listenOpts.notPassive);
|
|
}
|
|
|
|
if (action === 'remove') {
|
|
if (client.is.ios === true) {
|
|
if (hasViewport === true) {
|
|
window.visualViewport.removeEventListener('resize', onAppleResize, listenOpts.passiveCapture);
|
|
window.visualViewport.removeEventListener('scroll', onAppleResize, listenOpts.passiveCapture);
|
|
}
|
|
else {
|
|
window.removeEventListener('scroll', onAppleScroll, listenOpts.passiveCapture);
|
|
}
|
|
}
|
|
|
|
body.classList.remove('q-body--prevent-scroll');
|
|
body.classList.remove('q-body--force-scrollbar-x');
|
|
body.classList.remove('q-body--force-scrollbar-y');
|
|
|
|
document.qScrollPrevented = false;
|
|
|
|
body.style.left = bodyLeft;
|
|
body.style.top = bodyTop;
|
|
|
|
// scroll back only if route has not changed
|
|
if (window.location.href === href) {
|
|
window.scrollTo(scrollPositionX, scrollPositionY);
|
|
}
|
|
|
|
maxScrollTop = void 0;
|
|
}
|
|
}
|
|
|
|
function preventScroll (state) {
|
|
let action = 'add';
|
|
|
|
if (state === true) {
|
|
registered++;
|
|
|
|
if (closeTimer !== null) {
|
|
clearTimeout(closeTimer);
|
|
closeTimer = null;
|
|
return
|
|
}
|
|
|
|
if (registered > 1) {
|
|
return
|
|
}
|
|
}
|
|
else {
|
|
if (registered === 0) {
|
|
return
|
|
}
|
|
|
|
registered--;
|
|
|
|
if (registered > 0) {
|
|
return
|
|
}
|
|
|
|
action = 'remove';
|
|
|
|
if (client.is.ios === true && client.is.nativeMobile === true) {
|
|
closeTimer !== null && clearTimeout(closeTimer);
|
|
closeTimer = setTimeout(() => {
|
|
apply$1(action);
|
|
closeTimer = null;
|
|
}, 100);
|
|
return
|
|
}
|
|
}
|
|
|
|
apply$1(action);
|
|
}
|
|
|
|
function usePreventScroll () {
|
|
let currentState;
|
|
|
|
return {
|
|
preventBodyScroll (state) {
|
|
if (
|
|
state !== currentState
|
|
&& (currentState !== void 0 || state === true)
|
|
) {
|
|
currentState = state;
|
|
preventScroll(state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let maximizedModals = 0;
|
|
|
|
const positionClass$1 = {
|
|
standard: 'fixed-full flex-center',
|
|
top: 'fixed-top justify-center',
|
|
bottom: 'fixed-bottom justify-center',
|
|
right: 'fixed-right items-center',
|
|
left: 'fixed-left items-center'
|
|
};
|
|
|
|
const defaultTransitions = {
|
|
standard: [ 'scale', 'scale' ],
|
|
top: [ 'slide-down', 'slide-up' ],
|
|
bottom: [ 'slide-up', 'slide-down' ],
|
|
right: [ 'slide-left', 'slide-right' ],
|
|
left: [ 'slide-right', 'slide-left' ]
|
|
};
|
|
|
|
var QDialog = createComponent({
|
|
name: 'QDialog',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useModelToggleProps,
|
|
...useTransitionProps,
|
|
|
|
transitionShow: String, // override useTransitionProps
|
|
transitionHide: String, // override useTransitionProps
|
|
|
|
persistent: Boolean,
|
|
autoClose: Boolean,
|
|
allowFocusOutside: Boolean,
|
|
|
|
noEscDismiss: Boolean,
|
|
noBackdropDismiss: Boolean,
|
|
noRouteDismiss: Boolean,
|
|
noRefocus: Boolean,
|
|
noFocus: Boolean,
|
|
noShake: Boolean,
|
|
|
|
seamless: Boolean,
|
|
|
|
maximized: Boolean,
|
|
fullWidth: Boolean,
|
|
fullHeight: Boolean,
|
|
|
|
square: Boolean,
|
|
|
|
position: {
|
|
type: String,
|
|
default: 'standard',
|
|
validator: val => val === 'standard'
|
|
|| [ 'top', 'bottom', 'left', 'right' ].includes(val)
|
|
}
|
|
},
|
|
|
|
emits: [
|
|
...useModelToggleEmits,
|
|
'shake', 'click', 'escapeKey'
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
const vm = vue.getCurrentInstance();
|
|
|
|
const innerRef = vue.ref(null);
|
|
const showing = vue.ref(false);
|
|
const animating = vue.ref(false);
|
|
|
|
let shakeTimeout = null, refocusTarget = null, isMaximized, avoidAutoClose;
|
|
|
|
const hideOnRouteChange = vue.computed(() =>
|
|
props.persistent !== true
|
|
&& props.noRouteDismiss !== true
|
|
&& props.seamless !== true
|
|
);
|
|
|
|
const { preventBodyScroll } = usePreventScroll();
|
|
const { registerTimeout } = useTimeout();
|
|
const { registerTick, removeTick } = useTick();
|
|
|
|
const { transitionProps, transitionStyle } = useTransition(
|
|
props,
|
|
() => defaultTransitions[ props.position ][ 0 ],
|
|
() => defaultTransitions[ props.position ][ 1 ]
|
|
);
|
|
|
|
const { showPortal, hidePortal, portalIsAccessible, renderPortal } = usePortal(
|
|
vm, innerRef, renderPortalContent, 'dialog'
|
|
);
|
|
|
|
const { hide } = useModelToggle({
|
|
showing,
|
|
hideOnRouteChange,
|
|
handleShow,
|
|
handleHide,
|
|
processOnMount: true
|
|
});
|
|
|
|
const { addToHistory, removeFromHistory } = useHistory(showing, hide, hideOnRouteChange);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-dialog__inner flex no-pointer-events'
|
|
+ ` q-dialog__inner--${ props.maximized === true ? 'maximized' : 'minimized' }`
|
|
+ ` q-dialog__inner--${ props.position } ${ positionClass$1[ props.position ] }`
|
|
+ (animating.value === true ? ' q-dialog__inner--animating' : '')
|
|
+ (props.fullWidth === true ? ' q-dialog__inner--fullwidth' : '')
|
|
+ (props.fullHeight === true ? ' q-dialog__inner--fullheight' : '')
|
|
+ (props.square === true ? ' q-dialog__inner--square' : '')
|
|
);
|
|
|
|
const useBackdrop = vue.computed(() => showing.value === true && props.seamless !== true);
|
|
|
|
const onEvents = vue.computed(() => (
|
|
props.autoClose === true
|
|
? { onClick: onAutoClose }
|
|
: {}
|
|
));
|
|
|
|
const rootClasses = vue.computed(() => [
|
|
'q-dialog fullscreen no-pointer-events '
|
|
+ `q-dialog--${ useBackdrop.value === true ? 'modal' : 'seamless' }`,
|
|
attrs.class
|
|
]);
|
|
|
|
vue.watch(() => props.maximized, state => {
|
|
showing.value === true && updateMaximized(state);
|
|
});
|
|
|
|
vue.watch(useBackdrop, val => {
|
|
preventBodyScroll(val);
|
|
|
|
if (val === true) {
|
|
addFocusout(onFocusChange);
|
|
addEscapeKey(onEscapeKey);
|
|
}
|
|
else {
|
|
removeFocusout(onFocusChange);
|
|
removeEscapeKey(onEscapeKey);
|
|
}
|
|
});
|
|
|
|
function handleShow (evt) {
|
|
addToHistory();
|
|
|
|
refocusTarget = props.noRefocus === false && document.activeElement !== null
|
|
? document.activeElement
|
|
: null;
|
|
|
|
updateMaximized(props.maximized);
|
|
showPortal();
|
|
animating.value = true;
|
|
|
|
if (props.noFocus !== true) {
|
|
document.activeElement !== null && document.activeElement.blur();
|
|
registerTick(focus);
|
|
}
|
|
else {
|
|
removeTick();
|
|
}
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
if (vm.proxy.$q.platform.is.ios === true) {
|
|
if (props.seamless !== true && document.activeElement) {
|
|
const
|
|
{ top, bottom } = document.activeElement.getBoundingClientRect(),
|
|
{ innerHeight } = window,
|
|
height = window.visualViewport !== void 0
|
|
? window.visualViewport.height
|
|
: innerHeight;
|
|
|
|
if (top > 0 && bottom > height / 2) {
|
|
document.scrollingElement.scrollTop = Math.min(
|
|
document.scrollingElement.scrollHeight - height,
|
|
bottom >= innerHeight
|
|
? Infinity
|
|
: Math.ceil(document.scrollingElement.scrollTop + bottom - height / 2)
|
|
);
|
|
}
|
|
|
|
document.activeElement.scrollIntoView();
|
|
}
|
|
|
|
// required in order to avoid the "double-tap needed" issue
|
|
avoidAutoClose = true;
|
|
innerRef.value.click();
|
|
avoidAutoClose = false;
|
|
}
|
|
|
|
showPortal(true); // done showing portal
|
|
animating.value = false;
|
|
emit('show', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function handleHide (evt) {
|
|
removeTick();
|
|
removeFromHistory();
|
|
cleanup(true);
|
|
animating.value = true;
|
|
hidePortal();
|
|
|
|
if (refocusTarget !== null) {
|
|
((evt && evt.type.indexOf('key') === 0
|
|
? refocusTarget.closest('[tabindex]:not([tabindex^="-"])')
|
|
: void 0
|
|
) || refocusTarget).focus();
|
|
refocusTarget = null;
|
|
}
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
hidePortal(true); // done hiding, now destroy
|
|
animating.value = false;
|
|
emit('hide', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function focus (selector) {
|
|
addFocusFn(() => {
|
|
let node = innerRef.value;
|
|
|
|
if (node === null || node.contains(document.activeElement) === true) {
|
|
return
|
|
}
|
|
|
|
node = (selector !== '' ? node.querySelector(selector) : null)
|
|
|| node.querySelector('[autofocus][tabindex], [data-autofocus][tabindex]')
|
|
|| node.querySelector('[autofocus] [tabindex], [data-autofocus] [tabindex]')
|
|
|| node.querySelector('[autofocus], [data-autofocus]')
|
|
|| node;
|
|
node.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
|
|
function shake (focusTarget) {
|
|
if (focusTarget && typeof focusTarget.focus === 'function') {
|
|
focusTarget.focus({ preventScroll: true });
|
|
}
|
|
else {
|
|
focus();
|
|
}
|
|
|
|
emit('shake');
|
|
|
|
const node = innerRef.value;
|
|
|
|
if (node !== null) {
|
|
node.classList.remove('q-animate--scale');
|
|
node.classList.add('q-animate--scale');
|
|
shakeTimeout !== null && clearTimeout(shakeTimeout);
|
|
shakeTimeout = setTimeout(() => {
|
|
shakeTimeout = null;
|
|
if (innerRef.value !== null) {
|
|
node.classList.remove('q-animate--scale');
|
|
// some platforms (like desktop Chrome)
|
|
// require calling focus() again
|
|
focus();
|
|
}
|
|
}, 170);
|
|
}
|
|
}
|
|
|
|
function onEscapeKey () {
|
|
if (props.seamless !== true) {
|
|
if (props.persistent === true || props.noEscDismiss === true) {
|
|
props.maximized !== true && props.noShake !== true && shake();
|
|
}
|
|
else {
|
|
emit('escapeKey');
|
|
hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
function cleanup (hiding) {
|
|
if (shakeTimeout !== null) {
|
|
clearTimeout(shakeTimeout);
|
|
shakeTimeout = null;
|
|
}
|
|
|
|
if (hiding === true || showing.value === true) {
|
|
updateMaximized(false);
|
|
|
|
if (props.seamless !== true) {
|
|
preventBodyScroll(false);
|
|
removeFocusout(onFocusChange);
|
|
removeEscapeKey(onEscapeKey);
|
|
}
|
|
}
|
|
|
|
if (hiding !== true) {
|
|
refocusTarget = null;
|
|
}
|
|
}
|
|
|
|
function updateMaximized (active) {
|
|
if (active === true) {
|
|
if (isMaximized !== true) {
|
|
maximizedModals < 1 && document.body.classList.add('q-body--dialog');
|
|
maximizedModals++;
|
|
|
|
isMaximized = true;
|
|
}
|
|
}
|
|
else if (isMaximized === true) {
|
|
if (maximizedModals < 2) {
|
|
document.body.classList.remove('q-body--dialog');
|
|
}
|
|
|
|
maximizedModals--;
|
|
isMaximized = false;
|
|
}
|
|
}
|
|
|
|
function onAutoClose (e) {
|
|
if (avoidAutoClose !== true) {
|
|
hide(e);
|
|
emit('click', e);
|
|
}
|
|
}
|
|
|
|
function onBackdropClick (e) {
|
|
if (props.persistent !== true && props.noBackdropDismiss !== true) {
|
|
hide(e);
|
|
}
|
|
else if (props.noShake !== true) {
|
|
shake();
|
|
}
|
|
}
|
|
|
|
function onFocusChange (evt) {
|
|
// the focus is not in a vue child component
|
|
if (
|
|
props.allowFocusOutside !== true
|
|
&& portalIsAccessible.value === true
|
|
&& childHasFocus(innerRef.value, evt.target) !== true
|
|
) {
|
|
focus('[tabindex]:not([tabindex="-1"])');
|
|
}
|
|
}
|
|
|
|
Object.assign(vm.proxy, {
|
|
// expose public methods
|
|
focus, shake,
|
|
|
|
// private but needed by QSelect
|
|
__updateRefocusTarget (target) {
|
|
refocusTarget = target || null;
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(cleanup);
|
|
|
|
function renderPortalContent () {
|
|
return vue.h('div', {
|
|
role: 'dialog',
|
|
'aria-modal': useBackdrop.value === true ? 'true' : 'false',
|
|
...attrs,
|
|
class: rootClasses.value
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--fade',
|
|
appear: true
|
|
}, () => (
|
|
useBackdrop.value === true
|
|
? vue.h('div', {
|
|
class: 'q-dialog__backdrop fixed-full',
|
|
style: transitionStyle.value,
|
|
'aria-hidden': 'true',
|
|
tabindex: -1,
|
|
onClick: onBackdropClick
|
|
})
|
|
: null
|
|
)),
|
|
|
|
vue.h(
|
|
vue.Transition,
|
|
transitionProps.value,
|
|
() => (
|
|
showing.value === true
|
|
? vue.h('div', {
|
|
ref: innerRef,
|
|
class: classes.value,
|
|
style: transitionStyle.value,
|
|
tabindex: -1,
|
|
...onEvents.value
|
|
}, hSlot(slots.default))
|
|
: null
|
|
)
|
|
)
|
|
])
|
|
}
|
|
|
|
return renderPortal
|
|
}
|
|
});
|
|
|
|
const duration = 150;
|
|
|
|
var QDrawer = createComponent({
|
|
name: 'QDrawer',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useModelToggleProps,
|
|
...useDarkProps,
|
|
|
|
side: {
|
|
type: String,
|
|
default: 'left',
|
|
validator: v => [ 'left', 'right' ].includes(v)
|
|
},
|
|
|
|
width: {
|
|
type: Number,
|
|
default: 300
|
|
},
|
|
|
|
mini: Boolean,
|
|
miniToOverlay: Boolean,
|
|
miniWidth: {
|
|
type: Number,
|
|
default: 57
|
|
},
|
|
noMiniAnimation: Boolean,
|
|
|
|
breakpoint: {
|
|
type: Number,
|
|
default: 1023
|
|
},
|
|
showIfAbove: Boolean,
|
|
|
|
behavior: {
|
|
type: String,
|
|
validator: v => [ 'default', 'desktop', 'mobile' ].includes(v),
|
|
default: 'default'
|
|
},
|
|
|
|
bordered: Boolean,
|
|
elevated: Boolean,
|
|
|
|
overlay: Boolean,
|
|
persistent: Boolean,
|
|
noSwipeOpen: Boolean,
|
|
noSwipeClose: Boolean,
|
|
noSwipeBackdrop: Boolean
|
|
},
|
|
|
|
emits: [
|
|
...useModelToggleEmits,
|
|
'onLayout', 'miniState'
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { proxy: { $q } } = vm;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { preventBodyScroll } = usePreventScroll();
|
|
const { registerTimeout, removeTimeout } = useTimeout();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QDrawer needs to be child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
let lastDesktopState, timerMini = null, layoutTotalWidthWatcher;
|
|
|
|
const belowBreakpoint = vue.ref(
|
|
props.behavior === 'mobile'
|
|
|| (props.behavior !== 'desktop' && $layout.totalWidth.value <= props.breakpoint)
|
|
);
|
|
|
|
const isMini = vue.computed(() =>
|
|
props.mini === true && belowBreakpoint.value !== true
|
|
);
|
|
|
|
const size = vue.computed(() => (
|
|
isMini.value === true
|
|
? props.miniWidth
|
|
: props.width
|
|
));
|
|
|
|
const showing = vue.ref(
|
|
props.showIfAbove === true && belowBreakpoint.value === false
|
|
? true
|
|
: props.modelValue === true
|
|
);
|
|
|
|
const hideOnRouteChange = vue.computed(() =>
|
|
props.persistent !== true
|
|
&& (belowBreakpoint.value === true || onScreenOverlay.value === true)
|
|
);
|
|
|
|
function handleShow (evt, noEvent) {
|
|
addToHistory();
|
|
|
|
evt !== false && $layout.animate();
|
|
applyPosition(0);
|
|
|
|
if (belowBreakpoint.value === true) {
|
|
const otherInstance = $layout.instances[ otherSide.value ];
|
|
if (otherInstance !== void 0 && otherInstance.belowBreakpoint === true) {
|
|
otherInstance.hide(false);
|
|
}
|
|
|
|
applyBackdrop(1);
|
|
$layout.isContainer.value !== true && preventBodyScroll(true);
|
|
}
|
|
else {
|
|
applyBackdrop(0);
|
|
evt !== false && setScrollable(false);
|
|
}
|
|
|
|
registerTimeout(() => {
|
|
evt !== false && setScrollable(true);
|
|
noEvent !== true && emit('show', evt);
|
|
}, duration);
|
|
}
|
|
|
|
function handleHide (evt, noEvent) {
|
|
removeFromHistory();
|
|
|
|
evt !== false && $layout.animate();
|
|
|
|
applyBackdrop(0);
|
|
applyPosition(stateDirection.value * size.value);
|
|
|
|
cleanup();
|
|
|
|
if (noEvent !== true) {
|
|
registerTimeout(() => { emit('hide', evt); }, duration);
|
|
}
|
|
else {
|
|
removeTimeout();
|
|
}
|
|
}
|
|
|
|
const { show, hide } = useModelToggle({
|
|
showing,
|
|
hideOnRouteChange,
|
|
handleShow,
|
|
handleHide
|
|
});
|
|
|
|
const { addToHistory, removeFromHistory } = useHistory(showing, hide, hideOnRouteChange);
|
|
|
|
const instance = {
|
|
belowBreakpoint,
|
|
hide
|
|
};
|
|
|
|
const rightSide = vue.computed(() => props.side === 'right');
|
|
|
|
const stateDirection = vue.computed(() =>
|
|
($q.lang.rtl === true ? -1 : 1) * (rightSide.value === true ? 1 : -1)
|
|
);
|
|
|
|
const flagBackdropBg = vue.ref(0);
|
|
const flagPanning = vue.ref(false);
|
|
const flagMiniAnimate = vue.ref(false);
|
|
const flagContentPosition = vue.ref( // starting with "hidden" for SSR
|
|
size.value * stateDirection.value
|
|
);
|
|
|
|
const otherSide = vue.computed(() => (rightSide.value === true ? 'left' : 'right'));
|
|
const offset = vue.computed(() => (
|
|
showing.value === true && belowBreakpoint.value === false && props.overlay === false
|
|
? (props.miniToOverlay === true ? props.miniWidth : size.value)
|
|
: 0
|
|
));
|
|
|
|
const fixed = vue.computed(() =>
|
|
props.overlay === true
|
|
|| props.miniToOverlay === true
|
|
|| $layout.view.value.indexOf(rightSide.value ? 'R' : 'L') > -1
|
|
|| ($q.platform.is.ios === true && $layout.isContainer.value === true)
|
|
);
|
|
|
|
const onLayout = vue.computed(() =>
|
|
props.overlay === false
|
|
&& showing.value === true
|
|
&& belowBreakpoint.value === false
|
|
);
|
|
|
|
const onScreenOverlay = vue.computed(() =>
|
|
props.overlay === true
|
|
&& showing.value === true
|
|
&& belowBreakpoint.value === false
|
|
);
|
|
|
|
const backdropClass = vue.computed(() =>
|
|
'fullscreen q-drawer__backdrop'
|
|
+ (showing.value === false && flagPanning.value === false ? ' hidden' : '')
|
|
);
|
|
|
|
const backdropStyle = vue.computed(() => ({
|
|
backgroundColor: `rgba(0,0,0,${ flagBackdropBg.value * 0.4 })`
|
|
}));
|
|
|
|
const headerSlot = vue.computed(() => (
|
|
rightSide.value === true
|
|
? $layout.rows.value.top[ 2 ] === 'r'
|
|
: $layout.rows.value.top[ 0 ] === 'l'
|
|
));
|
|
|
|
const footerSlot = vue.computed(() => (
|
|
rightSide.value === true
|
|
? $layout.rows.value.bottom[ 2 ] === 'r'
|
|
: $layout.rows.value.bottom[ 0 ] === 'l'
|
|
));
|
|
|
|
const aboveStyle = vue.computed(() => {
|
|
const css = {};
|
|
|
|
if ($layout.header.space === true && headerSlot.value === false) {
|
|
if (fixed.value === true) {
|
|
css.top = `${ $layout.header.offset }px`;
|
|
}
|
|
else if ($layout.header.space === true) {
|
|
css.top = `${ $layout.header.size }px`;
|
|
}
|
|
}
|
|
|
|
if ($layout.footer.space === true && footerSlot.value === false) {
|
|
if (fixed.value === true) {
|
|
css.bottom = `${ $layout.footer.offset }px`;
|
|
}
|
|
else if ($layout.footer.space === true) {
|
|
css.bottom = `${ $layout.footer.size }px`;
|
|
}
|
|
}
|
|
|
|
return css
|
|
});
|
|
|
|
const style = vue.computed(() => {
|
|
const style = {
|
|
width: `${ size.value }px`,
|
|
transform: `translateX(${ flagContentPosition.value }px)`
|
|
};
|
|
|
|
return belowBreakpoint.value === true
|
|
? style
|
|
: Object.assign(style, aboveStyle.value)
|
|
});
|
|
|
|
const contentClass = vue.computed(() =>
|
|
'q-drawer__content fit '
|
|
+ ($layout.isContainer.value !== true ? 'scroll' : 'overflow-auto')
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-drawer q-drawer--${ props.side }`
|
|
+ (flagMiniAnimate.value === true ? ' q-drawer--mini-animate' : '')
|
|
+ (props.bordered === true ? ' q-drawer--bordered' : '')
|
|
+ (isDark.value === true ? ' q-drawer--dark q-dark' : '')
|
|
+ (
|
|
flagPanning.value === true
|
|
? ' no-transition'
|
|
: (showing.value === true ? '' : ' q-layout--prevent-focus')
|
|
)
|
|
+ (
|
|
belowBreakpoint.value === true
|
|
? ' fixed q-drawer--on-top q-drawer--mobile q-drawer--top-padding'
|
|
: ` q-drawer--${ isMini.value === true ? 'mini' : 'standard' }`
|
|
+ (fixed.value === true || onLayout.value !== true ? ' fixed' : '')
|
|
+ (props.overlay === true || props.miniToOverlay === true ? ' q-drawer--on-top' : '')
|
|
+ (headerSlot.value === true ? ' q-drawer--top-padding' : '')
|
|
)
|
|
);
|
|
|
|
const openDirective = vue.computed(() => {
|
|
// if props.noSwipeOpen !== true
|
|
const dir = $q.lang.rtl === true ? props.side : otherSide.value;
|
|
|
|
return [ [
|
|
TouchPan,
|
|
onOpenPan,
|
|
void 0,
|
|
{
|
|
[ dir ]: true,
|
|
mouse: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
const contentCloseDirective = vue.computed(() => {
|
|
// if belowBreakpoint.value === true && props.noSwipeClose !== true
|
|
const dir = $q.lang.rtl === true ? otherSide.value : props.side;
|
|
|
|
return [ [
|
|
TouchPan,
|
|
onClosePan,
|
|
void 0,
|
|
{
|
|
[ dir ]: true,
|
|
mouse: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
const backdropCloseDirective = vue.computed(() => {
|
|
// if showing.value === true && props.noSwipeBackdrop !== true
|
|
const dir = $q.lang.rtl === true ? otherSide.value : props.side;
|
|
|
|
return [ [
|
|
TouchPan,
|
|
onClosePan,
|
|
void 0,
|
|
{
|
|
[ dir ]: true,
|
|
mouse: true,
|
|
mouseAllDir: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
function updateBelowBreakpoint () {
|
|
updateLocal(belowBreakpoint, (
|
|
props.behavior === 'mobile'
|
|
|| (props.behavior !== 'desktop' && $layout.totalWidth.value <= props.breakpoint)
|
|
));
|
|
}
|
|
|
|
vue.watch(belowBreakpoint, val => {
|
|
if (val === true) { // from lg to xs
|
|
lastDesktopState = showing.value;
|
|
showing.value === true && hide(false);
|
|
}
|
|
else if (
|
|
props.overlay === false
|
|
&& props.behavior !== 'mobile'
|
|
&& lastDesktopState !== false
|
|
) { // from xs to lg
|
|
if (showing.value === true) {
|
|
applyPosition(0);
|
|
applyBackdrop(0);
|
|
cleanup();
|
|
}
|
|
else {
|
|
show(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.side, (newSide, oldSide) => {
|
|
if ($layout.instances[ oldSide ] === instance) {
|
|
$layout.instances[ oldSide ] = void 0;
|
|
$layout[ oldSide ].space = false;
|
|
$layout[ oldSide ].offset = 0;
|
|
}
|
|
|
|
$layout.instances[ newSide ] = instance;
|
|
$layout[ newSide ].size = size.value;
|
|
$layout[ newSide ].space = onLayout.value;
|
|
$layout[ newSide ].offset = offset.value;
|
|
});
|
|
|
|
vue.watch($layout.totalWidth, () => {
|
|
if ($layout.isContainer.value === true || document.qScrollPrevented !== true) {
|
|
updateBelowBreakpoint();
|
|
}
|
|
});
|
|
|
|
vue.watch(
|
|
() => props.behavior + props.breakpoint,
|
|
updateBelowBreakpoint
|
|
);
|
|
|
|
vue.watch($layout.isContainer, val => {
|
|
showing.value === true && preventBodyScroll(val !== true);
|
|
val === true && updateBelowBreakpoint();
|
|
});
|
|
|
|
vue.watch($layout.scrollbarWidth, () => {
|
|
applyPosition(showing.value === true ? 0 : void 0);
|
|
});
|
|
|
|
vue.watch(offset, val => { updateLayout('offset', val); });
|
|
|
|
vue.watch(onLayout, val => {
|
|
emit('onLayout', val);
|
|
updateLayout('space', val);
|
|
});
|
|
|
|
vue.watch(rightSide, () => { applyPosition(); });
|
|
|
|
vue.watch(size, val => {
|
|
applyPosition();
|
|
updateSizeOnLayout(props.miniToOverlay, val);
|
|
});
|
|
|
|
vue.watch(() => props.miniToOverlay, val => {
|
|
updateSizeOnLayout(val, size.value);
|
|
});
|
|
|
|
vue.watch(() => $q.lang.rtl, () => { applyPosition(); });
|
|
|
|
vue.watch(() => props.mini, () => {
|
|
if (props.noMiniAnimation) return
|
|
if (props.modelValue === true) {
|
|
animateMini();
|
|
$layout.animate();
|
|
}
|
|
});
|
|
|
|
vue.watch(isMini, val => { emit('miniState', val); });
|
|
|
|
function applyPosition (position) {
|
|
if (position === void 0) {
|
|
vue.nextTick(() => {
|
|
position = showing.value === true ? 0 : size.value;
|
|
applyPosition(stateDirection.value * position);
|
|
});
|
|
}
|
|
else {
|
|
if (
|
|
$layout.isContainer.value === true
|
|
&& rightSide.value === true
|
|
&& (belowBreakpoint.value === true || Math.abs(position) === size.value)
|
|
) {
|
|
position += stateDirection.value * $layout.scrollbarWidth.value;
|
|
}
|
|
|
|
flagContentPosition.value = position;
|
|
}
|
|
}
|
|
|
|
function applyBackdrop (x) {
|
|
flagBackdropBg.value = x;
|
|
}
|
|
|
|
function setScrollable (v) {
|
|
const action = v === true
|
|
? 'remove'
|
|
: ($layout.isContainer.value !== true ? 'add' : '');
|
|
|
|
action !== '' && document.body.classList[ action ]('q-body--drawer-toggle');
|
|
}
|
|
|
|
function animateMini () {
|
|
timerMini !== null && clearTimeout(timerMini);
|
|
|
|
if (vm.proxy && vm.proxy.$el) {
|
|
// need to speed it up and apply it immediately,
|
|
// even faster than Vue's nextTick!
|
|
vm.proxy.$el.classList.add('q-drawer--mini-animate');
|
|
}
|
|
|
|
flagMiniAnimate.value = true;
|
|
timerMini = setTimeout(() => {
|
|
timerMini = null;
|
|
flagMiniAnimate.value = false;
|
|
if (vm && vm.proxy && vm.proxy.$el) {
|
|
vm.proxy.$el.classList.remove('q-drawer--mini-animate');
|
|
}
|
|
}, 150);
|
|
}
|
|
|
|
function onOpenPan (evt) {
|
|
if (showing.value !== false) {
|
|
// some browsers might capture and trigger this
|
|
// even if Drawer has just been opened (but animation is still pending)
|
|
return
|
|
}
|
|
|
|
const
|
|
width = size.value,
|
|
position = between(evt.distance.x, 0, width);
|
|
|
|
if (evt.isFinal === true) {
|
|
const opened = position >= Math.min(75, width);
|
|
|
|
if (opened === true) {
|
|
show();
|
|
}
|
|
else {
|
|
$layout.animate();
|
|
applyBackdrop(0);
|
|
applyPosition(stateDirection.value * width);
|
|
}
|
|
|
|
flagPanning.value = false;
|
|
return
|
|
}
|
|
|
|
applyPosition(
|
|
($q.lang.rtl === true ? rightSide.value !== true : rightSide.value)
|
|
? Math.max(width - position, 0)
|
|
: Math.min(0, position - width)
|
|
);
|
|
applyBackdrop(
|
|
between(position / width, 0, 1)
|
|
);
|
|
|
|
if (evt.isFirst === true) {
|
|
flagPanning.value = true;
|
|
}
|
|
}
|
|
|
|
function onClosePan (evt) {
|
|
if (showing.value !== true) {
|
|
// some browsers might capture and trigger this
|
|
// even if Drawer has just been closed (but animation is still pending)
|
|
return
|
|
}
|
|
|
|
const
|
|
width = size.value,
|
|
dir = evt.direction === props.side,
|
|
position = ($q.lang.rtl === true ? dir !== true : dir)
|
|
? between(evt.distance.x, 0, width)
|
|
: 0;
|
|
|
|
if (evt.isFinal === true) {
|
|
const opened = Math.abs(position) < Math.min(75, width);
|
|
|
|
if (opened === true) {
|
|
$layout.animate();
|
|
applyBackdrop(1);
|
|
applyPosition(0);
|
|
}
|
|
else {
|
|
hide();
|
|
}
|
|
|
|
flagPanning.value = false;
|
|
return
|
|
}
|
|
|
|
applyPosition(stateDirection.value * position);
|
|
applyBackdrop(between(1 - position / width, 0, 1));
|
|
|
|
if (evt.isFirst === true) {
|
|
flagPanning.value = true;
|
|
}
|
|
}
|
|
|
|
function cleanup () {
|
|
preventBodyScroll(false);
|
|
setScrollable(true);
|
|
}
|
|
|
|
function updateLayout (prop, val) {
|
|
$layout.update(props.side, prop, val);
|
|
}
|
|
|
|
function updateLocal (prop, val) {
|
|
if (prop.value !== val) {
|
|
prop.value = val;
|
|
}
|
|
}
|
|
|
|
function updateSizeOnLayout (miniToOverlay, size) {
|
|
updateLayout('size', miniToOverlay === true ? props.miniWidth : size);
|
|
}
|
|
|
|
$layout.instances[ props.side ] = instance;
|
|
updateSizeOnLayout(props.miniToOverlay, size.value);
|
|
updateLayout('space', onLayout.value);
|
|
updateLayout('offset', offset.value);
|
|
|
|
if (
|
|
props.showIfAbove === true
|
|
&& props.modelValue !== true
|
|
&& showing.value === true
|
|
&& props[ 'onUpdate:modelValue' ] !== void 0
|
|
) {
|
|
emit('update:modelValue', true);
|
|
}
|
|
|
|
vue.onMounted(() => {
|
|
emit('onLayout', onLayout.value);
|
|
emit('miniState', isMini.value);
|
|
|
|
lastDesktopState = props.showIfAbove === true;
|
|
|
|
const fn = () => {
|
|
const action = showing.value === true ? handleShow : handleHide;
|
|
action(false, true);
|
|
};
|
|
|
|
if ($layout.totalWidth.value !== 0) {
|
|
// make sure that all computed properties
|
|
// have been updated before calling handleShow/handleHide()
|
|
vue.nextTick(fn);
|
|
return
|
|
}
|
|
|
|
layoutTotalWidthWatcher = vue.watch($layout.totalWidth, () => {
|
|
layoutTotalWidthWatcher();
|
|
layoutTotalWidthWatcher = void 0;
|
|
|
|
if (showing.value === false && props.showIfAbove === true && belowBreakpoint.value === false) {
|
|
show(false);
|
|
}
|
|
else {
|
|
fn();
|
|
}
|
|
});
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
layoutTotalWidthWatcher !== void 0 && layoutTotalWidthWatcher();
|
|
|
|
if (timerMini !== null) {
|
|
clearTimeout(timerMini);
|
|
timerMini = null;
|
|
}
|
|
|
|
showing.value === true && cleanup();
|
|
|
|
if ($layout.instances[ props.side ] === instance) {
|
|
$layout.instances[ props.side ] = void 0;
|
|
updateLayout('size', 0);
|
|
updateLayout('offset', 0);
|
|
updateLayout('space', false);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
const child = [];
|
|
|
|
if (belowBreakpoint.value === true) {
|
|
props.noSwipeOpen === false && child.push(
|
|
vue.withDirectives(
|
|
vue.h('div', {
|
|
key: 'open',
|
|
class: `q-drawer__opener fixed-${ props.side }`,
|
|
'aria-hidden': 'true'
|
|
}),
|
|
openDirective.value
|
|
)
|
|
);
|
|
|
|
child.push(
|
|
hDir(
|
|
'div',
|
|
{
|
|
ref: 'backdrop',
|
|
class: backdropClass.value,
|
|
style: backdropStyle.value,
|
|
'aria-hidden': 'true',
|
|
onClick: hide
|
|
},
|
|
void 0,
|
|
'backdrop',
|
|
props.noSwipeBackdrop !== true && showing.value === true,
|
|
() => backdropCloseDirective.value
|
|
)
|
|
);
|
|
}
|
|
|
|
const mini = isMini.value === true && slots.mini !== void 0;
|
|
const content = [
|
|
vue.h('div', {
|
|
...attrs,
|
|
key: '' + mini, // required otherwise Vue will not diff correctly
|
|
class: [
|
|
contentClass.value,
|
|
attrs.class
|
|
]
|
|
}, mini === true
|
|
? slots.mini()
|
|
: hSlot(slots.default)
|
|
)
|
|
];
|
|
|
|
if (props.elevated === true && showing.value === true) {
|
|
content.push(
|
|
vue.h('div', {
|
|
class: 'q-layout__shadow absolute-full overflow-hidden no-pointer-events'
|
|
})
|
|
);
|
|
}
|
|
|
|
child.push(
|
|
hDir(
|
|
'aside',
|
|
{ ref: 'content', class: classes.value, style: style.value },
|
|
content,
|
|
'contentclose',
|
|
props.noSwipeClose !== true && belowBreakpoint.value === true,
|
|
() => contentCloseDirective.value
|
|
)
|
|
);
|
|
|
|
return vue.h('div', { class: 'q-drawer-container' }, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
function getBlockElement (el, parent) {
|
|
if (parent && el === parent) {
|
|
return null
|
|
}
|
|
|
|
const nodeName = el.nodeName.toLowerCase();
|
|
|
|
if ([ 'div', 'li', 'ul', 'ol', 'blockquote' ].includes(nodeName) === true) {
|
|
return el
|
|
}
|
|
|
|
const
|
|
style = window.getComputedStyle
|
|
? window.getComputedStyle(el)
|
|
: el.currentStyle,
|
|
display = style.display;
|
|
|
|
if (display === 'block' || display === 'table') {
|
|
return el
|
|
}
|
|
|
|
return getBlockElement(el.parentNode)
|
|
}
|
|
|
|
function isChildOf (el, parent, orSame) {
|
|
return !el || el === document.body
|
|
? false
|
|
: (orSame === true && el === parent) || (parent === document ? document.body : parent).contains(el.parentNode)
|
|
}
|
|
|
|
function createRange (node, chars, range) {
|
|
if (!range) {
|
|
range = document.createRange();
|
|
range.selectNode(node);
|
|
range.setStart(node, 0);
|
|
}
|
|
|
|
if (chars.count === 0) {
|
|
range.setEnd(node, chars.count);
|
|
}
|
|
else if (chars.count > 0) {
|
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
if (node.textContent.length < chars.count) {
|
|
chars.count -= node.textContent.length;
|
|
}
|
|
else {
|
|
range.setEnd(node, chars.count);
|
|
chars.count = 0;
|
|
}
|
|
}
|
|
else {
|
|
for (let lp = 0; chars.count !== 0 && lp < node.childNodes.length; lp++) {
|
|
range = createRange(node.childNodes[ lp ], chars, range);
|
|
}
|
|
}
|
|
}
|
|
|
|
return range
|
|
}
|
|
|
|
const urlRegex = /^https?:\/\//;
|
|
|
|
class Caret {
|
|
constructor (el, eVm) {
|
|
this.el = el;
|
|
this.eVm = eVm;
|
|
this._range = null;
|
|
}
|
|
|
|
get selection () {
|
|
if (this.el) {
|
|
const sel = document.getSelection();
|
|
|
|
// only when the selection in element
|
|
if (isChildOf(sel.anchorNode, this.el, true) && isChildOf(sel.focusNode, this.el, true)) {
|
|
return sel
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
get hasSelection () {
|
|
return this.selection !== null
|
|
? this.selection.toString().length !== 0
|
|
: false
|
|
}
|
|
|
|
get range () {
|
|
const sel = this.selection;
|
|
|
|
if (sel !== null && sel.rangeCount) {
|
|
return sel.getRangeAt(0)
|
|
}
|
|
|
|
return this._range
|
|
}
|
|
|
|
get parent () {
|
|
const range = this.range;
|
|
|
|
if (range !== null) {
|
|
const node = range.startContainer;
|
|
|
|
return node.nodeType === document.ELEMENT_NODE
|
|
? node
|
|
: node.parentNode
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
get blockParent () {
|
|
const parent = this.parent;
|
|
|
|
if (parent !== null) {
|
|
return getBlockElement(parent, this.el)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
save (range = this.range) {
|
|
if (range !== null) {
|
|
this._range = range;
|
|
}
|
|
}
|
|
|
|
restore (range = this._range) {
|
|
const
|
|
r = document.createRange(),
|
|
sel = document.getSelection();
|
|
|
|
if (range !== null) {
|
|
r.setStart(range.startContainer, range.startOffset);
|
|
r.setEnd(range.endContainer, range.endOffset);
|
|
sel.removeAllRanges();
|
|
sel.addRange(r);
|
|
}
|
|
else {
|
|
sel.selectAllChildren(this.el);
|
|
sel.collapseToEnd();
|
|
}
|
|
}
|
|
|
|
savePosition () {
|
|
let charCount = -1, node;
|
|
const
|
|
selection = document.getSelection(),
|
|
parentEl = this.el.parentNode;
|
|
|
|
if (selection.focusNode && isChildOf(selection.focusNode, parentEl)) {
|
|
node = selection.focusNode;
|
|
charCount = selection.focusOffset;
|
|
|
|
while (node && node !== parentEl) {
|
|
if (node !== this.el && node.previousSibling) {
|
|
node = node.previousSibling;
|
|
charCount += node.textContent.length;
|
|
}
|
|
else {
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.savedPos = charCount;
|
|
}
|
|
|
|
restorePosition (length = 0) {
|
|
if (this.savedPos > 0 && this.savedPos < length) {
|
|
const
|
|
selection = window.getSelection(),
|
|
range = createRange(this.el, { count: this.savedPos });
|
|
|
|
if (range) {
|
|
range.collapse(false);
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
}
|
|
}
|
|
}
|
|
|
|
hasParent (name, spanLevel) {
|
|
const el = spanLevel
|
|
? this.parent
|
|
: this.blockParent;
|
|
|
|
return el !== null
|
|
? el.nodeName.toLowerCase() === name.toLowerCase()
|
|
: false
|
|
}
|
|
|
|
hasParents (list, recursive, el = this.parent) {
|
|
if (el === null) {
|
|
return false
|
|
}
|
|
|
|
if (list.includes(el.nodeName.toLowerCase()) === true) {
|
|
return true
|
|
}
|
|
|
|
return recursive === true
|
|
? this.hasParents(list, recursive, el.parentNode)
|
|
: false
|
|
}
|
|
|
|
is (cmd, param) {
|
|
if (this.selection === null) {
|
|
return false
|
|
}
|
|
|
|
switch (cmd) {
|
|
case 'formatBlock':
|
|
return (param === 'DIV' && this.parent === this.el)
|
|
|| this.hasParent(param, param === 'PRE')
|
|
case 'link':
|
|
return this.hasParent('A', true)
|
|
case 'fontSize':
|
|
return document.queryCommandValue(cmd) === param
|
|
case 'fontName':
|
|
const res = document.queryCommandValue(cmd);
|
|
return res === `"${ param }"` || res === param
|
|
case 'fullscreen':
|
|
return this.eVm.inFullscreen.value
|
|
case 'viewsource':
|
|
return this.eVm.isViewingSource.value
|
|
case void 0:
|
|
return false
|
|
default:
|
|
const state = document.queryCommandState(cmd);
|
|
return param !== void 0 ? state === param : state
|
|
}
|
|
}
|
|
|
|
getParentAttribute (attrib) {
|
|
if (this.parent !== null) {
|
|
return this.parent.getAttribute(attrib)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
can (name) {
|
|
if (name === 'outdent') {
|
|
return this.hasParents([ 'blockquote', 'li' ], true)
|
|
}
|
|
|
|
if (name === 'indent') {
|
|
return this.hasParents([ 'li' ], true)
|
|
}
|
|
|
|
if (name === 'link') {
|
|
return this.selection !== null || this.is('link')
|
|
}
|
|
}
|
|
|
|
apply (cmd, param, done = noop) {
|
|
if (cmd === 'formatBlock') {
|
|
if ([ 'BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].includes(param) && this.is(cmd, param)) {
|
|
cmd = 'outdent';
|
|
param = null;
|
|
}
|
|
|
|
if (param === 'PRE' && this.is(cmd, 'PRE')) {
|
|
param = 'P';
|
|
}
|
|
}
|
|
else if (cmd === 'print') {
|
|
done();
|
|
|
|
const win = window.open();
|
|
|
|
win.document.write(`
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>Print - ${ document.title }</title>
|
|
</head>
|
|
<body>
|
|
<div>${ this.el.innerHTML }</div>
|
|
</body>
|
|
</html>
|
|
`);
|
|
win.print();
|
|
win.close();
|
|
|
|
return
|
|
}
|
|
else if (cmd === 'link') {
|
|
const link = this.getParentAttribute('href');
|
|
|
|
if (link === null) {
|
|
const selection = this.selectWord(this.selection);
|
|
const url = selection ? selection.toString() : '';
|
|
|
|
if (!url.length) {
|
|
if (!this.range || !this.range.cloneContents().querySelector('img')) {
|
|
return
|
|
}
|
|
}
|
|
|
|
this.eVm.editLinkUrl.value = urlRegex.test(url) ? url : 'https://';
|
|
document.execCommand('createLink', false, this.eVm.editLinkUrl.value);
|
|
|
|
this.save(selection.getRangeAt(0));
|
|
}
|
|
else {
|
|
this.eVm.editLinkUrl.value = link;
|
|
|
|
this.range.selectNodeContents(this.parent);
|
|
this.save();
|
|
}
|
|
|
|
return
|
|
}
|
|
else if (cmd === 'fullscreen') {
|
|
this.eVm.toggleFullscreen();
|
|
done();
|
|
|
|
return
|
|
}
|
|
else if (cmd === 'viewsource') {
|
|
this.eVm.isViewingSource.value = this.eVm.isViewingSource.value === false;
|
|
this.eVm.setContent(this.eVm.props.modelValue);
|
|
done();
|
|
|
|
return
|
|
}
|
|
|
|
document.execCommand(cmd, false, param);
|
|
|
|
done();
|
|
}
|
|
|
|
selectWord (sel) {
|
|
if (sel === null || sel.isCollapsed !== true || /* IE 11 */ sel.modify === void 0) {
|
|
return sel
|
|
}
|
|
|
|
// Detect if selection is backwards
|
|
const range = document.createRange();
|
|
range.setStart(sel.anchorNode, sel.anchorOffset);
|
|
range.setEnd(sel.focusNode, sel.focusOffset);
|
|
const direction = range.collapsed ? [ 'backward', 'forward' ] : [ 'forward', 'backward' ];
|
|
range.detach();
|
|
|
|
// modify() works on the focus of the selection
|
|
const
|
|
endNode = sel.focusNode,
|
|
endOffset = sel.focusOffset;
|
|
sel.collapse(sel.anchorNode, sel.anchorOffset);
|
|
sel.modify('move', direction[ 0 ], 'character');
|
|
sel.modify('move', direction[ 1 ], 'word');
|
|
sel.extend(endNode, endOffset);
|
|
sel.modify('extend', direction[ 1 ], 'character');
|
|
sel.modify('extend', direction[ 0 ], 'word');
|
|
|
|
return sel
|
|
}
|
|
}
|
|
|
|
var QTooltip = createComponent({
|
|
name: 'QTooltip',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useAnchorProps,
|
|
...useModelToggleProps,
|
|
...useTransitionProps,
|
|
|
|
maxHeight: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
maxWidth: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
|
|
transitionShow: {
|
|
default: 'jump-down'
|
|
},
|
|
transitionHide: {
|
|
default: 'jump-up'
|
|
},
|
|
|
|
anchor: {
|
|
type: String,
|
|
default: 'bottom middle',
|
|
validator: validatePosition
|
|
},
|
|
self: {
|
|
type: String,
|
|
default: 'top middle',
|
|
validator: validatePosition
|
|
},
|
|
offset: {
|
|
type: Array,
|
|
default: () => [ 14, 14 ],
|
|
validator: validateOffset
|
|
},
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
},
|
|
|
|
delay: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
|
|
hideDelay: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
},
|
|
|
|
emits: [
|
|
...useModelToggleEmits
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
let unwatchPosition, observer;
|
|
|
|
const vm = vue.getCurrentInstance();
|
|
const { proxy: { $q } } = vm;
|
|
|
|
const innerRef = vue.ref(null);
|
|
const showing = vue.ref(false);
|
|
|
|
const anchorOrigin = vue.computed(() => parsePosition(props.anchor, $q.lang.rtl));
|
|
const selfOrigin = vue.computed(() => parsePosition(props.self, $q.lang.rtl));
|
|
const hideOnRouteChange = vue.computed(() => props.persistent !== true);
|
|
|
|
const { registerTick, removeTick } = useTick();
|
|
const { registerTimeout } = useTimeout();
|
|
const { transitionProps, transitionStyle } = useTransition(props);
|
|
const { localScrollTarget, changeScrollEvent, unconfigureScrollTarget } = useScrollTarget(props, configureScrollTarget);
|
|
|
|
const { anchorEl, canShow, anchorEvents } = useAnchor({ showing, configureAnchorEl });
|
|
|
|
const { show, hide } = useModelToggle({
|
|
showing, canShow, handleShow, handleHide,
|
|
hideOnRouteChange,
|
|
processOnMount: true
|
|
});
|
|
|
|
Object.assign(anchorEvents, { delayShow, delayHide });
|
|
|
|
const { showPortal, hidePortal, renderPortal } = usePortal(vm, innerRef, renderPortalContent, 'tooltip');
|
|
|
|
// if we're on mobile, let's improve the experience
|
|
// by closing it when user taps outside of it
|
|
if ($q.platform.is.mobile === true) {
|
|
const clickOutsideProps = {
|
|
anchorEl,
|
|
innerRef,
|
|
onClickOutside (e) {
|
|
hide(e);
|
|
|
|
// prevent click if it's on a dialog backdrop
|
|
if (e.target.classList.contains('q-dialog__backdrop')) {
|
|
stopAndPrevent(e);
|
|
}
|
|
|
|
return true
|
|
}
|
|
};
|
|
|
|
const hasClickOutside = vue.computed(() =>
|
|
// it doesn't has external model
|
|
// (null is the default value)
|
|
props.modelValue === null
|
|
// and it's not persistent
|
|
&& props.persistent !== true
|
|
&& showing.value === true
|
|
);
|
|
|
|
vue.watch(hasClickOutside, val => {
|
|
const fn = val === true ? addClickOutside : removeClickOutside;
|
|
fn(clickOutsideProps);
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
removeClickOutside(clickOutsideProps);
|
|
});
|
|
}
|
|
|
|
function handleShow (evt) {
|
|
showPortal();
|
|
|
|
// should removeTick() if this gets removed
|
|
registerTick(() => {
|
|
observer = new MutationObserver(() => updatePosition());
|
|
observer.observe(innerRef.value, { attributes: false, childList: true, characterData: true, subtree: true });
|
|
updatePosition();
|
|
configureScrollTarget();
|
|
});
|
|
|
|
if (unwatchPosition === void 0) {
|
|
unwatchPosition = vue.watch(
|
|
() => $q.screen.width + '|' + $q.screen.height + '|' + props.self + '|' + props.anchor + '|' + $q.lang.rtl,
|
|
updatePosition
|
|
);
|
|
}
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
showPortal(true); // done showing portal
|
|
emit('show', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function handleHide (evt) {
|
|
removeTick();
|
|
hidePortal();
|
|
|
|
anchorCleanup();
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => {
|
|
hidePortal(true); // done hiding, now destroy
|
|
emit('hide', evt);
|
|
}, props.transitionDuration);
|
|
}
|
|
|
|
function anchorCleanup () {
|
|
if (observer !== void 0) {
|
|
observer.disconnect();
|
|
observer = void 0;
|
|
}
|
|
|
|
if (unwatchPosition !== void 0) {
|
|
unwatchPosition();
|
|
unwatchPosition = void 0;
|
|
}
|
|
|
|
unconfigureScrollTarget();
|
|
cleanEvt(anchorEvents, 'tooltipTemp');
|
|
}
|
|
|
|
function updatePosition () {
|
|
setPosition({
|
|
targetEl: innerRef.value,
|
|
offset: props.offset,
|
|
anchorEl: anchorEl.value,
|
|
anchorOrigin: anchorOrigin.value,
|
|
selfOrigin: selfOrigin.value,
|
|
maxHeight: props.maxHeight,
|
|
maxWidth: props.maxWidth
|
|
});
|
|
}
|
|
|
|
function delayShow (evt) {
|
|
if ($q.platform.is.mobile === true) {
|
|
clearSelection();
|
|
document.body.classList.add('non-selectable');
|
|
|
|
const target = anchorEl.value;
|
|
const evts = [ 'touchmove', 'touchcancel', 'touchend', 'click' ]
|
|
.map(e => ([ target, e, 'delayHide', 'passiveCapture' ]));
|
|
|
|
addEvt(anchorEvents, 'tooltipTemp', evts);
|
|
}
|
|
|
|
registerTimeout(() => { show(evt); }, props.delay);
|
|
}
|
|
|
|
function delayHide (evt) {
|
|
if ($q.platform.is.mobile === true) {
|
|
cleanEvt(anchorEvents, 'tooltipTemp');
|
|
clearSelection();
|
|
// delay needed otherwise selection still occurs
|
|
setTimeout(() => {
|
|
document.body.classList.remove('non-selectable');
|
|
}, 10);
|
|
}
|
|
|
|
// should removeTimeout() if this gets removed
|
|
registerTimeout(() => { hide(evt); }, props.hideDelay);
|
|
}
|
|
|
|
function configureAnchorEl () {
|
|
if (props.noParentEvent === true || anchorEl.value === null) { return }
|
|
|
|
const evts = $q.platform.is.mobile === true
|
|
? [
|
|
[ anchorEl.value, 'touchstart', 'delayShow', 'passive' ]
|
|
]
|
|
: [
|
|
[ anchorEl.value, 'mouseenter', 'delayShow', 'passive' ],
|
|
[ anchorEl.value, 'mouseleave', 'delayHide', 'passive' ]
|
|
];
|
|
|
|
addEvt(anchorEvents, 'anchor', evts);
|
|
}
|
|
|
|
function configureScrollTarget () {
|
|
if (anchorEl.value !== null || props.scrollTarget !== void 0) {
|
|
localScrollTarget.value = getScrollTarget(anchorEl.value, props.scrollTarget);
|
|
const fn = props.noParentEvent === true
|
|
? updatePosition
|
|
: hide;
|
|
|
|
changeScrollEvent(localScrollTarget.value, fn);
|
|
}
|
|
}
|
|
|
|
function getTooltipContent () {
|
|
return showing.value === true
|
|
? vue.h('div', {
|
|
...attrs,
|
|
ref: innerRef,
|
|
class: [
|
|
'q-tooltip q-tooltip--style q-position-engine no-pointer-events',
|
|
attrs.class
|
|
],
|
|
style: [
|
|
attrs.style,
|
|
transitionStyle.value
|
|
],
|
|
role: 'tooltip'
|
|
}, hSlot(slots.default))
|
|
: null
|
|
}
|
|
|
|
function renderPortalContent () {
|
|
return vue.h(vue.Transition, transitionProps.value, getTooltipContent)
|
|
}
|
|
|
|
vue.onBeforeUnmount(anchorCleanup);
|
|
|
|
// expose public methods
|
|
Object.assign(vm.proxy, { updatePosition });
|
|
|
|
return renderPortal
|
|
}
|
|
});
|
|
|
|
var QItem = createComponent({
|
|
name: 'QItem',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useRouterLinkProps,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
active: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
clickable: Boolean,
|
|
dense: Boolean,
|
|
insetLevel: Number,
|
|
|
|
tabindex: [ String, Number ],
|
|
|
|
focused: Boolean,
|
|
manualFocus: Boolean
|
|
},
|
|
|
|
emits: [ 'click', 'keyup' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { hasLink, linkAttrs, linkClass, linkTag, navigateOnClick } = useRouterLink();
|
|
|
|
const rootRef = vue.ref(null);
|
|
const blurTargetRef = vue.ref(null);
|
|
|
|
const isActionable = vue.computed(() =>
|
|
props.clickable === true
|
|
|| hasLink.value === true
|
|
|| props.tag === 'label'
|
|
);
|
|
|
|
const isClickable = vue.computed(() =>
|
|
props.disable !== true && isActionable.value === true
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-item q-item-type row no-wrap'
|
|
+ (props.dense === true ? ' q-item--dense' : '')
|
|
+ (isDark.value === true ? ' q-item--dark' : '')
|
|
+ (
|
|
hasLink.value === true && props.active === null
|
|
? linkClass.value
|
|
: (
|
|
props.active === true
|
|
? ` q-item--active${ props.activeClass !== void 0 ? ` ${ props.activeClass }` : '' }`
|
|
: ''
|
|
)
|
|
)
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (
|
|
isClickable.value === true
|
|
? ' q-item--clickable q-link cursor-pointer '
|
|
+ (props.manualFocus === true ? 'q-manual-focusable' : 'q-focusable q-hoverable')
|
|
+ (props.focused === true ? ' q-manual-focusable--focused' : '')
|
|
: ''
|
|
)
|
|
);
|
|
|
|
const style = vue.computed(() => {
|
|
if (props.insetLevel === void 0) {
|
|
return null
|
|
}
|
|
|
|
const dir = $q.lang.rtl === true ? 'Right' : 'Left';
|
|
return {
|
|
[ 'padding' + dir ]: (16 + props.insetLevel * 56) + 'px'
|
|
}
|
|
});
|
|
|
|
function onClick (e) {
|
|
if (isClickable.value === true) {
|
|
if (blurTargetRef.value !== null) {
|
|
if (e.qKeyEvent !== true && document.activeElement === rootRef.value) {
|
|
blurTargetRef.value.focus();
|
|
}
|
|
else if (document.activeElement === blurTargetRef.value) {
|
|
rootRef.value.focus();
|
|
}
|
|
}
|
|
|
|
navigateOnClick(e);
|
|
}
|
|
}
|
|
|
|
function onKeyup (e) {
|
|
if (isClickable.value === true && isKeyCode(e, 13) === true) {
|
|
stopAndPrevent(e);
|
|
|
|
// for ripple
|
|
e.qKeyEvent = true;
|
|
|
|
// for click trigger
|
|
const evt = new MouseEvent('click', e);
|
|
evt.qKeyEvent = true;
|
|
rootRef.value.dispatchEvent(evt);
|
|
}
|
|
|
|
emit('keyup', e);
|
|
}
|
|
|
|
function getContent () {
|
|
const child = hUniqueSlot(slots.default, []);
|
|
|
|
isClickable.value === true && child.unshift(
|
|
vue.h('div', { class: 'q-focus-helper', tabindex: -1, ref: blurTargetRef })
|
|
);
|
|
|
|
return child
|
|
}
|
|
|
|
return () => {
|
|
const data = {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
style: style.value,
|
|
role: 'listitem',
|
|
onClick,
|
|
onKeyup
|
|
};
|
|
|
|
if (isClickable.value === true) {
|
|
data.tabindex = props.tabindex || '0';
|
|
Object.assign(data, linkAttrs.value);
|
|
}
|
|
else if (isActionable.value === true) {
|
|
data[ 'aria-disabled' ] = 'true';
|
|
}
|
|
|
|
return vue.h(
|
|
linkTag.value,
|
|
data,
|
|
getContent()
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QItemSection = createComponent({
|
|
name: 'QItemSection',
|
|
|
|
props: {
|
|
avatar: Boolean,
|
|
thumbnail: Boolean,
|
|
side: Boolean,
|
|
top: Boolean,
|
|
noWrap: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() =>
|
|
'q-item__section column'
|
|
+ ` q-item__section--${ props.avatar === true || props.side === true || props.thumbnail === true ? 'side' : 'main' }`
|
|
+ (props.top === true ? ' q-item__section--top justify-start' : ' justify-center')
|
|
+ (props.avatar === true ? ' q-item__section--avatar' : '')
|
|
+ (props.thumbnail === true ? ' q-item__section--thumbnail' : '')
|
|
+ (props.noWrap === true ? ' q-item__section--nowrap' : '')
|
|
);
|
|
|
|
return () => vue.h('div', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
function run (e, btn, eVm) {
|
|
if (btn.handler) {
|
|
btn.handler(e, eVm, eVm.caret);
|
|
}
|
|
else {
|
|
eVm.runCmd(btn.cmd, btn.param);
|
|
}
|
|
}
|
|
|
|
function getGroup (children) {
|
|
return vue.h('div', { class: 'q-editor__toolbar-group' }, children)
|
|
}
|
|
|
|
function getBtn (eVm, btn, clickHandler, active = false) {
|
|
const
|
|
toggled = active || (btn.type === 'toggle'
|
|
? (btn.toggled ? btn.toggled(eVm) : btn.cmd && eVm.caret.is(btn.cmd, btn.param))
|
|
: false),
|
|
child = [];
|
|
|
|
if (btn.tip && eVm.$q.platform.is.desktop) {
|
|
const Key = btn.key
|
|
? vue.h('div', [
|
|
vue.h('small', `(CTRL + ${ String.fromCharCode(btn.key) })`)
|
|
])
|
|
: null;
|
|
child.push(
|
|
vue.h(QTooltip, { delay: 1000 }, () => [
|
|
vue.h('div', { innerHTML: btn.tip }),
|
|
Key
|
|
])
|
|
);
|
|
}
|
|
|
|
return vue.h(QBtn, {
|
|
...eVm.buttonProps.value,
|
|
icon: btn.icon !== null ? btn.icon : void 0,
|
|
color: toggled ? btn.toggleColor || eVm.props.toolbarToggleColor : btn.color || eVm.props.toolbarColor,
|
|
textColor: toggled && !eVm.props.toolbarPush ? null : btn.textColor || eVm.props.toolbarTextColor,
|
|
label: btn.label,
|
|
disable: btn.disable ? (typeof btn.disable === 'function' ? btn.disable(eVm) : true) : false,
|
|
size: 'sm',
|
|
onClick (e) {
|
|
clickHandler && clickHandler();
|
|
run(e, btn, eVm);
|
|
}
|
|
}, () => child)
|
|
}
|
|
|
|
function getDropdown (eVm, btn) {
|
|
const onlyIcons = btn.list === 'only-icons';
|
|
let
|
|
label = btn.label,
|
|
icon = btn.icon !== null ? btn.icon : void 0,
|
|
contentClass,
|
|
Items;
|
|
|
|
function closeDropdown () {
|
|
Dropdown.component.proxy.hide();
|
|
}
|
|
|
|
if (onlyIcons) {
|
|
Items = btn.options.map(btn => {
|
|
const active = btn.type === void 0
|
|
? eVm.caret.is(btn.cmd, btn.param)
|
|
: false;
|
|
|
|
if (active) {
|
|
label = btn.tip;
|
|
icon = btn.icon !== null ? btn.icon : void 0;
|
|
}
|
|
return getBtn(eVm, btn, closeDropdown, active)
|
|
});
|
|
contentClass = eVm.toolbarBackgroundClass.value;
|
|
Items = [
|
|
getGroup(Items)
|
|
];
|
|
}
|
|
else {
|
|
const activeClass = eVm.props.toolbarToggleColor !== void 0
|
|
? `text-${ eVm.props.toolbarToggleColor }`
|
|
: null;
|
|
const inactiveClass = eVm.props.toolbarTextColor !== void 0
|
|
? `text-${ eVm.props.toolbarTextColor }`
|
|
: null;
|
|
|
|
const noIcons = btn.list === 'no-icons';
|
|
|
|
Items = btn.options.map(btn => {
|
|
const disable = btn.disable ? btn.disable(eVm) : false;
|
|
const active = btn.type === void 0
|
|
? eVm.caret.is(btn.cmd, btn.param)
|
|
: false;
|
|
|
|
if (active) {
|
|
label = btn.tip;
|
|
icon = btn.icon !== null ? btn.icon : void 0;
|
|
}
|
|
|
|
const htmlTip = btn.htmlTip;
|
|
|
|
return vue.h(QItem, {
|
|
active,
|
|
activeClass,
|
|
clickable: true,
|
|
disable,
|
|
dense: true,
|
|
onClick (e) {
|
|
closeDropdown();
|
|
eVm.contentRef.value !== null && eVm.contentRef.value.focus();
|
|
eVm.caret.restore();
|
|
run(e, btn, eVm);
|
|
}
|
|
}, () => [
|
|
noIcons === true
|
|
? null
|
|
: vue.h(
|
|
QItemSection,
|
|
{
|
|
class: active ? activeClass : inactiveClass,
|
|
side: true
|
|
},
|
|
() => vue.h(QIcon, { name: btn.icon !== null ? btn.icon : void 0 })
|
|
),
|
|
|
|
vue.h(
|
|
QItemSection,
|
|
htmlTip
|
|
? () => vue.h('div', { class: 'text-no-wrap', innerHTML: btn.htmlTip })
|
|
: (btn.tip ? () => vue.h('div', { class: 'text-no-wrap' }, btn.tip) : void 0)
|
|
)
|
|
])
|
|
});
|
|
contentClass = [ eVm.toolbarBackgroundClass.value, inactiveClass ];
|
|
}
|
|
|
|
const highlight = btn.highlight && label !== btn.label;
|
|
const Dropdown = vue.h(QBtnDropdown, {
|
|
...eVm.buttonProps.value,
|
|
noCaps: true,
|
|
noWrap: true,
|
|
color: highlight ? eVm.props.toolbarToggleColor : eVm.props.toolbarColor,
|
|
textColor: highlight && !eVm.props.toolbarPush ? null : eVm.props.toolbarTextColor,
|
|
label: btn.fixedLabel ? btn.label : label,
|
|
icon: btn.fixedIcon ? (btn.icon !== null ? btn.icon : void 0) : icon,
|
|
contentClass,
|
|
onShow: evt => eVm.emit('dropdownShow', evt),
|
|
onHide: evt => eVm.emit('dropdownHide', evt),
|
|
onBeforeShow: evt => eVm.emit('dropdownBeforeShow', evt),
|
|
onBeforeHide: evt => eVm.emit('dropdownBeforeHide', evt)
|
|
}, () => Items);
|
|
|
|
return Dropdown
|
|
}
|
|
|
|
function getToolbar (eVm) {
|
|
if (eVm.caret) {
|
|
return eVm.buttons.value
|
|
.filter(f => {
|
|
return !eVm.isViewingSource.value || f.find(fb => fb.cmd === 'viewsource')
|
|
})
|
|
.map(group => getGroup(
|
|
group.map(btn => {
|
|
if (eVm.isViewingSource.value && btn.cmd !== 'viewsource') {
|
|
return false
|
|
}
|
|
|
|
if (btn.type === 'slot') {
|
|
return hSlot(eVm.slots[ btn.slot ])
|
|
}
|
|
|
|
if (btn.type === 'dropdown') {
|
|
return getDropdown(eVm, btn)
|
|
}
|
|
|
|
return getBtn(eVm, btn)
|
|
})
|
|
))
|
|
}
|
|
}
|
|
|
|
function getFonts (defaultFont, defaultFontLabel, defaultFontIcon, fonts = {}) {
|
|
const aliases = Object.keys(fonts);
|
|
if (aliases.length === 0) {
|
|
return {}
|
|
}
|
|
|
|
const def = {
|
|
default_font: {
|
|
cmd: 'fontName',
|
|
param: defaultFont,
|
|
icon: defaultFontIcon,
|
|
tip: defaultFontLabel
|
|
}
|
|
};
|
|
|
|
aliases.forEach(alias => {
|
|
const name = fonts[ alias ];
|
|
def[ alias ] = {
|
|
cmd: 'fontName',
|
|
param: name,
|
|
icon: defaultFontIcon,
|
|
tip: name,
|
|
htmlTip: `<font face="${ name }">${ name }</font>`
|
|
};
|
|
});
|
|
|
|
return def
|
|
}
|
|
|
|
function getLinkEditor (eVm) {
|
|
if (eVm.caret) {
|
|
const color = eVm.props.toolbarColor || eVm.props.toolbarTextColor;
|
|
let link = eVm.editLinkUrl.value;
|
|
const updateLink = () => {
|
|
eVm.caret.restore();
|
|
|
|
if (link !== eVm.editLinkUrl.value) {
|
|
document.execCommand('createLink', false, link === '' ? ' ' : link);
|
|
}
|
|
|
|
eVm.editLinkUrl.value = null;
|
|
};
|
|
|
|
return [
|
|
vue.h('div', { class: `q-mx-xs text-${ color }` }, `${ eVm.$q.lang.editor.url }: `),
|
|
vue.h('input', {
|
|
key: 'qedt_btm_input',
|
|
class: 'col q-editor__link-input',
|
|
value: link,
|
|
onInput: evt => {
|
|
stop(evt);
|
|
link = evt.target.value;
|
|
},
|
|
onKeydown: evt => {
|
|
if (shouldIgnoreKey(evt) === true) {
|
|
return
|
|
}
|
|
|
|
switch (evt.keyCode) {
|
|
case 13: // ENTER key
|
|
prevent(evt);
|
|
return updateLink()
|
|
case 27: // ESCAPE key
|
|
prevent(evt);
|
|
eVm.caret.restore();
|
|
if (!eVm.editLinkUrl.value || eVm.editLinkUrl.value === 'https://') {
|
|
document.execCommand('unlink');
|
|
}
|
|
eVm.editLinkUrl.value = null;
|
|
break
|
|
}
|
|
}
|
|
}),
|
|
getGroup([
|
|
vue.h(QBtn, {
|
|
key: 'qedt_btm_rem',
|
|
tabindex: -1,
|
|
...eVm.buttonProps.value,
|
|
label: eVm.$q.lang.label.remove,
|
|
noCaps: true,
|
|
onClick: () => {
|
|
eVm.caret.restore();
|
|
document.execCommand('unlink');
|
|
eVm.editLinkUrl.value = null;
|
|
}
|
|
}),
|
|
vue.h(QBtn, {
|
|
key: 'qedt_btm_upd',
|
|
...eVm.buttonProps.value,
|
|
label: eVm.$q.lang.label.update,
|
|
noCaps: true,
|
|
onClick: updateLink
|
|
})
|
|
])
|
|
]
|
|
}
|
|
}
|
|
|
|
const listenerRE = /^on[A-Z]/;
|
|
|
|
function useSplitAttrs (attrs, vnode) {
|
|
const acc = {
|
|
listeners: vue.ref({}),
|
|
attributes: vue.ref({})
|
|
};
|
|
|
|
function update () {
|
|
const attributes = {};
|
|
const listeners = {};
|
|
|
|
for (const key in attrs) {
|
|
if (key !== 'class' && key !== 'style' && listenerRE.test(key) === false) {
|
|
attributes[ key ] = attrs[ key ];
|
|
}
|
|
}
|
|
|
|
for (const key in vnode.props) {
|
|
if (listenerRE.test(key) === true) {
|
|
listeners[ key ] = vnode.props[ key ];
|
|
}
|
|
}
|
|
|
|
acc.attributes.value = attributes;
|
|
acc.listeners.value = listeners;
|
|
}
|
|
|
|
vue.onBeforeUpdate(update);
|
|
|
|
update();
|
|
|
|
return acc
|
|
}
|
|
|
|
const
|
|
toString = Object.prototype.toString,
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
notPlainObject = new Set(
|
|
[ 'Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp' ]
|
|
.map(name => '[object ' + name + ']')
|
|
);
|
|
|
|
function isPlainObject (obj) {
|
|
if (obj !== Object(obj) || notPlainObject.has(toString.call(obj)) === true) {
|
|
return false
|
|
}
|
|
|
|
if (
|
|
obj.constructor
|
|
&& hasOwn.call(obj, 'constructor') === false
|
|
&& hasOwn.call(obj.constructor.prototype, 'isPrototypeOf') === false
|
|
) {
|
|
return false
|
|
}
|
|
|
|
let key;
|
|
for (key in obj) {} // eslint-disable-line
|
|
|
|
return key === void 0 || hasOwn.call(obj, key)
|
|
}
|
|
|
|
function extend () {
|
|
let
|
|
options, name, src, copy, copyIsArray, clone,
|
|
target = arguments[ 0 ] || {},
|
|
i = 1,
|
|
deep = false;
|
|
const length = arguments.length;
|
|
|
|
if (typeof target === 'boolean') {
|
|
deep = target;
|
|
target = arguments[ 1 ] || {};
|
|
i = 2;
|
|
}
|
|
|
|
if (Object(target) !== target && typeof target !== 'function') {
|
|
target = {};
|
|
}
|
|
|
|
if (length === i) {
|
|
target = this;
|
|
i--;
|
|
}
|
|
|
|
for (; i < length; i++) {
|
|
if ((options = arguments[ i ]) !== null) {
|
|
for (name in options) {
|
|
src = target[ name ];
|
|
copy = options[ name ];
|
|
|
|
if (target === copy) {
|
|
continue
|
|
}
|
|
|
|
if (
|
|
deep === true
|
|
&& copy
|
|
&& ((copyIsArray = Array.isArray(copy)) || isPlainObject(copy) === true)
|
|
) {
|
|
if (copyIsArray === true) {
|
|
clone = Array.isArray(src) === true ? src : [];
|
|
}
|
|
else {
|
|
clone = isPlainObject(src) === true ? src : {};
|
|
}
|
|
|
|
target[ name ] = extend(deep, clone, copy);
|
|
}
|
|
else if (copy !== void 0) {
|
|
target[ name ] = copy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return target
|
|
}
|
|
|
|
var QEditor = createComponent({
|
|
name: 'QEditor',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useFullscreenProps,
|
|
|
|
modelValue: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
readonly: Boolean,
|
|
disable: Boolean,
|
|
minHeight: {
|
|
type: String,
|
|
default: '10rem'
|
|
},
|
|
maxHeight: String,
|
|
height: String,
|
|
definitions: Object,
|
|
fonts: Object,
|
|
placeholder: String,
|
|
|
|
toolbar: {
|
|
type: Array,
|
|
validator: v => v.length === 0 || v.every(group => group.length),
|
|
default () {
|
|
return [
|
|
[ 'left', 'center', 'right', 'justify' ],
|
|
[ 'bold', 'italic', 'underline', 'strike' ],
|
|
[ 'undo', 'redo' ]
|
|
]
|
|
}
|
|
},
|
|
toolbarColor: String,
|
|
toolbarBg: String,
|
|
toolbarTextColor: String,
|
|
toolbarToggleColor: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
toolbarOutline: Boolean,
|
|
toolbarPush: Boolean,
|
|
toolbarRounded: Boolean,
|
|
|
|
paragraphTag: {
|
|
type: String,
|
|
validator: v => [ 'div', 'p' ].includes(v),
|
|
default: 'div'
|
|
},
|
|
|
|
contentStyle: Object,
|
|
contentClass: [ Object, Array, String ],
|
|
|
|
square: Boolean,
|
|
flat: Boolean,
|
|
dense: Boolean
|
|
},
|
|
|
|
emits: [
|
|
...useFullscreenEmits,
|
|
'update:modelValue',
|
|
'keydown', 'click', 'mouseup', 'keyup', 'touchend',
|
|
'focus', 'blur',
|
|
'dropdownShow',
|
|
'dropdownHide',
|
|
'dropdownBeforeShow',
|
|
'dropdownBeforeHide',
|
|
'linkShow',
|
|
'linkHide'
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
const { proxy, vnode } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { inFullscreen, toggleFullscreen } = useFullscreen();
|
|
const splitAttrs = useSplitAttrs(attrs, vnode);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const contentRef = vue.ref(null);
|
|
|
|
const editLinkUrl = vue.ref(null);
|
|
const isViewingSource = vue.ref(false);
|
|
|
|
const editable = vue.computed(() => !props.readonly && !props.disable);
|
|
|
|
let defaultFont, offsetBottom;
|
|
let lastEmit = props.modelValue;
|
|
|
|
{
|
|
document.execCommand('defaultParagraphSeparator', false, props.paragraphTag);
|
|
defaultFont = window.getComputedStyle(document.body).fontFamily;
|
|
}
|
|
|
|
const toolbarBackgroundClass = vue.computed(() => (
|
|
props.toolbarBg ? ` bg-${ props.toolbarBg }` : ''
|
|
));
|
|
|
|
const buttonProps = vue.computed(() => {
|
|
const flat = props.toolbarOutline !== true
|
|
&& props.toolbarPush !== true;
|
|
|
|
return {
|
|
type: 'a',
|
|
flat,
|
|
noWrap: true,
|
|
outline: props.toolbarOutline,
|
|
push: props.toolbarPush,
|
|
rounded: props.toolbarRounded,
|
|
dense: true,
|
|
color: props.toolbarColor,
|
|
disable: !editable.value,
|
|
size: 'sm'
|
|
}
|
|
});
|
|
|
|
const buttonDef = vue.computed(() => {
|
|
const
|
|
e = $q.lang.editor,
|
|
i = $q.iconSet.editor;
|
|
|
|
return {
|
|
bold: { cmd: 'bold', icon: i.bold, tip: e.bold, key: 66 },
|
|
italic: { cmd: 'italic', icon: i.italic, tip: e.italic, key: 73 },
|
|
strike: { cmd: 'strikeThrough', icon: i.strikethrough, tip: e.strikethrough, key: 83 },
|
|
underline: { cmd: 'underline', icon: i.underline, tip: e.underline, key: 85 },
|
|
unordered: { cmd: 'insertUnorderedList', icon: i.unorderedList, tip: e.unorderedList },
|
|
ordered: { cmd: 'insertOrderedList', icon: i.orderedList, tip: e.orderedList },
|
|
subscript: { cmd: 'subscript', icon: i.subscript, tip: e.subscript, htmlTip: 'x<subscript>2</subscript>' },
|
|
superscript: { cmd: 'superscript', icon: i.superscript, tip: e.superscript, htmlTip: 'x<superscript>2</superscript>' },
|
|
link: { cmd: 'link', disable: eVm => eVm.caret && !eVm.caret.can('link'), icon: i.hyperlink, tip: e.hyperlink, key: 76 },
|
|
fullscreen: { cmd: 'fullscreen', icon: i.toggleFullscreen, tip: e.toggleFullscreen, key: 70 },
|
|
viewsource: { cmd: 'viewsource', icon: i.viewSource, tip: e.viewSource },
|
|
|
|
quote: { cmd: 'formatBlock', param: 'BLOCKQUOTE', icon: i.quote, tip: e.quote, key: 81 },
|
|
left: { cmd: 'justifyLeft', icon: i.left, tip: e.left },
|
|
center: { cmd: 'justifyCenter', icon: i.center, tip: e.center },
|
|
right: { cmd: 'justifyRight', icon: i.right, tip: e.right },
|
|
justify: { cmd: 'justifyFull', icon: i.justify, tip: e.justify },
|
|
|
|
print: { type: 'no-state', cmd: 'print', icon: i.print, tip: e.print, key: 80 },
|
|
outdent: { type: 'no-state', disable: eVm => eVm.caret && !eVm.caret.can('outdent'), cmd: 'outdent', icon: i.outdent, tip: e.outdent },
|
|
indent: { type: 'no-state', disable: eVm => eVm.caret && !eVm.caret.can('indent'), cmd: 'indent', icon: i.indent, tip: e.indent },
|
|
removeFormat: { type: 'no-state', cmd: 'removeFormat', icon: i.removeFormat, tip: e.removeFormat },
|
|
hr: { type: 'no-state', cmd: 'insertHorizontalRule', icon: i.hr, tip: e.hr },
|
|
undo: { type: 'no-state', cmd: 'undo', icon: i.undo, tip: e.undo, key: 90 },
|
|
redo: { type: 'no-state', cmd: 'redo', icon: i.redo, tip: e.redo, key: 89 },
|
|
|
|
h1: { cmd: 'formatBlock', param: 'H1', icon: i.heading1 || i.heading, tip: e.heading1, htmlTip: `<h1 class="q-ma-none">${ e.heading1 }</h1>` },
|
|
h2: { cmd: 'formatBlock', param: 'H2', icon: i.heading2 || i.heading, tip: e.heading2, htmlTip: `<h2 class="q-ma-none">${ e.heading2 }</h2>` },
|
|
h3: { cmd: 'formatBlock', param: 'H3', icon: i.heading3 || i.heading, tip: e.heading3, htmlTip: `<h3 class="q-ma-none">${ e.heading3 }</h3>` },
|
|
h4: { cmd: 'formatBlock', param: 'H4', icon: i.heading4 || i.heading, tip: e.heading4, htmlTip: `<h4 class="q-ma-none">${ e.heading4 }</h4>` },
|
|
h5: { cmd: 'formatBlock', param: 'H5', icon: i.heading5 || i.heading, tip: e.heading5, htmlTip: `<h5 class="q-ma-none">${ e.heading5 }</h5>` },
|
|
h6: { cmd: 'formatBlock', param: 'H6', icon: i.heading6 || i.heading, tip: e.heading6, htmlTip: `<h6 class="q-ma-none">${ e.heading6 }</h6>` },
|
|
p: { cmd: 'formatBlock', param: props.paragraphTag, icon: i.heading, tip: e.paragraph },
|
|
code: { cmd: 'formatBlock', param: 'PRE', icon: i.code, htmlTip: `<code>${ e.code }</code>` },
|
|
|
|
'size-1': { cmd: 'fontSize', param: '1', icon: i.size1 || i.size, tip: e.size1, htmlTip: `<font size="1">${ e.size1 }</font>` },
|
|
'size-2': { cmd: 'fontSize', param: '2', icon: i.size2 || i.size, tip: e.size2, htmlTip: `<font size="2">${ e.size2 }</font>` },
|
|
'size-3': { cmd: 'fontSize', param: '3', icon: i.size3 || i.size, tip: e.size3, htmlTip: `<font size="3">${ e.size3 }</font>` },
|
|
'size-4': { cmd: 'fontSize', param: '4', icon: i.size4 || i.size, tip: e.size4, htmlTip: `<font size="4">${ e.size4 }</font>` },
|
|
'size-5': { cmd: 'fontSize', param: '5', icon: i.size5 || i.size, tip: e.size5, htmlTip: `<font size="5">${ e.size5 }</font>` },
|
|
'size-6': { cmd: 'fontSize', param: '6', icon: i.size6 || i.size, tip: e.size6, htmlTip: `<font size="6">${ e.size6 }</font>` },
|
|
'size-7': { cmd: 'fontSize', param: '7', icon: i.size7 || i.size, tip: e.size7, htmlTip: `<font size="7">${ e.size7 }</font>` }
|
|
}
|
|
});
|
|
|
|
const buttons = vue.computed(() => {
|
|
const userDef = props.definitions || {};
|
|
const def = props.definitions || props.fonts
|
|
? extend(
|
|
true,
|
|
{},
|
|
buttonDef.value,
|
|
userDef,
|
|
getFonts(
|
|
defaultFont,
|
|
$q.lang.editor.defaultFont,
|
|
$q.iconSet.editor.font,
|
|
props.fonts
|
|
)
|
|
)
|
|
: buttonDef.value;
|
|
|
|
return props.toolbar.map(
|
|
group => group.map(token => {
|
|
if (token.options) {
|
|
return {
|
|
type: 'dropdown',
|
|
icon: token.icon,
|
|
label: token.label,
|
|
size: 'sm',
|
|
dense: true,
|
|
fixedLabel: token.fixedLabel,
|
|
fixedIcon: token.fixedIcon,
|
|
highlight: token.highlight,
|
|
list: token.list,
|
|
options: token.options.map(item => def[ item ])
|
|
}
|
|
}
|
|
|
|
const obj = def[ token ];
|
|
|
|
if (obj) {
|
|
return obj.type === 'no-state' || (userDef[ token ] && (
|
|
obj.cmd === void 0 || (buttonDef.value[ obj.cmd ] && buttonDef.value[ obj.cmd ].type === 'no-state')
|
|
))
|
|
? obj
|
|
: Object.assign({ type: 'toggle' }, obj)
|
|
}
|
|
else {
|
|
return {
|
|
type: 'slot',
|
|
slot: token
|
|
}
|
|
}
|
|
})
|
|
)
|
|
});
|
|
|
|
const eVm = {
|
|
$q,
|
|
props,
|
|
slots,
|
|
emit,
|
|
// caret (will get injected after mount)
|
|
inFullscreen,
|
|
toggleFullscreen,
|
|
runCmd,
|
|
isViewingSource,
|
|
editLinkUrl,
|
|
toolbarBackgroundClass,
|
|
buttonProps,
|
|
contentRef,
|
|
buttons,
|
|
setContent
|
|
};
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
if (lastEmit !== v) {
|
|
lastEmit = v;
|
|
setContent(v, true);
|
|
}
|
|
});
|
|
|
|
vue.watch(editLinkUrl, v => {
|
|
emit(`link${ v ? 'Show' : 'Hide' }`);
|
|
});
|
|
|
|
const hasToolbar = vue.computed(() => props.toolbar && props.toolbar.length !== 0);
|
|
|
|
const keys = vue.computed(() => {
|
|
const
|
|
k = {},
|
|
add = btn => {
|
|
if (btn.key) {
|
|
k[ btn.key ] = {
|
|
cmd: btn.cmd,
|
|
param: btn.param
|
|
};
|
|
}
|
|
};
|
|
|
|
buttons.value.forEach(group => {
|
|
group.forEach(token => {
|
|
if (token.options) {
|
|
token.options.forEach(add);
|
|
}
|
|
else {
|
|
add(token);
|
|
}
|
|
});
|
|
});
|
|
return k
|
|
});
|
|
|
|
const innerStyle = vue.computed(() => (
|
|
inFullscreen.value
|
|
? props.contentStyle
|
|
: [
|
|
{
|
|
minHeight: props.minHeight,
|
|
height: props.height,
|
|
maxHeight: props.maxHeight
|
|
},
|
|
props.contentStyle
|
|
]
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-editor q-editor--${ isViewingSource.value === true ? 'source' : 'default' }`
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (inFullscreen.value === true ? ' fullscreen column' : '')
|
|
+ (props.square === true ? ' q-editor--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-editor--flat' : '')
|
|
+ (props.dense === true ? ' q-editor--dense' : '')
|
|
+ (isDark.value === true ? ' q-editor--dark q-dark' : '')
|
|
);
|
|
|
|
const innerClass = vue.computed(() => ([
|
|
props.contentClass,
|
|
'q-editor__content',
|
|
{ col: inFullscreen.value, 'overflow-auto': inFullscreen.value || props.maxHeight }
|
|
]));
|
|
|
|
const attributes = vue.computed(() => (
|
|
props.disable === true
|
|
? { 'aria-disabled': 'true' }
|
|
: (props.readonly === true ? { 'aria-readonly': 'true' } : {})
|
|
));
|
|
|
|
function onInput () {
|
|
if (contentRef.value !== null) {
|
|
const prop = `inner${ isViewingSource.value === true ? 'Text' : 'HTML' }`;
|
|
const val = contentRef.value[ prop ];
|
|
|
|
if (val !== props.modelValue) {
|
|
lastEmit = val;
|
|
emit('update:modelValue', val);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
emit('keydown', e);
|
|
|
|
if (e.ctrlKey !== true || shouldIgnoreKey(e) === true) {
|
|
refreshToolbar();
|
|
return
|
|
}
|
|
|
|
const key = e.keyCode;
|
|
const target = keys.value[ key ];
|
|
if (target !== void 0) {
|
|
const { cmd, param } = target;
|
|
stopAndPrevent(e);
|
|
runCmd(cmd, param, false);
|
|
}
|
|
}
|
|
|
|
function onClick (e) {
|
|
refreshToolbar();
|
|
emit('click', e);
|
|
}
|
|
|
|
function onBlur (e) {
|
|
if (contentRef.value !== null) {
|
|
const { scrollTop, scrollHeight } = contentRef.value;
|
|
offsetBottom = scrollHeight - scrollTop;
|
|
}
|
|
eVm.caret.save();
|
|
emit('blur', e);
|
|
}
|
|
|
|
function onFocus (e) {
|
|
vue.nextTick(() => {
|
|
if (contentRef.value !== null && offsetBottom !== void 0) {
|
|
contentRef.value.scrollTop = contentRef.value.scrollHeight - offsetBottom;
|
|
}
|
|
});
|
|
emit('focus', e);
|
|
}
|
|
|
|
function onFocusin (e) {
|
|
const root = rootRef.value;
|
|
|
|
if (
|
|
root !== null
|
|
&& root.contains(e.target) === true
|
|
&& (
|
|
e.relatedTarget === null
|
|
|| root.contains(e.relatedTarget) !== true
|
|
)
|
|
) {
|
|
const prop = `inner${ isViewingSource.value === true ? 'Text' : 'HTML' }`;
|
|
eVm.caret.restorePosition(contentRef.value[ prop ].length);
|
|
refreshToolbar();
|
|
}
|
|
}
|
|
|
|
function onFocusout (e) {
|
|
const root = rootRef.value;
|
|
|
|
if (
|
|
root !== null
|
|
&& root.contains(e.target) === true
|
|
&& (
|
|
e.relatedTarget === null
|
|
|| root.contains(e.relatedTarget) !== true
|
|
)
|
|
) {
|
|
eVm.caret.savePosition();
|
|
refreshToolbar();
|
|
}
|
|
}
|
|
|
|
function onPointerStart () {
|
|
offsetBottom = void 0;
|
|
}
|
|
|
|
function onSelectionchange (e) {
|
|
eVm.caret.save();
|
|
}
|
|
|
|
function setContent (v, restorePosition) {
|
|
if (contentRef.value !== null) {
|
|
if (restorePosition === true) {
|
|
eVm.caret.savePosition();
|
|
}
|
|
|
|
const prop = `inner${ isViewingSource.value === true ? 'Text' : 'HTML' }`;
|
|
contentRef.value[ prop ] = v;
|
|
|
|
if (restorePosition === true) {
|
|
eVm.caret.restorePosition(contentRef.value[ prop ].length);
|
|
refreshToolbar();
|
|
}
|
|
}
|
|
}
|
|
|
|
function runCmd (cmd, param, update = true) {
|
|
focus();
|
|
eVm.caret.restore();
|
|
eVm.caret.apply(cmd, param, () => {
|
|
focus();
|
|
eVm.caret.save();
|
|
if (update) {
|
|
refreshToolbar();
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshToolbar () {
|
|
setTimeout(() => {
|
|
editLinkUrl.value = null;
|
|
proxy.$forceUpdate();
|
|
}, 1);
|
|
}
|
|
|
|
function focus () {
|
|
addFocusFn(() => {
|
|
contentRef.value !== null && contentRef.value.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
|
|
function getContentEl () {
|
|
return contentRef.value
|
|
}
|
|
|
|
vue.onMounted(() => {
|
|
eVm.caret = proxy.caret = new Caret(contentRef.value, eVm);
|
|
setContent(props.modelValue);
|
|
refreshToolbar();
|
|
|
|
document.addEventListener('selectionchange', onSelectionchange);
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
document.removeEventListener('selectionchange', onSelectionchange);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
runCmd, refreshToolbar, focus, getContentEl
|
|
});
|
|
|
|
return () => {
|
|
let toolbars;
|
|
|
|
if (hasToolbar.value) {
|
|
const bars = [
|
|
vue.h('div', {
|
|
key: 'qedt_top',
|
|
class: 'q-editor__toolbar row no-wrap scroll-x'
|
|
+ toolbarBackgroundClass.value
|
|
}, getToolbar(eVm))
|
|
];
|
|
|
|
editLinkUrl.value !== null && bars.push(
|
|
vue.h('div', {
|
|
key: 'qedt_btm',
|
|
class: 'q-editor__toolbar row no-wrap items-center scroll-x'
|
|
+ toolbarBackgroundClass.value
|
|
}, getLinkEditor(eVm))
|
|
);
|
|
|
|
toolbars = vue.h('div', {
|
|
key: 'toolbar_ctainer',
|
|
class: 'q-editor__toolbars-container'
|
|
}, bars);
|
|
}
|
|
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
style: { height: inFullscreen.value === true ? '100%' : null },
|
|
...attributes.value,
|
|
onFocusin,
|
|
onFocusout
|
|
}, [
|
|
toolbars,
|
|
|
|
vue.h('div', {
|
|
ref: contentRef,
|
|
style: innerStyle.value,
|
|
class: innerClass.value,
|
|
contenteditable: editable.value,
|
|
placeholder: props.placeholder,
|
|
...({}),
|
|
...splitAttrs.listeners.value,
|
|
onInput,
|
|
onKeydown,
|
|
onClick,
|
|
onBlur,
|
|
onFocus,
|
|
|
|
// clean saved scroll position
|
|
onMousedown: onPointerStart,
|
|
onTouchstartPassive: onPointerStart
|
|
})
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
var QItemLabel = createComponent({
|
|
name: 'QItemLabel',
|
|
|
|
props: {
|
|
overline: Boolean,
|
|
caption: Boolean,
|
|
header: Boolean,
|
|
lines: [ Number, String ]
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const parsedLines = vue.computed(() => parseInt(props.lines, 10));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-item__label'
|
|
+ (props.overline === true ? ' q-item__label--overline text-overline' : '')
|
|
+ (props.caption === true ? ' q-item__label--caption text-caption' : '')
|
|
+ (props.header === true ? ' q-item__label--header' : '')
|
|
+ (parsedLines.value === 1 ? ' ellipsis' : '')
|
|
);
|
|
|
|
const style = vue.computed(() => {
|
|
return props.lines !== void 0 && parsedLines.value > 1
|
|
? {
|
|
overflow: 'hidden',
|
|
display: '-webkit-box',
|
|
'-webkit-box-orient': 'vertical',
|
|
'-webkit-line-clamp': parsedLines.value
|
|
}
|
|
: null
|
|
});
|
|
|
|
return () => vue.h('div', {
|
|
style: style.value,
|
|
class: classes.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QSlideTransition = createComponent({
|
|
name: 'QSlideTransition',
|
|
|
|
props: {
|
|
appear: Boolean,
|
|
duration: {
|
|
type: Number,
|
|
default: 300
|
|
}
|
|
},
|
|
|
|
emits: [ 'show', 'hide' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
let animating = false, doneFn, element;
|
|
let timer = null, timerFallback = null, animListener, lastEvent;
|
|
|
|
function cleanup () {
|
|
doneFn && doneFn();
|
|
doneFn = null;
|
|
animating = false;
|
|
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
if (timerFallback !== null) {
|
|
clearTimeout(timerFallback);
|
|
timerFallback = null;
|
|
}
|
|
|
|
element !== void 0 && element.removeEventListener('transitionend', animListener);
|
|
animListener = null;
|
|
}
|
|
|
|
function begin (el, height, done) {
|
|
// here overflowY is 'hidden'
|
|
if (height !== void 0) {
|
|
el.style.height = `${ height }px`;
|
|
}
|
|
el.style.transition = `height ${ props.duration }ms cubic-bezier(.25, .8, .50, 1)`;
|
|
|
|
animating = true;
|
|
doneFn = done;
|
|
}
|
|
|
|
function end (el, event) {
|
|
el.style.overflowY = null;
|
|
el.style.height = null;
|
|
el.style.transition = null;
|
|
cleanup();
|
|
event !== lastEvent && emit(event);
|
|
}
|
|
|
|
function onEnter (el, done) {
|
|
let pos = 0;
|
|
element = el;
|
|
|
|
// if animationg overflowY is already 'hidden'
|
|
if (animating === true) {
|
|
cleanup();
|
|
pos = el.offsetHeight === el.scrollHeight ? 0 : void 0;
|
|
}
|
|
else {
|
|
lastEvent = 'hide';
|
|
el.style.overflowY = 'hidden';
|
|
}
|
|
|
|
begin(el, pos, done);
|
|
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
el.style.height = `${ el.scrollHeight }px`;
|
|
animListener = evt => {
|
|
timerFallback = null;
|
|
|
|
if (Object(evt) !== evt || evt.target === el) {
|
|
end(el, 'show');
|
|
}
|
|
};
|
|
el.addEventListener('transitionend', animListener);
|
|
timerFallback = setTimeout(animListener, props.duration * 1.1);
|
|
}, 100);
|
|
}
|
|
|
|
function onLeave (el, done) {
|
|
let pos;
|
|
element = el;
|
|
|
|
if (animating === true) {
|
|
cleanup();
|
|
}
|
|
else {
|
|
lastEvent = 'show';
|
|
// we need to set overflowY 'hidden' before calculating the height
|
|
// or else we get small differences
|
|
el.style.overflowY = 'hidden';
|
|
pos = el.scrollHeight;
|
|
}
|
|
|
|
begin(el, pos, done);
|
|
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
el.style.height = 0;
|
|
animListener = evt => {
|
|
timerFallback = null;
|
|
|
|
if (Object(evt) !== evt || evt.target === el) {
|
|
end(el, 'hide');
|
|
}
|
|
};
|
|
el.addEventListener('transitionend', animListener);
|
|
timerFallback = setTimeout(animListener, props.duration * 1.1);
|
|
}, 100);
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
animating === true && cleanup();
|
|
});
|
|
|
|
return () => vue.h(vue.Transition, {
|
|
css: false,
|
|
appear: props.appear,
|
|
onEnter,
|
|
onLeave
|
|
}, slots.default)
|
|
}
|
|
});
|
|
|
|
const insetMap = {
|
|
true: 'inset',
|
|
item: 'item-inset',
|
|
'item-thumbnail': 'item-thumbnail-inset'
|
|
};
|
|
|
|
const margins = {
|
|
xs: 2,
|
|
sm: 4,
|
|
md: 8,
|
|
lg: 16,
|
|
xl: 24
|
|
};
|
|
|
|
var QSeparator = createComponent({
|
|
name: 'QSeparator',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
spaced: [ Boolean, String ],
|
|
inset: [ Boolean, String ],
|
|
vertical: Boolean,
|
|
color: String,
|
|
size: String
|
|
},
|
|
|
|
setup (props) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const orientation = vue.computed(() => (
|
|
props.vertical === true
|
|
? 'vertical'
|
|
: 'horizontal'
|
|
));
|
|
|
|
const orientClass = vue.computed(() => ` q-separator--${ orientation.value }`);
|
|
|
|
const insetClass = vue.computed(() => (
|
|
props.inset !== false
|
|
? `${ orientClass.value }-${ insetMap[ props.inset ] }`
|
|
: ''
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-separator${ orientClass.value }${ insetClass.value }`
|
|
+ (props.color !== void 0 ? ` bg-${ props.color }` : '')
|
|
+ (isDark.value === true ? ' q-separator--dark' : '')
|
|
);
|
|
|
|
const style = vue.computed(() => {
|
|
const acc = {};
|
|
|
|
if (props.size !== void 0) {
|
|
acc[ props.vertical === true ? 'width' : 'height' ] = props.size;
|
|
}
|
|
|
|
if (props.spaced !== false) {
|
|
const size = props.spaced === true
|
|
? `${ margins.md }px`
|
|
: props.spaced in margins ? `${ margins[ props.spaced ] }px` : props.spaced;
|
|
|
|
const dir = props.vertical === true
|
|
? [ 'Left', 'Right' ]
|
|
: [ 'Top', 'Bottom' ];
|
|
|
|
acc[ `margin${ dir[ 0 ] }` ] = acc[ `margin${ dir[ 1 ] }` ] = size;
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
return () => vue.h('hr', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
'aria-orientation': orientation.value
|
|
})
|
|
}
|
|
});
|
|
|
|
const itemGroups = vue.shallowReactive({});
|
|
const LINK_PROPS = Object.keys(useRouterLinkProps);
|
|
|
|
var QExpansionItem = createComponent({
|
|
name: 'QExpansionItem',
|
|
|
|
props: {
|
|
...useRouterLinkProps,
|
|
...useModelToggleProps,
|
|
...useDarkProps,
|
|
|
|
icon: String,
|
|
|
|
label: String,
|
|
labelLines: [ Number, String ],
|
|
|
|
caption: String,
|
|
captionLines: [ Number, String ],
|
|
|
|
dense: Boolean,
|
|
|
|
toggleAriaLabel: String,
|
|
expandIcon: String,
|
|
expandedIcon: String,
|
|
expandIconClass: [ Array, String, Object ],
|
|
duration: Number,
|
|
|
|
headerInsetLevel: Number,
|
|
contentInsetLevel: Number,
|
|
|
|
expandSeparator: Boolean,
|
|
defaultOpened: Boolean,
|
|
hideExpandIcon: Boolean,
|
|
expandIconToggle: Boolean,
|
|
switchToggleSide: Boolean,
|
|
denseToggle: Boolean,
|
|
group: String,
|
|
popup: Boolean,
|
|
|
|
headerStyle: [ Array, String, Object ],
|
|
headerClass: [ Array, String, Object ]
|
|
},
|
|
|
|
emits: [
|
|
...useModelToggleEmits,
|
|
'click', 'afterShow', 'afterHide'
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const showing = vue.ref(
|
|
props.modelValue !== null
|
|
? props.modelValue
|
|
: props.defaultOpened
|
|
);
|
|
|
|
const blurTargetRef = vue.ref(null);
|
|
const targetUid = uid$3();
|
|
|
|
const { show, hide, toggle } = useModelToggle({ showing });
|
|
|
|
let uniqueId, exitGroup;
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-expansion-item q-item-type'
|
|
+ ` q-expansion-item--${ showing.value === true ? 'expanded' : 'collapsed' }`
|
|
+ ` q-expansion-item--${ props.popup === true ? 'popup' : 'standard' }`
|
|
);
|
|
|
|
const contentStyle = vue.computed(() => {
|
|
if (props.contentInsetLevel === void 0) {
|
|
return null
|
|
}
|
|
|
|
const dir = $q.lang.rtl === true ? 'Right' : 'Left';
|
|
return {
|
|
[ 'padding' + dir ]: (props.contentInsetLevel * 56) + 'px'
|
|
}
|
|
});
|
|
|
|
const hasLink = vue.computed(() =>
|
|
props.disable !== true && (
|
|
props.href !== void 0
|
|
|| (props.to !== void 0 && props.to !== null && props.to !== '')
|
|
)
|
|
);
|
|
|
|
const linkProps = vue.computed(() => {
|
|
const acc = {};
|
|
LINK_PROPS.forEach(key => {
|
|
acc[ key ] = props[ key ];
|
|
});
|
|
return acc
|
|
});
|
|
|
|
const isClickable = vue.computed(() =>
|
|
hasLink.value === true || props.expandIconToggle !== true
|
|
);
|
|
|
|
const expansionIcon = vue.computed(() => (
|
|
props.expandedIcon !== void 0 && showing.value === true
|
|
? props.expandedIcon
|
|
: props.expandIcon || $q.iconSet.expansionItem[ props.denseToggle === true ? 'denseIcon' : 'icon' ]
|
|
));
|
|
|
|
const activeToggleIcon = vue.computed(() =>
|
|
props.disable !== true && (hasLink.value === true || props.expandIconToggle === true)
|
|
);
|
|
|
|
const headerSlotScope = vue.computed(() => ({
|
|
expanded: showing.value === true,
|
|
detailsId: props.targetUid,
|
|
toggle,
|
|
show,
|
|
hide
|
|
}));
|
|
|
|
const toggleAriaAttrs = vue.computed(() => {
|
|
const toggleAriaLabel = props.toggleAriaLabel !== void 0
|
|
? props.toggleAriaLabel
|
|
: $q.lang.label[ showing.value === true ? 'collapse' : 'expand' ](props.label);
|
|
|
|
return {
|
|
role: 'button',
|
|
'aria-expanded': showing.value === true ? 'true' : 'false',
|
|
'aria-controls': targetUid,
|
|
'aria-label': toggleAriaLabel
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.group, name => {
|
|
exitGroup !== void 0 && exitGroup();
|
|
name !== void 0 && enterGroup();
|
|
});
|
|
|
|
function onHeaderClick (e) {
|
|
hasLink.value !== true && toggle(e);
|
|
emit('click', e);
|
|
}
|
|
|
|
function toggleIconKeyboard (e) {
|
|
e.keyCode === 13 && toggleIcon(e, true);
|
|
}
|
|
|
|
function toggleIcon (e, keyboard) {
|
|
keyboard !== true && blurTargetRef.value !== null && blurTargetRef.value.focus();
|
|
toggle(e);
|
|
stopAndPrevent(e);
|
|
}
|
|
|
|
function onShow () {
|
|
emit('afterShow');
|
|
}
|
|
|
|
function onHide () {
|
|
emit('afterHide');
|
|
}
|
|
|
|
function enterGroup () {
|
|
if (uniqueId === void 0) {
|
|
uniqueId = uid$3();
|
|
}
|
|
|
|
if (showing.value === true) {
|
|
itemGroups[ props.group ] = uniqueId;
|
|
}
|
|
|
|
const show = vue.watch(showing, val => {
|
|
if (val === true) {
|
|
itemGroups[ props.group ] = uniqueId;
|
|
}
|
|
else if (itemGroups[ props.group ] === uniqueId) {
|
|
delete itemGroups[ props.group ];
|
|
}
|
|
});
|
|
|
|
const group = vue.watch(
|
|
() => itemGroups[ props.group ],
|
|
(val, oldVal) => {
|
|
if (oldVal === uniqueId && val !== void 0 && val !== uniqueId) {
|
|
hide();
|
|
}
|
|
}
|
|
);
|
|
|
|
exitGroup = () => {
|
|
show();
|
|
group();
|
|
|
|
if (itemGroups[ props.group ] === uniqueId) {
|
|
delete itemGroups[ props.group ];
|
|
}
|
|
|
|
exitGroup = void 0;
|
|
};
|
|
}
|
|
|
|
function getToggleIcon () {
|
|
const data = {
|
|
class: [
|
|
'q-focusable relative-position cursor-pointer'
|
|
+ `${ props.denseToggle === true && props.switchToggleSide === true ? ' items-end' : '' }`,
|
|
props.expandIconClass
|
|
],
|
|
side: props.switchToggleSide !== true,
|
|
avatar: props.switchToggleSide
|
|
};
|
|
|
|
const child = [
|
|
vue.h(QIcon, {
|
|
class: 'q-expansion-item__toggle-icon'
|
|
+ (props.expandedIcon === void 0 && showing.value === true
|
|
? ' q-expansion-item__toggle-icon--rotated'
|
|
: ''),
|
|
name: expansionIcon.value
|
|
})
|
|
];
|
|
|
|
if (activeToggleIcon.value === true) {
|
|
Object.assign(data, {
|
|
tabindex: 0,
|
|
...toggleAriaAttrs.value,
|
|
onClick: toggleIcon,
|
|
onKeyup: toggleIconKeyboard
|
|
});
|
|
|
|
child.unshift(
|
|
vue.h('div', {
|
|
ref: blurTargetRef,
|
|
class: 'q-expansion-item__toggle-focus q-icon q-focus-helper q-focus-helper--rounded',
|
|
tabindex: -1
|
|
})
|
|
);
|
|
}
|
|
|
|
return vue.h(QItemSection, data, () => child)
|
|
}
|
|
|
|
function getHeaderChild () {
|
|
let child;
|
|
|
|
if (slots.header !== void 0) {
|
|
child = [].concat(slots.header(headerSlotScope.value));
|
|
}
|
|
else {
|
|
child = [
|
|
vue.h(QItemSection, () => [
|
|
vue.h(QItemLabel, { lines: props.labelLines }, () => props.label || ''),
|
|
|
|
props.caption
|
|
? vue.h(QItemLabel, { lines: props.captionLines, caption: true }, () => props.caption)
|
|
: null
|
|
])
|
|
];
|
|
|
|
props.icon && child[ props.switchToggleSide === true ? 'push' : 'unshift' ](
|
|
vue.h(QItemSection, {
|
|
side: props.switchToggleSide === true,
|
|
avatar: props.switchToggleSide !== true
|
|
}, () => vue.h(QIcon, { name: props.icon }))
|
|
);
|
|
}
|
|
|
|
if (props.disable !== true && props.hideExpandIcon !== true) {
|
|
child[ props.switchToggleSide === true ? 'unshift' : 'push' ](
|
|
getToggleIcon()
|
|
);
|
|
}
|
|
|
|
return child
|
|
}
|
|
|
|
function getHeader () {
|
|
const data = {
|
|
ref: 'item',
|
|
style: props.headerStyle,
|
|
class: props.headerClass,
|
|
dark: isDark.value,
|
|
disable: props.disable,
|
|
dense: props.dense,
|
|
insetLevel: props.headerInsetLevel
|
|
};
|
|
|
|
if (isClickable.value === true) {
|
|
data.clickable = true;
|
|
data.onClick = onHeaderClick;
|
|
|
|
Object.assign(
|
|
data,
|
|
hasLink.value === true ? linkProps.value : toggleAriaAttrs.value
|
|
);
|
|
}
|
|
|
|
return vue.h(QItem, data, getHeaderChild)
|
|
}
|
|
|
|
function getTransitionChild () {
|
|
return vue.withDirectives(
|
|
vue.h('div', {
|
|
key: 'e-content',
|
|
class: 'q-expansion-item__content relative-position',
|
|
style: contentStyle.value,
|
|
id: targetUid
|
|
}, hSlot(slots.default)),
|
|
[ [
|
|
vue.vShow,
|
|
showing.value
|
|
] ]
|
|
)
|
|
}
|
|
|
|
function getContent () {
|
|
const node = [
|
|
getHeader(),
|
|
|
|
vue.h(QSlideTransition, {
|
|
duration: props.duration,
|
|
onShow,
|
|
onHide
|
|
}, getTransitionChild)
|
|
];
|
|
|
|
if (props.expandSeparator === true) {
|
|
node.push(
|
|
vue.h(QSeparator, {
|
|
class: 'q-expansion-item__border q-expansion-item__border--top absolute-top',
|
|
dark: isDark.value
|
|
}),
|
|
vue.h(QSeparator, {
|
|
class: 'q-expansion-item__border q-expansion-item__border--bottom absolute-bottom',
|
|
dark: isDark.value
|
|
})
|
|
);
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
props.group !== void 0 && enterGroup();
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
exitGroup !== void 0 && exitGroup();
|
|
});
|
|
|
|
return () => vue.h('div', { class: classes.value }, [
|
|
vue.h('div', { class: 'q-expansion-item__container relative-position' }, getContent())
|
|
])
|
|
}
|
|
});
|
|
|
|
const labelPositions = [ 'top', 'right', 'bottom', 'left' ];
|
|
|
|
const useFabProps = {
|
|
type: {
|
|
type: String,
|
|
default: 'a'
|
|
},
|
|
|
|
outline: Boolean,
|
|
push: Boolean,
|
|
flat: Boolean,
|
|
unelevated: Boolean,
|
|
|
|
color: String,
|
|
textColor: String,
|
|
glossy: Boolean,
|
|
|
|
square: Boolean,
|
|
padding: String,
|
|
|
|
label: {
|
|
type: [ String, Number ],
|
|
default: ''
|
|
},
|
|
labelPosition: {
|
|
type: String,
|
|
default: 'right',
|
|
validator: v => labelPositions.includes(v)
|
|
},
|
|
externalLabel: Boolean,
|
|
hideLabel: {
|
|
type: Boolean
|
|
},
|
|
labelClass: [ Array, String, Object ],
|
|
labelStyle: [ Array, String, Object ],
|
|
|
|
disable: Boolean,
|
|
|
|
tabindex: [ Number, String ]
|
|
};
|
|
|
|
function useFab (props, showing) {
|
|
return {
|
|
formClass: vue.computed(() =>
|
|
`q-fab--form-${ props.square === true ? 'square' : 'rounded' }`
|
|
),
|
|
|
|
stacked: vue.computed(() =>
|
|
props.externalLabel === false
|
|
&& [ 'top', 'bottom' ].includes(props.labelPosition)
|
|
),
|
|
|
|
labelProps: vue.computed(() => {
|
|
if (props.externalLabel === true) {
|
|
const hideLabel = props.hideLabel === null
|
|
? showing.value === false
|
|
: props.hideLabel;
|
|
|
|
return {
|
|
action: 'push',
|
|
data: {
|
|
class: [
|
|
props.labelClass,
|
|
'q-fab__label q-tooltip--style q-fab__label--external'
|
|
+ ` q-fab__label--external-${ props.labelPosition }`
|
|
+ (hideLabel === true ? ' q-fab__label--external-hidden' : '')
|
|
],
|
|
style: props.labelStyle
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
action: [ 'left', 'top' ].includes(props.labelPosition)
|
|
? 'unshift'
|
|
: 'push',
|
|
data: {
|
|
class: [
|
|
props.labelClass,
|
|
`q-fab__label q-fab__label--internal q-fab__label--internal-${ props.labelPosition }`
|
|
+ (props.hideLabel === true ? ' q-fab__label--internal-hidden' : '')
|
|
],
|
|
style: props.labelStyle
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const directions = [ 'up', 'right', 'down', 'left' ];
|
|
const alignValues = [ 'left', 'center', 'right' ];
|
|
|
|
var QFab = createComponent({
|
|
name: 'QFab',
|
|
|
|
props: {
|
|
...useFabProps,
|
|
...useModelToggleProps,
|
|
|
|
icon: String,
|
|
activeIcon: String,
|
|
|
|
hideIcon: Boolean,
|
|
hideLabel: {
|
|
default: null
|
|
},
|
|
|
|
direction: {
|
|
type: String,
|
|
default: 'right',
|
|
validator: v => directions.includes(v)
|
|
},
|
|
|
|
persistent: Boolean,
|
|
|
|
verticalActionsAlign: {
|
|
type: String,
|
|
default: 'center',
|
|
validator: v => alignValues.includes(v)
|
|
}
|
|
},
|
|
|
|
emits: useModelToggleEmits,
|
|
|
|
setup (props, { slots }) {
|
|
const triggerRef = vue.ref(null);
|
|
const showing = vue.ref(props.modelValue === true);
|
|
const targetUid = uid$3();
|
|
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const { formClass, labelProps } = useFab(props, showing);
|
|
|
|
const hideOnRouteChange = vue.computed(() => props.persistent !== true);
|
|
|
|
const { hide, toggle } = useModelToggle({
|
|
showing,
|
|
hideOnRouteChange
|
|
});
|
|
|
|
const slotScope = vue.computed(() => ({ opened: showing.value }));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-fab z-fab row inline justify-center'
|
|
+ ` q-fab--align-${ props.verticalActionsAlign } ${ formClass.value }`
|
|
+ (showing.value === true ? ' q-fab--opened' : ' q-fab--closed')
|
|
);
|
|
|
|
const actionClass = vue.computed(() =>
|
|
'q-fab__actions flex no-wrap inline'
|
|
+ ` q-fab__actions--${ props.direction }`
|
|
+ ` q-fab__actions--${ showing.value === true ? 'opened' : 'closed' }`
|
|
);
|
|
|
|
const actionAttrs = vue.computed(() => {
|
|
const attrs = {
|
|
id: targetUid,
|
|
role: 'menu'
|
|
};
|
|
|
|
if (showing.value !== true) {
|
|
attrs[ 'aria-hidden' ] = 'true';
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
const iconHolderClass = vue.computed(() =>
|
|
'q-fab__icon-holder '
|
|
+ ` q-fab__icon-holder--${ showing.value === true ? 'opened' : 'closed' }`
|
|
);
|
|
|
|
function getIcon (kebab, camel) {
|
|
const slotFn = slots[ kebab ];
|
|
const classes = `q-fab__${ kebab } absolute-full`;
|
|
|
|
return slotFn === void 0
|
|
? vue.h(QIcon, { class: classes, name: props[ camel ] || $q.iconSet.fab[ camel ] })
|
|
: vue.h('div', { class: classes }, slotFn(slotScope.value))
|
|
}
|
|
|
|
function getTriggerContent () {
|
|
const child = [];
|
|
|
|
props.hideIcon !== true && child.push(
|
|
vue.h('div', { class: iconHolderClass.value }, [
|
|
getIcon('icon', 'icon'),
|
|
getIcon('active-icon', 'activeIcon')
|
|
])
|
|
);
|
|
|
|
if (props.label !== '' || slots.label !== void 0) {
|
|
child[ labelProps.value.action ](
|
|
vue.h('div', labelProps.value.data, slots.label !== void 0 ? slots.label(slotScope.value) : [ props.label ])
|
|
);
|
|
}
|
|
|
|
return hMergeSlot(slots.tooltip, child)
|
|
}
|
|
|
|
vue.provide(fabKey, {
|
|
showing,
|
|
|
|
onChildClick (evt) {
|
|
hide(evt);
|
|
|
|
if (triggerRef.value !== null) {
|
|
triggerRef.value.$el.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value
|
|
}, [
|
|
vue.h(QBtn, {
|
|
ref: triggerRef,
|
|
class: formClass.value,
|
|
...props,
|
|
noWrap: true,
|
|
stack: props.stacked,
|
|
align: void 0,
|
|
icon: void 0,
|
|
label: void 0,
|
|
noCaps: true,
|
|
fab: true,
|
|
'aria-expanded': showing.value === true ? 'true' : 'false',
|
|
'aria-haspopup': 'true',
|
|
'aria-controls': targetUid,
|
|
onClick: toggle
|
|
}, getTriggerContent),
|
|
|
|
vue.h('div', { class: actionClass.value, ...actionAttrs.value }, hSlot(slots.default))
|
|
])
|
|
}
|
|
});
|
|
|
|
const anchorMap = {
|
|
start: 'self-end',
|
|
center: 'self-center',
|
|
end: 'self-start'
|
|
};
|
|
|
|
const anchorValues = Object.keys(anchorMap);
|
|
|
|
var QFabAction = createComponent({
|
|
name: 'QFabAction',
|
|
|
|
props: {
|
|
...useFabProps,
|
|
|
|
icon: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
|
|
anchor: {
|
|
type: String,
|
|
validator: v => anchorValues.includes(v)
|
|
},
|
|
|
|
to: [ String, Object ],
|
|
replace: Boolean
|
|
},
|
|
|
|
emits: [ 'click' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const $fab = vue.inject(fabKey, () => ({
|
|
showing: { value: true },
|
|
onChildClick: noop
|
|
}));
|
|
|
|
const { formClass, labelProps } = useFab(props, $fab.showing);
|
|
|
|
const classes = vue.computed(() => {
|
|
const align = anchorMap[ props.anchor ];
|
|
return formClass.value + (align !== void 0 ? ` ${ align }` : '')
|
|
});
|
|
|
|
const isDisabled = vue.computed(() =>
|
|
props.disable === true
|
|
|| $fab.showing.value !== true
|
|
);
|
|
|
|
function click (e) {
|
|
$fab.onChildClick(e);
|
|
emit('click', e);
|
|
}
|
|
|
|
function getContent () {
|
|
const child = [];
|
|
|
|
if (slots.icon !== void 0) {
|
|
child.push(slots.icon());
|
|
}
|
|
else if (props.icon !== '') {
|
|
child.push(
|
|
vue.h(QIcon, { name: props.icon })
|
|
);
|
|
}
|
|
|
|
if (props.label !== '' || slots.label !== void 0) {
|
|
child[ labelProps.value.action ](
|
|
vue.h('div', labelProps.value.data, slots.label !== void 0 ? slots.label() : [ props.label ])
|
|
);
|
|
}
|
|
|
|
return hMergeSlot(slots.default, child)
|
|
}
|
|
|
|
// expose public methods
|
|
const vm = vue.getCurrentInstance();
|
|
Object.assign(vm.proxy, { click });
|
|
|
|
return () => vue.h(QBtn, {
|
|
class: classes.value,
|
|
...props,
|
|
noWrap: true,
|
|
stack: props.stacked,
|
|
icon: void 0,
|
|
label: void 0,
|
|
noCaps: true,
|
|
fabMini: true,
|
|
disable: isDisabled.value,
|
|
onClick: click
|
|
}, getContent)
|
|
}
|
|
});
|
|
|
|
function useFormChild ({ validate, resetValidation, requiresQForm }) {
|
|
const $form = vue.inject(formKey, false);
|
|
|
|
if ($form !== false) {
|
|
const { props, proxy } = vue.getCurrentInstance();
|
|
|
|
// export public method (so it can be used in QForm)
|
|
Object.assign(proxy, { validate, resetValidation });
|
|
|
|
vue.watch(() => props.disable, val => {
|
|
if (val === true) {
|
|
typeof resetValidation === 'function' && resetValidation();
|
|
$form.unbindComponent(proxy);
|
|
}
|
|
else {
|
|
$form.bindComponent(proxy);
|
|
}
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
// register to parent QForm
|
|
props.disable !== true && $form.bindComponent(proxy);
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
// un-register from parent QForm
|
|
props.disable !== true && $form.unbindComponent(proxy);
|
|
});
|
|
}
|
|
else if (requiresQForm === true) {
|
|
console.error('Parent QForm not found on useFormChild()!');
|
|
}
|
|
}
|
|
|
|
const lazyRulesValues = [ true, false, 'ondemand' ];
|
|
|
|
const useValidateProps = {
|
|
modelValue: {},
|
|
|
|
error: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
errorMessage: String,
|
|
noErrorIcon: Boolean,
|
|
|
|
rules: Array,
|
|
reactiveRules: Boolean,
|
|
lazyRules: {
|
|
type: [ Boolean, String ],
|
|
validator: v => lazyRulesValues.includes(v)
|
|
}
|
|
};
|
|
|
|
function useValidate (focused, innerLoading) {
|
|
const { props, proxy } = vue.getCurrentInstance();
|
|
|
|
const innerError = vue.ref(false);
|
|
const innerErrorMessage = vue.ref(null);
|
|
const isDirtyModel = vue.ref(null);
|
|
|
|
useFormChild({ validate, resetValidation });
|
|
|
|
let validateIndex = 0, unwatchRules;
|
|
|
|
const hasRules = vue.computed(() =>
|
|
props.rules !== void 0
|
|
&& props.rules !== null
|
|
&& props.rules.length !== 0
|
|
);
|
|
|
|
const hasActiveRules = vue.computed(() =>
|
|
props.disable !== true
|
|
&& hasRules.value === true
|
|
);
|
|
|
|
const hasError = vue.computed(() =>
|
|
props.error === true || innerError.value === true
|
|
);
|
|
|
|
const errorMessage = vue.computed(() => (
|
|
typeof props.errorMessage === 'string' && props.errorMessage.length !== 0
|
|
? props.errorMessage
|
|
: innerErrorMessage.value
|
|
));
|
|
|
|
vue.watch(() => props.modelValue, () => {
|
|
validateIfNeeded();
|
|
});
|
|
|
|
vue.watch(() => props.reactiveRules, val => {
|
|
if (val === true) {
|
|
if (unwatchRules === void 0) {
|
|
unwatchRules = vue.watch(() => props.rules, () => {
|
|
validateIfNeeded(true);
|
|
});
|
|
}
|
|
}
|
|
else if (unwatchRules !== void 0) {
|
|
unwatchRules();
|
|
unwatchRules = void 0;
|
|
}
|
|
}, { immediate: true });
|
|
|
|
vue.watch(focused, val => {
|
|
if (val === true) {
|
|
if (isDirtyModel.value === null) {
|
|
isDirtyModel.value = false;
|
|
}
|
|
}
|
|
else if (isDirtyModel.value === false) {
|
|
isDirtyModel.value = true;
|
|
|
|
if (
|
|
hasActiveRules.value === true
|
|
&& props.lazyRules !== 'ondemand'
|
|
// Don't re-trigger if it's already in progress;
|
|
// It might mean that focus switched to submit btn and
|
|
// QForm's submit() has been called already (ENTER key)
|
|
&& innerLoading.value === false
|
|
) {
|
|
debouncedValidate();
|
|
}
|
|
}
|
|
});
|
|
|
|
function resetValidation () {
|
|
validateIndex++;
|
|
innerLoading.value = false;
|
|
isDirtyModel.value = null;
|
|
innerError.value = false;
|
|
innerErrorMessage.value = null;
|
|
debouncedValidate.cancel();
|
|
}
|
|
|
|
/*
|
|
* Return value
|
|
* - true (validation succeeded)
|
|
* - false (validation failed)
|
|
* - Promise (pending async validation)
|
|
*/
|
|
function validate (val = props.modelValue) {
|
|
if (hasActiveRules.value !== true) {
|
|
return true
|
|
}
|
|
|
|
const index = ++validateIndex;
|
|
|
|
const setDirty = innerLoading.value !== true
|
|
? () => { isDirtyModel.value = true; }
|
|
: () => {};
|
|
|
|
const update = (err, msg) => {
|
|
err === true && setDirty();
|
|
|
|
innerError.value = err;
|
|
innerErrorMessage.value = msg || null;
|
|
innerLoading.value = false;
|
|
};
|
|
|
|
const promises = [];
|
|
|
|
for (let i = 0; i < props.rules.length; i++) {
|
|
const rule = props.rules[ i ];
|
|
let res;
|
|
|
|
if (typeof rule === 'function') {
|
|
res = rule(val, testPattern);
|
|
}
|
|
else if (typeof rule === 'string' && testPattern[ rule ] !== void 0) {
|
|
res = testPattern[ rule ](val);
|
|
}
|
|
|
|
if (res === false || typeof res === 'string') {
|
|
update(true, res);
|
|
return false
|
|
}
|
|
else if (res !== true && res !== void 0) {
|
|
promises.push(res);
|
|
}
|
|
}
|
|
|
|
if (promises.length === 0) {
|
|
update(false);
|
|
return true
|
|
}
|
|
|
|
innerLoading.value = true;
|
|
|
|
return Promise.all(promises).then(
|
|
res => {
|
|
if (res === void 0 || Array.isArray(res) === false || res.length === 0) {
|
|
index === validateIndex && update(false);
|
|
return true
|
|
}
|
|
|
|
const msg = res.find(r => r === false || typeof r === 'string');
|
|
index === validateIndex && update(msg !== void 0, msg);
|
|
return msg === void 0
|
|
},
|
|
e => {
|
|
if (index === validateIndex) {
|
|
console.error(e);
|
|
update(true);
|
|
}
|
|
|
|
return false
|
|
}
|
|
)
|
|
}
|
|
|
|
function validateIfNeeded (changedRules) {
|
|
if (
|
|
hasActiveRules.value === true
|
|
&& props.lazyRules !== 'ondemand'
|
|
&& (isDirtyModel.value === true || (props.lazyRules !== true && changedRules !== true))
|
|
) {
|
|
debouncedValidate();
|
|
}
|
|
}
|
|
|
|
const debouncedValidate = debounce(validate, 0);
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
unwatchRules !== void 0 && unwatchRules();
|
|
debouncedValidate.cancel();
|
|
});
|
|
|
|
// expose public methods & props
|
|
Object.assign(proxy, { resetValidation, validate });
|
|
injectProp(proxy, 'hasError', () => hasError.value);
|
|
|
|
return {
|
|
isDirtyModel,
|
|
hasRules,
|
|
hasError,
|
|
errorMessage,
|
|
|
|
validate,
|
|
resetValidation
|
|
}
|
|
}
|
|
|
|
function getTargetUid (val) {
|
|
return val === void 0 ? `f_${ uid$3() }` : val
|
|
}
|
|
|
|
function fieldValueIsFilled (val) {
|
|
return val !== void 0
|
|
&& val !== null
|
|
&& ('' + val).length !== 0
|
|
}
|
|
|
|
const useFieldProps = {
|
|
...useDarkProps,
|
|
...useValidateProps,
|
|
|
|
label: String,
|
|
stackLabel: Boolean,
|
|
hint: String,
|
|
hideHint: Boolean,
|
|
prefix: String,
|
|
suffix: String,
|
|
|
|
labelColor: String,
|
|
color: String,
|
|
bgColor: String,
|
|
|
|
filled: Boolean,
|
|
outlined: Boolean,
|
|
borderless: Boolean,
|
|
standout: [ Boolean, String ],
|
|
|
|
square: Boolean,
|
|
|
|
loading: Boolean,
|
|
|
|
labelSlot: Boolean,
|
|
|
|
bottomSlots: Boolean,
|
|
hideBottomSpace: Boolean,
|
|
|
|
rounded: Boolean,
|
|
dense: Boolean,
|
|
itemAligned: Boolean,
|
|
|
|
counter: Boolean,
|
|
|
|
clearable: Boolean,
|
|
clearIcon: String,
|
|
|
|
disable: Boolean,
|
|
readonly: Boolean,
|
|
|
|
autofocus: Boolean,
|
|
|
|
for: String,
|
|
|
|
maxlength: [ Number, String ]
|
|
};
|
|
|
|
const useFieldEmits = [ 'update:modelValue', 'clear', 'focus', 'blur', 'popupShow', 'popupHide' ];
|
|
|
|
function useFieldState () {
|
|
const { props, attrs, proxy, vnode } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, proxy.$q);
|
|
|
|
return {
|
|
isDark,
|
|
|
|
editable: vue.computed(() =>
|
|
props.disable !== true && props.readonly !== true
|
|
),
|
|
|
|
innerLoading: vue.ref(false),
|
|
focused: vue.ref(false),
|
|
hasPopupOpen: false,
|
|
|
|
splitAttrs: useSplitAttrs(attrs, vnode),
|
|
targetUid: vue.ref(getTargetUid(props.for)),
|
|
|
|
rootRef: vue.ref(null),
|
|
targetRef: vue.ref(null),
|
|
controlRef: vue.ref(null)
|
|
|
|
/**
|
|
* user supplied additionals:
|
|
|
|
* innerValue - computed
|
|
* floatingLabel - computed
|
|
* inputRef - computed
|
|
|
|
* fieldClass - computed
|
|
* hasShadow - computed
|
|
|
|
* controlEvents - Object with fn(e)
|
|
|
|
* getControl - fn
|
|
* getInnerAppend - fn
|
|
* getControlChild - fn
|
|
* getShadowControl - fn
|
|
* showPopup - fn
|
|
*/
|
|
}
|
|
}
|
|
|
|
function useField (state) {
|
|
const { props, emit, slots, attrs, proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
let focusoutTimer = null;
|
|
|
|
if (state.hasValue === void 0) {
|
|
state.hasValue = vue.computed(() => fieldValueIsFilled(props.modelValue));
|
|
}
|
|
|
|
if (state.emitValue === void 0) {
|
|
state.emitValue = value => {
|
|
emit('update:modelValue', value);
|
|
};
|
|
}
|
|
|
|
if (state.controlEvents === void 0) {
|
|
state.controlEvents = {
|
|
onFocusin: onControlFocusin,
|
|
onFocusout: onControlFocusout
|
|
};
|
|
}
|
|
|
|
Object.assign(state, {
|
|
clearValue,
|
|
onControlFocusin,
|
|
onControlFocusout,
|
|
focus
|
|
});
|
|
|
|
if (state.computedCounter === void 0) {
|
|
state.computedCounter = vue.computed(() => {
|
|
if (props.counter !== false) {
|
|
const len = typeof props.modelValue === 'string' || typeof props.modelValue === 'number'
|
|
? ('' + props.modelValue).length
|
|
: (Array.isArray(props.modelValue) === true ? props.modelValue.length : 0);
|
|
|
|
const max = props.maxlength !== void 0
|
|
? props.maxlength
|
|
: props.maxValues;
|
|
|
|
return len + (max !== void 0 ? ' / ' + max : '')
|
|
}
|
|
});
|
|
}
|
|
|
|
const {
|
|
isDirtyModel,
|
|
hasRules,
|
|
hasError,
|
|
errorMessage,
|
|
resetValidation
|
|
} = useValidate(state.focused, state.innerLoading);
|
|
|
|
const floatingLabel = state.floatingLabel !== void 0
|
|
? vue.computed(() => props.stackLabel === true || state.focused.value === true || state.floatingLabel.value === true)
|
|
: vue.computed(() => props.stackLabel === true || state.focused.value === true || state.hasValue.value === true);
|
|
|
|
const shouldRenderBottom = vue.computed(() =>
|
|
props.bottomSlots === true
|
|
|| props.hint !== void 0
|
|
|| hasRules.value === true
|
|
|| props.counter === true
|
|
|| props.error !== null
|
|
);
|
|
|
|
const styleType = vue.computed(() => {
|
|
if (props.filled === true) { return 'filled' }
|
|
if (props.outlined === true) { return 'outlined' }
|
|
if (props.borderless === true) { return 'borderless' }
|
|
if (props.standout) { return 'standout' }
|
|
return 'standard'
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-field row no-wrap items-start q-field--${ styleType.value }`
|
|
+ (state.fieldClass !== void 0 ? ` ${ state.fieldClass.value }` : '')
|
|
+ (props.rounded === true ? ' q-field--rounded' : '')
|
|
+ (props.square === true ? ' q-field--square' : '')
|
|
+ (floatingLabel.value === true ? ' q-field--float' : '')
|
|
+ (hasLabel.value === true ? ' q-field--labeled' : '')
|
|
+ (props.dense === true ? ' q-field--dense' : '')
|
|
+ (props.itemAligned === true ? ' q-field--item-aligned q-item-type' : '')
|
|
+ (state.isDark.value === true ? ' q-field--dark' : '')
|
|
+ (state.getControl === void 0 ? ' q-field--auto-height' : '')
|
|
+ (state.focused.value === true ? ' q-field--focused' : '')
|
|
+ (hasError.value === true ? ' q-field--error' : '')
|
|
+ (hasError.value === true || state.focused.value === true ? ' q-field--highlighted' : '')
|
|
+ (props.hideBottomSpace !== true && shouldRenderBottom.value === true ? ' q-field--with-bottom' : '')
|
|
+ (props.disable === true ? ' q-field--disabled' : (props.readonly === true ? ' q-field--readonly' : ''))
|
|
);
|
|
|
|
const contentClass = vue.computed(() =>
|
|
'q-field__control relative-position row no-wrap'
|
|
+ (props.bgColor !== void 0 ? ` bg-${ props.bgColor }` : '')
|
|
+ (
|
|
hasError.value === true
|
|
? ' text-negative'
|
|
: (
|
|
typeof props.standout === 'string' && props.standout.length !== 0 && state.focused.value === true
|
|
? ` ${ props.standout }`
|
|
: (props.color !== void 0 ? ` text-${ props.color }` : '')
|
|
)
|
|
)
|
|
);
|
|
|
|
const hasLabel = vue.computed(() =>
|
|
props.labelSlot === true || props.label !== void 0
|
|
);
|
|
|
|
const labelClass = vue.computed(() =>
|
|
'q-field__label no-pointer-events absolute ellipsis'
|
|
+ (props.labelColor !== void 0 && hasError.value !== true ? ` text-${ props.labelColor }` : '')
|
|
);
|
|
|
|
const controlSlotScope = vue.computed(() => ({
|
|
id: state.targetUid.value,
|
|
editable: state.editable.value,
|
|
focused: state.focused.value,
|
|
floatingLabel: floatingLabel.value,
|
|
modelValue: props.modelValue,
|
|
emitValue: state.emitValue
|
|
}));
|
|
|
|
const attributes = vue.computed(() => {
|
|
const acc = {
|
|
for: state.targetUid.value
|
|
};
|
|
|
|
if (props.disable === true) {
|
|
acc[ 'aria-disabled' ] = 'true';
|
|
}
|
|
else if (props.readonly === true) {
|
|
acc[ 'aria-readonly' ] = 'true';
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
vue.watch(() => props.for, val => {
|
|
// don't transform targetUid into a computed
|
|
// prop as it will break SSR
|
|
state.targetUid.value = getTargetUid(val);
|
|
});
|
|
|
|
function focusHandler () {
|
|
const el = document.activeElement;
|
|
let target = state.targetRef !== void 0 && state.targetRef.value;
|
|
|
|
if (target && (el === null || el.id !== state.targetUid.value)) {
|
|
target.hasAttribute('tabindex') === true || (target = target.querySelector('[tabindex]'));
|
|
if (target && target !== el) {
|
|
target.focus({ preventScroll: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
function focus () {
|
|
addFocusFn(focusHandler);
|
|
}
|
|
|
|
function blur () {
|
|
removeFocusFn(focusHandler);
|
|
const el = document.activeElement;
|
|
if (el !== null && state.rootRef.value.contains(el)) {
|
|
el.blur();
|
|
}
|
|
}
|
|
|
|
function onControlFocusin (e) {
|
|
if (focusoutTimer !== null) {
|
|
clearTimeout(focusoutTimer);
|
|
focusoutTimer = null;
|
|
}
|
|
|
|
if (state.editable.value === true && state.focused.value === false) {
|
|
state.focused.value = true;
|
|
emit('focus', e);
|
|
}
|
|
}
|
|
|
|
function onControlFocusout (e, then) {
|
|
focusoutTimer !== null && clearTimeout(focusoutTimer);
|
|
focusoutTimer = setTimeout(() => {
|
|
focusoutTimer = null;
|
|
|
|
if (
|
|
document.hasFocus() === true && (
|
|
state.hasPopupOpen === true
|
|
|| state.controlRef === void 0
|
|
|| state.controlRef.value === null
|
|
|| state.controlRef.value.contains(document.activeElement) !== false
|
|
)
|
|
) {
|
|
return
|
|
}
|
|
|
|
if (state.focused.value === true) {
|
|
state.focused.value = false;
|
|
emit('blur', e);
|
|
}
|
|
|
|
then !== void 0 && then();
|
|
});
|
|
}
|
|
|
|
function clearValue (e) {
|
|
// prevent activating the field but keep focus on desktop
|
|
stopAndPrevent(e);
|
|
|
|
if ($q.platform.is.mobile !== true) {
|
|
const el = (state.targetRef !== void 0 && state.targetRef.value) || state.rootRef.value;
|
|
el.focus();
|
|
}
|
|
else if (state.rootRef.value.contains(document.activeElement) === true) {
|
|
document.activeElement.blur();
|
|
}
|
|
|
|
if (props.type === 'file') { // TODO vue3
|
|
// do not let focus be triggered
|
|
// as it will make the native file dialog
|
|
// appear for another selection
|
|
state.inputRef.value.value = null;
|
|
}
|
|
|
|
emit('update:modelValue', null);
|
|
emit('clear', props.modelValue);
|
|
|
|
vue.nextTick(() => {
|
|
resetValidation();
|
|
|
|
if ($q.platform.is.mobile !== true) {
|
|
isDirtyModel.value = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
function getContent () {
|
|
const node = [];
|
|
|
|
slots.prepend !== void 0 && node.push(
|
|
vue.h('div', {
|
|
class: 'q-field__prepend q-field__marginal row no-wrap items-center',
|
|
key: 'prepend',
|
|
onClick: prevent
|
|
}, slots.prepend())
|
|
);
|
|
|
|
node.push(
|
|
vue.h('div', {
|
|
class: 'q-field__control-container col relative-position row no-wrap q-anchor--skip'
|
|
}, getControlContainer())
|
|
);
|
|
|
|
hasError.value === true && props.noErrorIcon === false && node.push(
|
|
getInnerAppendNode('error', [
|
|
vue.h(QIcon, { name: $q.iconSet.field.error, color: 'negative' })
|
|
])
|
|
);
|
|
|
|
if (props.loading === true || state.innerLoading.value === true) {
|
|
node.push(
|
|
getInnerAppendNode(
|
|
'inner-loading-append',
|
|
slots.loading !== void 0
|
|
? slots.loading()
|
|
: [ vue.h(QSpinner, { color: props.color }) ]
|
|
)
|
|
);
|
|
}
|
|
else if (props.clearable === true && state.hasValue.value === true && state.editable.value === true) {
|
|
node.push(
|
|
getInnerAppendNode('inner-clearable-append', [
|
|
vue.h(QIcon, {
|
|
class: 'q-field__focusable-action',
|
|
tag: 'button',
|
|
name: props.clearIcon || $q.iconSet.field.clear,
|
|
tabindex: 0,
|
|
type: 'button',
|
|
'aria-hidden': null,
|
|
role: null,
|
|
onClick: clearValue
|
|
})
|
|
])
|
|
);
|
|
}
|
|
|
|
slots.append !== void 0 && node.push(
|
|
vue.h('div', {
|
|
class: 'q-field__append q-field__marginal row no-wrap items-center',
|
|
key: 'append',
|
|
onClick: prevent
|
|
}, slots.append())
|
|
);
|
|
|
|
state.getInnerAppend !== void 0 && node.push(
|
|
getInnerAppendNode('inner-append', state.getInnerAppend())
|
|
);
|
|
|
|
state.getControlChild !== void 0 && node.push(
|
|
state.getControlChild()
|
|
);
|
|
|
|
return node
|
|
}
|
|
|
|
function getControlContainer () {
|
|
const node = [];
|
|
|
|
props.prefix !== void 0 && props.prefix !== null && node.push(
|
|
vue.h('div', {
|
|
class: 'q-field__prefix no-pointer-events row items-center'
|
|
}, props.prefix)
|
|
);
|
|
|
|
if (state.getShadowControl !== void 0 && state.hasShadow.value === true) {
|
|
node.push(
|
|
state.getShadowControl()
|
|
);
|
|
}
|
|
|
|
if (state.getControl !== void 0) {
|
|
node.push(state.getControl());
|
|
}
|
|
// internal usage only:
|
|
else if (slots.rawControl !== void 0) {
|
|
node.push(slots.rawControl());
|
|
}
|
|
else if (slots.control !== void 0) {
|
|
node.push(
|
|
vue.h('div', {
|
|
ref: state.targetRef,
|
|
class: 'q-field__native row',
|
|
tabindex: -1,
|
|
...state.splitAttrs.attributes.value,
|
|
'data-autofocus': props.autofocus === true || void 0
|
|
}, slots.control(controlSlotScope.value))
|
|
);
|
|
}
|
|
|
|
hasLabel.value === true && node.push(
|
|
vue.h('div', {
|
|
class: labelClass.value
|
|
}, hSlot(slots.label, props.label))
|
|
);
|
|
|
|
props.suffix !== void 0 && props.suffix !== null && node.push(
|
|
vue.h('div', {
|
|
class: 'q-field__suffix no-pointer-events row items-center'
|
|
}, props.suffix)
|
|
);
|
|
|
|
return node.concat(hSlot(slots.default))
|
|
}
|
|
|
|
function getBottom () {
|
|
let msg, key;
|
|
|
|
if (hasError.value === true) {
|
|
if (errorMessage.value !== null) {
|
|
msg = [ vue.h('div', { role: 'alert' }, errorMessage.value) ];
|
|
key = `q--slot-error-${ errorMessage.value }`;
|
|
}
|
|
else {
|
|
msg = hSlot(slots.error);
|
|
key = 'q--slot-error';
|
|
}
|
|
}
|
|
else if (props.hideHint !== true || state.focused.value === true) {
|
|
if (props.hint !== void 0) {
|
|
msg = [ vue.h('div', props.hint) ];
|
|
key = `q--slot-hint-${ props.hint }`;
|
|
}
|
|
else {
|
|
msg = hSlot(slots.hint);
|
|
key = 'q--slot-hint';
|
|
}
|
|
}
|
|
|
|
const hasCounter = props.counter === true || slots.counter !== void 0;
|
|
|
|
if (props.hideBottomSpace === true && hasCounter === false && msg === void 0) {
|
|
return
|
|
}
|
|
|
|
const main = vue.h('div', {
|
|
key,
|
|
class: 'q-field__messages col'
|
|
}, msg);
|
|
|
|
return vue.h('div', {
|
|
class: 'q-field__bottom row items-start q-field__bottom--'
|
|
+ (props.hideBottomSpace !== true ? 'animated' : 'stale'),
|
|
onClick: prevent
|
|
}, [
|
|
props.hideBottomSpace === true
|
|
? main
|
|
: vue.h(vue.Transition, { name: 'q-transition--field-message' }, () => main),
|
|
|
|
hasCounter === true
|
|
? vue.h('div', {
|
|
class: 'q-field__counter'
|
|
}, slots.counter !== void 0 ? slots.counter() : state.computedCounter.value)
|
|
: null
|
|
])
|
|
}
|
|
|
|
function getInnerAppendNode (key, content) {
|
|
return content === null
|
|
? null
|
|
: vue.h('div', {
|
|
key,
|
|
class: 'q-field__append q-field__marginal row no-wrap items-center q-anchor--skip'
|
|
}, content)
|
|
}
|
|
|
|
let shouldActivate = false;
|
|
|
|
vue.onDeactivated(() => {
|
|
shouldActivate = true;
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
shouldActivate === true && props.autofocus === true && proxy.focus();
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
if (isRuntimeSsrPreHydration.value === true && props.for === void 0) {
|
|
state.targetUid.value = getTargetUid();
|
|
}
|
|
|
|
props.autofocus === true && proxy.focus();
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
focusoutTimer !== null && clearTimeout(focusoutTimer);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { focus, blur });
|
|
|
|
return function renderField () {
|
|
const labelAttrs = state.getControl === void 0 && slots.control === void 0
|
|
? {
|
|
...state.splitAttrs.attributes.value,
|
|
'data-autofocus': props.autofocus === true || void 0,
|
|
...attributes.value
|
|
}
|
|
: attributes.value;
|
|
|
|
return vue.h('label', {
|
|
ref: state.rootRef,
|
|
class: [
|
|
classes.value,
|
|
attrs.class
|
|
],
|
|
style: attrs.style,
|
|
...labelAttrs
|
|
}, [
|
|
slots.before !== void 0
|
|
? vue.h('div', {
|
|
class: 'q-field__before q-field__marginal row no-wrap items-center',
|
|
onClick: prevent
|
|
}, slots.before())
|
|
: null,
|
|
|
|
vue.h('div', {
|
|
class: 'q-field__inner relative-position col self-stretch'
|
|
}, [
|
|
vue.h('div', {
|
|
ref: state.controlRef,
|
|
class: contentClass.value,
|
|
tabindex: -1,
|
|
...state.controlEvents
|
|
}, getContent()),
|
|
|
|
shouldRenderBottom.value === true
|
|
? getBottom()
|
|
: null
|
|
]),
|
|
|
|
slots.after !== void 0
|
|
? vue.h('div', {
|
|
class: 'q-field__after q-field__marginal row no-wrap items-center',
|
|
onClick: prevent
|
|
}, slots.after())
|
|
: null
|
|
])
|
|
}
|
|
}
|
|
|
|
var QField = createComponent({
|
|
name: 'QField',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: useFieldProps,
|
|
|
|
emits: useFieldEmits,
|
|
|
|
setup () {
|
|
return useField(useFieldState())
|
|
}
|
|
});
|
|
|
|
function filterFiles (files, rejectedFiles, failedPropValidation, filterFn) {
|
|
const acceptedFiles = [];
|
|
|
|
files.forEach(file => {
|
|
if (filterFn(file) === true) {
|
|
acceptedFiles.push(file);
|
|
}
|
|
else {
|
|
rejectedFiles.push({ failedPropValidation, file });
|
|
}
|
|
});
|
|
|
|
return acceptedFiles
|
|
}
|
|
|
|
function stopAndPreventDrag (e) {
|
|
e && e.dataTransfer && (e.dataTransfer.dropEffect = 'copy');
|
|
stopAndPrevent(e);
|
|
}
|
|
|
|
const useFileProps = {
|
|
multiple: Boolean,
|
|
accept: String,
|
|
capture: String,
|
|
maxFileSize: [ Number, String ],
|
|
maxTotalSize: [ Number, String ],
|
|
maxFiles: [ Number, String ],
|
|
filter: Function
|
|
};
|
|
|
|
const useFileEmits = [ 'rejected' ];
|
|
|
|
function useFile ({
|
|
editable,
|
|
dnd,
|
|
getFileInput,
|
|
addFilesToQueue
|
|
}) {
|
|
const { props, emit, proxy } = vue.getCurrentInstance();
|
|
|
|
const dndRef = vue.ref(null);
|
|
|
|
const extensions = vue.computed(() => (
|
|
props.accept !== void 0
|
|
? props.accept.split(',').map(ext => {
|
|
ext = ext.trim();
|
|
if (ext === '*') { // support "*"
|
|
return '*/'
|
|
}
|
|
else if (ext.endsWith('/*')) { // support "image/*" or "*/*"
|
|
ext = ext.slice(0, ext.length - 1);
|
|
}
|
|
return ext.toUpperCase()
|
|
})
|
|
: null
|
|
));
|
|
|
|
const maxFilesNumber = vue.computed(() => parseInt(props.maxFiles, 10));
|
|
const maxTotalSizeNumber = vue.computed(() => parseInt(props.maxTotalSize, 10));
|
|
|
|
function pickFiles (e) {
|
|
if (editable.value) {
|
|
if (e !== Object(e)) {
|
|
e = { target: null };
|
|
}
|
|
|
|
if (e.target !== null && e.target.matches('input[type="file"]') === true) {
|
|
// stop propagation if it's not a real pointer event
|
|
e.clientX === 0 && e.clientY === 0 && stop(e);
|
|
}
|
|
else {
|
|
const input = getFileInput();
|
|
input && input !== e.target && input.click(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addFiles (files) {
|
|
if (editable.value && files) {
|
|
addFilesToQueue(null, files);
|
|
}
|
|
}
|
|
|
|
function processFiles (e, filesToProcess, currentFileList, append) {
|
|
let files = Array.from(filesToProcess || e.target.files);
|
|
const rejectedFiles = [];
|
|
|
|
const done = () => {
|
|
if (rejectedFiles.length !== 0) {
|
|
emit('rejected', rejectedFiles);
|
|
}
|
|
};
|
|
|
|
// filter file types
|
|
if (props.accept !== void 0 && extensions.value.indexOf('*/') === -1) {
|
|
files = filterFiles(files, rejectedFiles, 'accept', file => {
|
|
return extensions.value.some(ext => (
|
|
file.type.toUpperCase().startsWith(ext)
|
|
|| file.name.toUpperCase().endsWith(ext)
|
|
))
|
|
});
|
|
|
|
if (files.length === 0) { return done() }
|
|
}
|
|
|
|
// filter max file size
|
|
if (props.maxFileSize !== void 0) {
|
|
const maxFileSize = parseInt(props.maxFileSize, 10);
|
|
files = filterFiles(files, rejectedFiles, 'max-file-size', file => {
|
|
return file.size <= maxFileSize
|
|
});
|
|
|
|
if (files.length === 0) { return done() }
|
|
}
|
|
|
|
// Cordova/iOS allows selecting multiple files even when the
|
|
// multiple attribute is not specified. We also normalize drag'n'dropped
|
|
// files here:
|
|
if (props.multiple !== true && files.length !== 0) {
|
|
files = [ files[ 0 ] ];
|
|
}
|
|
|
|
// Compute key to use for each file
|
|
files.forEach(file => {
|
|
file.__key = file.webkitRelativePath + file.lastModified + file.name + file.size;
|
|
});
|
|
|
|
if (append === true) {
|
|
// Avoid duplicate files
|
|
const filenameMap = currentFileList.map(entry => entry.__key);
|
|
files = filterFiles(files, rejectedFiles, 'duplicate', file => {
|
|
return filenameMap.includes(file.__key) === false
|
|
});
|
|
}
|
|
|
|
if (files.length === 0) { return done() }
|
|
|
|
if (props.maxTotalSize !== void 0) {
|
|
let size = append === true
|
|
? currentFileList.reduce((total, file) => total + file.size, 0)
|
|
: 0;
|
|
|
|
files = filterFiles(files, rejectedFiles, 'max-total-size', file => {
|
|
size += file.size;
|
|
return size <= maxTotalSizeNumber.value
|
|
});
|
|
|
|
if (files.length === 0) { return done() }
|
|
}
|
|
|
|
// do we have custom filter function?
|
|
if (typeof props.filter === 'function') {
|
|
const filteredFiles = props.filter(files);
|
|
files = filterFiles(files, rejectedFiles, 'filter', file => {
|
|
return filteredFiles.includes(file)
|
|
});
|
|
}
|
|
|
|
if (props.maxFiles !== void 0) {
|
|
let filesNumber = append === true
|
|
? currentFileList.length
|
|
: 0;
|
|
|
|
files = filterFiles(files, rejectedFiles, 'max-files', () => {
|
|
filesNumber++;
|
|
return filesNumber <= maxFilesNumber.value
|
|
});
|
|
|
|
if (files.length === 0) { return done() }
|
|
}
|
|
|
|
done();
|
|
|
|
if (files.length !== 0) {
|
|
return files
|
|
}
|
|
}
|
|
|
|
function onDragover (e) {
|
|
stopAndPreventDrag(e);
|
|
dnd.value !== true && (dnd.value = true);
|
|
}
|
|
|
|
function onDragleave (e) {
|
|
stopAndPrevent(e);
|
|
|
|
// Safari bug: relatedTarget is null for over 10 years
|
|
// https://bugs.webkit.org/show_bug.cgi?id=66547
|
|
const gone = e.relatedTarget !== null || client.is.safari !== true
|
|
? e.relatedTarget !== dndRef.value
|
|
: document.elementsFromPoint(e.clientX, e.clientY).includes(dndRef.value) === false;
|
|
|
|
gone === true && (dnd.value = false);
|
|
}
|
|
|
|
function onDrop (e) {
|
|
stopAndPreventDrag(e);
|
|
const files = e.dataTransfer.files;
|
|
|
|
if (files.length !== 0) {
|
|
addFilesToQueue(null, files);
|
|
}
|
|
|
|
dnd.value = false;
|
|
}
|
|
|
|
function getDndNode (type) {
|
|
if (dnd.value === true) {
|
|
return vue.h('div', {
|
|
ref: dndRef,
|
|
class: `q-${ type }__dnd absolute-full`,
|
|
onDragenter: stopAndPreventDrag,
|
|
onDragover: stopAndPreventDrag,
|
|
onDragleave,
|
|
onDrop
|
|
})
|
|
}
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { pickFiles, addFiles });
|
|
|
|
return {
|
|
pickFiles,
|
|
addFiles,
|
|
onDragover,
|
|
onDragleave,
|
|
processFiles,
|
|
getDndNode,
|
|
|
|
maxFilesNumber,
|
|
maxTotalSizeNumber
|
|
}
|
|
}
|
|
|
|
function useFileFormDomProps (props, typeGuard) {
|
|
function getFormDomProps () {
|
|
const model = props.modelValue;
|
|
|
|
try {
|
|
const dt = 'DataTransfer' in window
|
|
? new DataTransfer()
|
|
: ('ClipboardEvent' in window
|
|
? new ClipboardEvent('').clipboardData
|
|
: void 0
|
|
);
|
|
|
|
if (Object(model) === model) {
|
|
('length' in model
|
|
? Array.from(model)
|
|
: [ model ]
|
|
).forEach(file => {
|
|
dt.items.add(file);
|
|
});
|
|
}
|
|
|
|
return {
|
|
files: dt.files
|
|
}
|
|
}
|
|
catch (e) {
|
|
return {
|
|
files: void 0
|
|
}
|
|
}
|
|
}
|
|
|
|
return typeGuard === true
|
|
? vue.computed(() => {
|
|
if (props.type !== 'file') {
|
|
return
|
|
}
|
|
|
|
return getFormDomProps()
|
|
})
|
|
: vue.computed(getFormDomProps)
|
|
}
|
|
|
|
var QFile = createComponent({
|
|
name: 'QFile',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useFieldProps,
|
|
...useFormProps,
|
|
...useFileProps,
|
|
|
|
/* SSR does not know about File & FileList */
|
|
modelValue: [ File, FileList, Array ],
|
|
|
|
append: Boolean,
|
|
useChips: Boolean,
|
|
displayValue: [ String, Number ],
|
|
|
|
tabindex: {
|
|
type: [ String, Number ],
|
|
default: 0
|
|
},
|
|
|
|
counterLabel: Function,
|
|
|
|
inputClass: [ Array, String, Object ],
|
|
inputStyle: [ Array, String, Object ]
|
|
},
|
|
|
|
emits: [
|
|
...useFieldEmits,
|
|
...useFileEmits
|
|
],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const state = useFieldState();
|
|
|
|
const inputRef = vue.ref(null);
|
|
const dnd = vue.ref(false);
|
|
const nameProp = useFormInputNameAttr(props);
|
|
|
|
const {
|
|
pickFiles,
|
|
onDragover,
|
|
onDragleave,
|
|
processFiles,
|
|
getDndNode
|
|
} = useFile({ editable: state.editable, dnd, getFileInput, addFilesToQueue });
|
|
|
|
const formDomProps = useFileFormDomProps(props);
|
|
|
|
const innerValue = vue.computed(() => (
|
|
Object(props.modelValue) === props.modelValue
|
|
? ('length' in props.modelValue ? Array.from(props.modelValue) : [ props.modelValue ])
|
|
: []
|
|
));
|
|
|
|
const hasValue = vue.computed(() => fieldValueIsFilled(innerValue.value));
|
|
|
|
const selectedString = vue.computed(() =>
|
|
innerValue.value
|
|
.map(file => file.name)
|
|
.join(', ')
|
|
);
|
|
|
|
const totalSize = vue.computed(() =>
|
|
humanStorageSize(
|
|
innerValue.value.reduce((acc, file) => acc + file.size, 0)
|
|
)
|
|
);
|
|
|
|
const counterProps = vue.computed(() => ({
|
|
totalSize: totalSize.value,
|
|
filesNumber: innerValue.value.length,
|
|
maxFiles: props.maxFiles
|
|
}));
|
|
|
|
const inputAttrs = vue.computed(() => ({
|
|
tabindex: -1,
|
|
type: 'file',
|
|
title: '', // try to remove default tooltip,
|
|
accept: props.accept,
|
|
capture: props.capture,
|
|
name: nameProp.value,
|
|
...attrs,
|
|
id: state.targetUid.value,
|
|
disabled: state.editable.value !== true
|
|
}));
|
|
|
|
const fieldClass = vue.computed(() =>
|
|
'q-file q-field--auto-height'
|
|
+ (dnd.value === true ? ' q-file--dnd' : '')
|
|
);
|
|
|
|
const isAppending = vue.computed(() =>
|
|
props.multiple === true && props.append === true
|
|
);
|
|
|
|
function removeAtIndex (index) {
|
|
const files = innerValue.value.slice();
|
|
files.splice(index, 1);
|
|
emitValue(files);
|
|
}
|
|
|
|
function removeFile (file) {
|
|
const index = innerValue.value.indexOf(file);
|
|
if (index > -1) {
|
|
removeAtIndex(index);
|
|
}
|
|
}
|
|
|
|
function emitValue (files) {
|
|
emit('update:modelValue', props.multiple === true ? files : files[ 0 ]);
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
// prevent form submit if ENTER is pressed
|
|
e.keyCode === 13 && prevent(e);
|
|
}
|
|
|
|
function onKeyup (e) {
|
|
// only on ENTER and SPACE to match native input field
|
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
pickFiles(e);
|
|
}
|
|
}
|
|
|
|
function getFileInput () {
|
|
return inputRef.value
|
|
}
|
|
|
|
function addFilesToQueue (e, fileList) {
|
|
const files = processFiles(e, fileList, innerValue.value, isAppending.value);
|
|
const fileInput = getFileInput();
|
|
|
|
if (fileInput !== void 0 && fileInput !== null) {
|
|
fileInput.value = '';
|
|
}
|
|
|
|
// if nothing to do...
|
|
if (files === void 0) { return }
|
|
|
|
// protect against input @change being called in a loop
|
|
// like it happens on Safari, so don't emit same thing:
|
|
if (
|
|
props.multiple === true
|
|
? props.modelValue && files.every(f => innerValue.value.includes(f))
|
|
: props.modelValue === files[ 0 ]
|
|
) {
|
|
return
|
|
}
|
|
|
|
emitValue(
|
|
isAppending.value === true
|
|
? innerValue.value.concat(files)
|
|
: files
|
|
);
|
|
}
|
|
|
|
function getFiller () {
|
|
return [
|
|
vue.h('input', {
|
|
class: [ props.inputClass, 'q-file__filler' ],
|
|
style: props.inputStyle
|
|
})
|
|
]
|
|
}
|
|
|
|
function getSelection () {
|
|
if (slots.file !== void 0) {
|
|
return innerValue.value.length === 0
|
|
? getFiller()
|
|
: innerValue.value.map(
|
|
(file, index) => slots.file({ index, file, ref: this })
|
|
)
|
|
}
|
|
|
|
if (slots.selected !== void 0) {
|
|
return innerValue.value.length === 0
|
|
? getFiller()
|
|
: slots.selected({ files: innerValue.value, ref: this })
|
|
}
|
|
|
|
if (props.useChips === true) {
|
|
return innerValue.value.length === 0
|
|
? getFiller()
|
|
: innerValue.value.map((file, i) => vue.h(QChip, {
|
|
key: 'file-' + i,
|
|
removable: state.editable.value,
|
|
dense: true,
|
|
textColor: props.color,
|
|
tabindex: props.tabindex,
|
|
onRemove: () => { removeAtIndex(i); }
|
|
}, () => vue.h('span', {
|
|
class: 'ellipsis',
|
|
textContent: file.name
|
|
})))
|
|
}
|
|
|
|
const textContent = props.displayValue !== void 0
|
|
? props.displayValue
|
|
: selectedString.value;
|
|
|
|
return textContent.length !== 0
|
|
? [
|
|
vue.h('div', {
|
|
class: props.inputClass,
|
|
style: props.inputStyle,
|
|
textContent
|
|
})
|
|
]
|
|
: getFiller()
|
|
}
|
|
|
|
function getInput () {
|
|
const data = {
|
|
ref: inputRef,
|
|
...inputAttrs.value,
|
|
...formDomProps.value,
|
|
class: 'q-field__input fit absolute-full cursor-pointer',
|
|
onChange: addFilesToQueue
|
|
};
|
|
|
|
if (props.multiple === true) {
|
|
data.multiple = true;
|
|
}
|
|
|
|
return vue.h('input', data)
|
|
}
|
|
|
|
Object.assign(state, {
|
|
fieldClass,
|
|
emitValue,
|
|
hasValue,
|
|
inputRef,
|
|
innerValue,
|
|
|
|
floatingLabel: vue.computed(() =>
|
|
hasValue.value === true
|
|
|| fieldValueIsFilled(props.displayValue)
|
|
),
|
|
|
|
computedCounter: vue.computed(() => {
|
|
if (props.counterLabel !== void 0) {
|
|
return props.counterLabel(counterProps.value)
|
|
}
|
|
|
|
const max = props.maxFiles;
|
|
return `${ innerValue.value.length }${ max !== void 0 ? ' / ' + max : '' } (${ totalSize.value })`
|
|
}),
|
|
|
|
getControlChild: () => getDndNode('file'),
|
|
getControl: () => {
|
|
const data = {
|
|
ref: state.targetRef,
|
|
class: 'q-field__native row items-center cursor-pointer',
|
|
tabindex: props.tabindex
|
|
};
|
|
|
|
if (state.editable.value === true) {
|
|
Object.assign(data, { onDragover, onDragleave, onKeydown, onKeyup });
|
|
}
|
|
|
|
return vue.h('div', data, [ getInput() ].concat(getSelection()))
|
|
}
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
removeAtIndex,
|
|
removeFile,
|
|
getNativeElement: () => inputRef.value // deprecated
|
|
});
|
|
|
|
injectProp(proxy, 'nativeEl', () => inputRef.value);
|
|
|
|
return useField(state)
|
|
}
|
|
});
|
|
|
|
var QFooter = createComponent({
|
|
name: 'QFooter',
|
|
|
|
props: {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
reveal: Boolean,
|
|
bordered: Boolean,
|
|
elevated: Boolean,
|
|
|
|
heightHint: {
|
|
type: [ String, Number ],
|
|
default: 50
|
|
}
|
|
},
|
|
|
|
emits: [ 'reveal', 'focusin' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QFooter needs to be child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const size = vue.ref(parseInt(props.heightHint, 10));
|
|
const revealed = vue.ref(true);
|
|
const windowHeight = vue.ref(
|
|
isRuntimeSsrPreHydration.value === true || $layout.isContainer.value === true
|
|
? 0
|
|
: window.innerHeight
|
|
);
|
|
|
|
const fixed = vue.computed(() =>
|
|
props.reveal === true
|
|
|| $layout.view.value.indexOf('F') > -1
|
|
|| ($q.platform.is.ios && $layout.isContainer.value === true)
|
|
);
|
|
|
|
const containerHeight = vue.computed(() => (
|
|
$layout.isContainer.value === true
|
|
? $layout.containerHeight.value
|
|
: windowHeight.value
|
|
));
|
|
|
|
const offset = vue.computed(() => {
|
|
if (props.modelValue !== true) {
|
|
return 0
|
|
}
|
|
if (fixed.value === true) {
|
|
return revealed.value === true ? size.value : 0
|
|
}
|
|
const offset = $layout.scroll.value.position + containerHeight.value + size.value - $layout.height.value;
|
|
return offset > 0 ? offset : 0
|
|
});
|
|
|
|
const hidden = vue.computed(() =>
|
|
props.modelValue !== true || (fixed.value === true && revealed.value !== true)
|
|
);
|
|
|
|
const revealOnFocus = vue.computed(() =>
|
|
props.modelValue === true && hidden.value === true && props.reveal === true
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-footer q-layout__section--marginal '
|
|
+ (fixed.value === true ? 'fixed' : 'absolute') + '-bottom'
|
|
+ (props.bordered === true ? ' q-footer--bordered' : '')
|
|
+ (hidden.value === true ? ' q-footer--hidden' : '')
|
|
+ (
|
|
props.modelValue !== true
|
|
? ' q-layout--prevent-focus' + (fixed.value !== true ? ' hidden' : '')
|
|
: ''
|
|
)
|
|
);
|
|
|
|
const style = vue.computed(() => {
|
|
const
|
|
view = $layout.rows.value.bottom,
|
|
css = {};
|
|
|
|
if (view[ 0 ] === 'l' && $layout.left.space === true) {
|
|
css[ $q.lang.rtl === true ? 'right' : 'left' ] = `${ $layout.left.size }px`;
|
|
}
|
|
if (view[ 2 ] === 'r' && $layout.right.space === true) {
|
|
css[ $q.lang.rtl === true ? 'left' : 'right' ] = `${ $layout.right.size }px`;
|
|
}
|
|
|
|
return css
|
|
});
|
|
|
|
function updateLayout (prop, val) {
|
|
$layout.update('footer', prop, val);
|
|
}
|
|
|
|
function updateLocal (prop, val) {
|
|
if (prop.value !== val) {
|
|
prop.value = val;
|
|
}
|
|
}
|
|
|
|
function onResize ({ height }) {
|
|
updateLocal(size, height);
|
|
updateLayout('size', height);
|
|
}
|
|
|
|
function updateRevealed () {
|
|
if (props.reveal !== true) { return }
|
|
|
|
const { direction, position, inflectionPoint } = $layout.scroll.value;
|
|
|
|
updateLocal(revealed, (
|
|
direction === 'up'
|
|
|| position - inflectionPoint < 100
|
|
|| $layout.height.value - containerHeight.value - position - size.value < 300
|
|
));
|
|
}
|
|
|
|
function onFocusin (evt) {
|
|
if (revealOnFocus.value === true) {
|
|
updateLocal(revealed, true);
|
|
}
|
|
|
|
emit('focusin', evt);
|
|
}
|
|
|
|
vue.watch(() => props.modelValue, val => {
|
|
updateLayout('space', val);
|
|
updateLocal(revealed, true);
|
|
$layout.animate();
|
|
});
|
|
|
|
vue.watch(offset, val => {
|
|
updateLayout('offset', val);
|
|
});
|
|
|
|
vue.watch(() => props.reveal, val => {
|
|
val === false && updateLocal(revealed, props.modelValue);
|
|
});
|
|
|
|
vue.watch(revealed, val => {
|
|
$layout.animate();
|
|
emit('reveal', val);
|
|
});
|
|
|
|
vue.watch([ size, $layout.scroll, $layout.height ], updateRevealed);
|
|
|
|
vue.watch(() => $q.screen.height, val => {
|
|
$layout.isContainer.value !== true && updateLocal(windowHeight, val);
|
|
});
|
|
|
|
const instance = {};
|
|
|
|
$layout.instances.footer = instance;
|
|
props.modelValue === true && updateLayout('size', size.value);
|
|
updateLayout('space', props.modelValue);
|
|
updateLayout('offset', offset.value);
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
if ($layout.instances.footer === instance) {
|
|
$layout.instances.footer = void 0;
|
|
updateLayout('size', 0);
|
|
updateLayout('offset', 0);
|
|
updateLayout('space', false);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
const child = hMergeSlot(slots.default, [
|
|
vue.h(QResizeObserver, {
|
|
debounce: 0,
|
|
onResize
|
|
})
|
|
]);
|
|
|
|
props.elevated === true && child.push(
|
|
vue.h('div', {
|
|
class: 'q-layout__shadow absolute-full overflow-hidden no-pointer-events'
|
|
})
|
|
);
|
|
|
|
return vue.h('footer', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
onFocusin
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QForm = createComponent({
|
|
name: 'QForm',
|
|
|
|
props: {
|
|
autofocus: Boolean,
|
|
noErrorFocus: Boolean,
|
|
noResetFocus: Boolean,
|
|
greedy: Boolean,
|
|
|
|
onSubmit: Function
|
|
},
|
|
|
|
emits: [ 'reset', 'validationSuccess', 'validationError' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const rootRef = vue.ref(null);
|
|
|
|
let validateIndex = 0;
|
|
const registeredComponents = [];
|
|
|
|
function validate (shouldFocus) {
|
|
const focus = typeof shouldFocus === 'boolean'
|
|
? shouldFocus
|
|
: props.noErrorFocus !== true;
|
|
|
|
const index = ++validateIndex;
|
|
|
|
const emitEvent = (res, ref) => {
|
|
emit('validation' + (res === true ? 'Success' : 'Error'), ref);
|
|
};
|
|
|
|
const validateComponent = comp => {
|
|
const valid = comp.validate();
|
|
|
|
return typeof valid.then === 'function'
|
|
? valid.then(
|
|
valid => ({ valid, comp }),
|
|
err => ({ valid: false, comp, err })
|
|
)
|
|
: Promise.resolve({ valid, comp })
|
|
};
|
|
|
|
const errorsPromise = props.greedy === true
|
|
? Promise
|
|
.all(registeredComponents.map(validateComponent))
|
|
.then(res => res.filter(r => r.valid !== true))
|
|
: registeredComponents
|
|
.reduce(
|
|
(acc, comp) => acc.then(() => {
|
|
return validateComponent(comp).then(r => {
|
|
if (r.valid === false) { return Promise.reject(r) }
|
|
})
|
|
}),
|
|
Promise.resolve()
|
|
)
|
|
.catch(error => [ error ]);
|
|
|
|
return errorsPromise.then(errors => {
|
|
if (errors === void 0 || errors.length === 0) {
|
|
index === validateIndex && emitEvent(true);
|
|
return true
|
|
}
|
|
|
|
// if not outdated already
|
|
if (index === validateIndex) {
|
|
const { comp, err } = errors[ 0 ];
|
|
|
|
err !== void 0 && console.error(err);
|
|
emitEvent(false, comp);
|
|
|
|
if (focus === true) {
|
|
// Try to focus first mounted and active component
|
|
const activeError = errors.find(({ comp }) => (
|
|
typeof comp.focus === 'function'
|
|
&& vmIsDestroyed(comp.$) === false
|
|
));
|
|
|
|
if (activeError !== void 0) {
|
|
activeError.comp.focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
})
|
|
}
|
|
|
|
function resetValidation () {
|
|
validateIndex++;
|
|
|
|
registeredComponents.forEach(comp => {
|
|
typeof comp.resetValidation === 'function' && comp.resetValidation();
|
|
});
|
|
}
|
|
|
|
function submit (evt) {
|
|
evt !== void 0 && stopAndPrevent(evt);
|
|
|
|
const index = validateIndex + 1;
|
|
|
|
validate().then(val => {
|
|
// if not outdated && validation succeeded
|
|
if (index === validateIndex && val === true) {
|
|
if (props.onSubmit !== void 0) {
|
|
emit('submit', evt);
|
|
}
|
|
else if (evt !== void 0 && evt.target !== void 0 && typeof evt.target.submit === 'function') {
|
|
evt.target.submit();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function reset (evt) {
|
|
evt !== void 0 && stopAndPrevent(evt);
|
|
|
|
emit('reset');
|
|
|
|
vue.nextTick(() => { // allow userland to reset values before
|
|
resetValidation();
|
|
if (props.autofocus === true && props.noResetFocus !== true) {
|
|
focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
function focus () {
|
|
addFocusFn(() => {
|
|
if (rootRef.value === null) { return }
|
|
|
|
const target = rootRef.value.querySelector('[autofocus][tabindex], [data-autofocus][tabindex]')
|
|
|| rootRef.value.querySelector('[autofocus] [tabindex], [data-autofocus] [tabindex]')
|
|
|| rootRef.value.querySelector('[autofocus], [data-autofocus]')
|
|
|| Array.prototype.find.call(rootRef.value.querySelectorAll('[tabindex]'), el => el.tabIndex > -1);
|
|
|
|
target !== null && target !== void 0 && target.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
|
|
vue.provide(formKey, {
|
|
bindComponent (vmProxy) {
|
|
registeredComponents.push(vmProxy);
|
|
},
|
|
|
|
unbindComponent (vmProxy) {
|
|
const index = registeredComponents.indexOf(vmProxy);
|
|
if (index > -1) {
|
|
registeredComponents.splice(index, 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
let shouldActivate = false;
|
|
|
|
vue.onDeactivated(() => {
|
|
shouldActivate = true;
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
shouldActivate === true && props.autofocus === true && focus();
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
props.autofocus === true && focus();
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(vm.proxy, {
|
|
validate,
|
|
resetValidation,
|
|
submit,
|
|
reset,
|
|
focus,
|
|
getValidationComponents: () => registeredComponents
|
|
});
|
|
|
|
return () => vue.h('form', {
|
|
class: 'q-form',
|
|
ref: rootRef,
|
|
onSubmit: submit,
|
|
onReset: reset
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QFormChildMixin = {
|
|
inject: {
|
|
[ formKey ]: {
|
|
default: noop
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
disable (val) {
|
|
const $form = this.$.provides[ formKey ];
|
|
if ($form !== void 0) {
|
|
if (val === true) {
|
|
this.resetValidation();
|
|
$form.unbindComponent(this);
|
|
}
|
|
else {
|
|
$form.bindComponent(this);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
validate () {},
|
|
resetValidation () {}
|
|
},
|
|
|
|
mounted () {
|
|
// register to parent QForm
|
|
const $form = this.$.provides[ formKey ];
|
|
$form !== void 0 && this.disable !== true && $form.bindComponent(this);
|
|
},
|
|
|
|
beforeUnmount () {
|
|
// un-register from parent QForm
|
|
const $form = this.$.provides[ formKey ];
|
|
$form !== void 0 && this.disable !== true && $form.unbindComponent(this);
|
|
}
|
|
};
|
|
|
|
var QHeader = createComponent({
|
|
name: 'QHeader',
|
|
|
|
props: {
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
reveal: Boolean,
|
|
revealOffset: {
|
|
type: Number,
|
|
default: 250
|
|
},
|
|
bordered: Boolean,
|
|
elevated: Boolean,
|
|
|
|
heightHint: {
|
|
type: [ String, Number ],
|
|
default: 50
|
|
}
|
|
},
|
|
|
|
emits: [ 'reveal', 'focusin' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QHeader needs to be child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const size = vue.ref(parseInt(props.heightHint, 10));
|
|
const revealed = vue.ref(true);
|
|
|
|
const fixed = vue.computed(() =>
|
|
props.reveal === true
|
|
|| $layout.view.value.indexOf('H') > -1
|
|
|| ($q.platform.is.ios && $layout.isContainer.value === true)
|
|
);
|
|
|
|
const offset = vue.computed(() => {
|
|
if (props.modelValue !== true) {
|
|
return 0
|
|
}
|
|
if (fixed.value === true) {
|
|
return revealed.value === true ? size.value : 0
|
|
}
|
|
const offset = size.value - $layout.scroll.value.position;
|
|
return offset > 0 ? offset : 0
|
|
});
|
|
|
|
const hidden = vue.computed(() => props.modelValue !== true
|
|
|| (fixed.value === true && revealed.value !== true)
|
|
);
|
|
|
|
const revealOnFocus = vue.computed(() =>
|
|
props.modelValue === true && hidden.value === true && props.reveal === true
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-header q-layout__section--marginal '
|
|
+ (fixed.value === true ? 'fixed' : 'absolute') + '-top'
|
|
+ (props.bordered === true ? ' q-header--bordered' : '')
|
|
+ (hidden.value === true ? ' q-header--hidden' : '')
|
|
+ (props.modelValue !== true ? ' q-layout--prevent-focus' : '')
|
|
);
|
|
|
|
const style = vue.computed(() => {
|
|
const
|
|
view = $layout.rows.value.top,
|
|
css = {};
|
|
|
|
if (view[ 0 ] === 'l' && $layout.left.space === true) {
|
|
css[ $q.lang.rtl === true ? 'right' : 'left' ] = `${ $layout.left.size }px`;
|
|
}
|
|
if (view[ 2 ] === 'r' && $layout.right.space === true) {
|
|
css[ $q.lang.rtl === true ? 'left' : 'right' ] = `${ $layout.right.size }px`;
|
|
}
|
|
|
|
return css
|
|
});
|
|
|
|
function updateLayout (prop, val) {
|
|
$layout.update('header', prop, val);
|
|
}
|
|
|
|
function updateLocal (prop, val) {
|
|
if (prop.value !== val) {
|
|
prop.value = val;
|
|
}
|
|
}
|
|
|
|
function onResize ({ height }) {
|
|
updateLocal(size, height);
|
|
updateLayout('size', height);
|
|
}
|
|
|
|
function onFocusin (evt) {
|
|
if (revealOnFocus.value === true) {
|
|
updateLocal(revealed, true);
|
|
}
|
|
|
|
emit('focusin', evt);
|
|
}
|
|
|
|
vue.watch(() => props.modelValue, val => {
|
|
updateLayout('space', val);
|
|
updateLocal(revealed, true);
|
|
$layout.animate();
|
|
});
|
|
|
|
vue.watch(offset, val => {
|
|
updateLayout('offset', val);
|
|
});
|
|
|
|
vue.watch(() => props.reveal, val => {
|
|
val === false && updateLocal(revealed, props.modelValue);
|
|
});
|
|
|
|
vue.watch(revealed, val => {
|
|
$layout.animate();
|
|
emit('reveal', val);
|
|
});
|
|
|
|
vue.watch($layout.scroll, scroll => {
|
|
props.reveal === true && updateLocal(revealed,
|
|
scroll.direction === 'up'
|
|
|| scroll.position <= props.revealOffset
|
|
|| scroll.position - scroll.inflectionPoint < 100
|
|
);
|
|
});
|
|
|
|
const instance = {};
|
|
|
|
$layout.instances.header = instance;
|
|
props.modelValue === true && updateLayout('size', size.value);
|
|
updateLayout('space', props.modelValue);
|
|
updateLayout('offset', offset.value);
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
if ($layout.instances.header === instance) {
|
|
$layout.instances.header = void 0;
|
|
updateLayout('size', 0);
|
|
updateLayout('offset', 0);
|
|
updateLayout('space', false);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
const child = hUniqueSlot(slots.default, []);
|
|
|
|
props.elevated === true && child.push(
|
|
vue.h('div', {
|
|
class: 'q-layout__shadow absolute-full overflow-hidden no-pointer-events'
|
|
})
|
|
);
|
|
|
|
child.push(
|
|
vue.h(QResizeObserver, {
|
|
debounce: 0,
|
|
onResize
|
|
})
|
|
);
|
|
|
|
return vue.h('header', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
onFocusin
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
const useRatioProps = {
|
|
ratio: [ String, Number ]
|
|
};
|
|
|
|
function useRatio (props, naturalRatio) {
|
|
// return ratioStyle
|
|
return vue.computed(() => {
|
|
const ratio = Number(
|
|
props.ratio || (naturalRatio !== void 0 ? naturalRatio.value : void 0)
|
|
);
|
|
|
|
return isNaN(ratio) !== true && ratio > 0
|
|
? { paddingBottom: `${ 100 / ratio }%` }
|
|
: null
|
|
})
|
|
}
|
|
|
|
const defaultRatio = 16 / 9;
|
|
|
|
var QImg = createComponent({
|
|
name: 'QImg',
|
|
|
|
props: {
|
|
...useRatioProps,
|
|
|
|
src: String,
|
|
srcset: String,
|
|
sizes: String,
|
|
|
|
alt: String,
|
|
crossorigin: String,
|
|
decoding: String,
|
|
referrerpolicy: String,
|
|
|
|
draggable: Boolean,
|
|
|
|
loading: {
|
|
type: String,
|
|
default: 'lazy'
|
|
},
|
|
fetchpriority: {
|
|
type: String,
|
|
default: 'auto'
|
|
},
|
|
width: String,
|
|
height: String,
|
|
initialRatio: {
|
|
type: [ Number, String ],
|
|
default: defaultRatio
|
|
},
|
|
|
|
placeholderSrc: String,
|
|
|
|
fit: {
|
|
type: String,
|
|
default: 'cover'
|
|
},
|
|
position: {
|
|
type: String,
|
|
default: '50% 50%'
|
|
},
|
|
|
|
imgClass: String,
|
|
imgStyle: Object,
|
|
|
|
noSpinner: Boolean,
|
|
noNativeMenu: Boolean,
|
|
noTransition: Boolean,
|
|
|
|
spinnerColor: String,
|
|
spinnerSize: String
|
|
},
|
|
|
|
emits: [ 'load', 'error' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const naturalRatio = vue.ref(props.initialRatio);
|
|
const ratioStyle = useRatio(props, naturalRatio);
|
|
|
|
let loadTimer = null, isDestroyed = false;
|
|
|
|
const images = [
|
|
vue.ref(null),
|
|
vue.ref(getPlaceholderSrc())
|
|
];
|
|
|
|
const position = vue.ref(0);
|
|
|
|
const isLoading = vue.ref(false);
|
|
const hasError = vue.ref(false);
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-img q-img--${ props.noNativeMenu === true ? 'no-' : '' }menu`
|
|
);
|
|
|
|
const style = vue.computed(() => ({
|
|
width: props.width,
|
|
height: props.height
|
|
}));
|
|
|
|
const imgClass = vue.computed(() =>
|
|
`q-img__image ${ props.imgClass !== void 0 ? props.imgClass + ' ' : '' }`
|
|
+ `q-img__image--with${ props.noTransition === true ? 'out' : '' }-transition`
|
|
);
|
|
|
|
const imgStyle = vue.computed(() => ({
|
|
...props.imgStyle,
|
|
objectFit: props.fit,
|
|
objectPosition: props.position
|
|
}));
|
|
|
|
vue.watch(() => getCurrentSrc(), addImage);
|
|
|
|
function getCurrentSrc () {
|
|
return props.src || props.srcset || props.sizes
|
|
? {
|
|
src: props.src,
|
|
srcset: props.srcset,
|
|
sizes: props.sizes
|
|
}
|
|
: null
|
|
}
|
|
|
|
function getPlaceholderSrc () {
|
|
return props.placeholderSrc !== void 0
|
|
? { src: props.placeholderSrc }
|
|
: null
|
|
}
|
|
|
|
function addImage (imgProps) {
|
|
if (loadTimer !== null) {
|
|
clearTimeout(loadTimer);
|
|
loadTimer = null;
|
|
}
|
|
|
|
hasError.value = false;
|
|
|
|
if (imgProps === null) {
|
|
isLoading.value = false;
|
|
images[ position.value ^ 1 ].value = getPlaceholderSrc();
|
|
}
|
|
else {
|
|
isLoading.value = true;
|
|
}
|
|
|
|
images[ position.value ].value = imgProps;
|
|
}
|
|
|
|
function onLoad ({ target }) {
|
|
if (isDestroyed === true) { return }
|
|
|
|
if (loadTimer !== null) {
|
|
clearTimeout(loadTimer);
|
|
loadTimer = null;
|
|
}
|
|
|
|
naturalRatio.value = target.naturalHeight === 0
|
|
? 0.5
|
|
: target.naturalWidth / target.naturalHeight;
|
|
|
|
waitForCompleteness(target, 1);
|
|
}
|
|
|
|
function waitForCompleteness (target, count) {
|
|
// protect against running forever
|
|
if (isDestroyed === true || count === 1000) { return }
|
|
|
|
if (target.complete === true) {
|
|
onReady(target);
|
|
}
|
|
else {
|
|
loadTimer = setTimeout(() => {
|
|
loadTimer = null;
|
|
waitForCompleteness(target, count + 1);
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
function onReady (img) {
|
|
if (isDestroyed === true) { return }
|
|
|
|
position.value = position.value ^ 1;
|
|
images[ position.value ].value = null;
|
|
isLoading.value = false;
|
|
hasError.value = false;
|
|
emit('load', img.currentSrc || img.src);
|
|
}
|
|
|
|
function onError (err) {
|
|
if (loadTimer !== null) {
|
|
clearTimeout(loadTimer);
|
|
loadTimer = null;
|
|
}
|
|
|
|
isLoading.value = false;
|
|
hasError.value = true;
|
|
images[ position.value ].value = null;
|
|
images[ position.value ^ 1 ].value = getPlaceholderSrc();
|
|
emit('error', err);
|
|
}
|
|
|
|
function getImage (index) {
|
|
const img = images[ index ].value;
|
|
|
|
const data = {
|
|
key: 'img_' + index,
|
|
class: imgClass.value,
|
|
style: imgStyle.value,
|
|
crossorigin: props.crossorigin,
|
|
decoding: props.decoding,
|
|
referrerpolicy: props.referrerpolicy,
|
|
height: props.height,
|
|
width: props.width,
|
|
loading: props.loading,
|
|
fetchpriority: props.fetchpriority,
|
|
'aria-hidden': 'true',
|
|
draggable: props.draggable,
|
|
...img
|
|
};
|
|
|
|
if (position.value === index) {
|
|
data.class += ' q-img__image--waiting';
|
|
Object.assign(data, { onLoad, onError });
|
|
}
|
|
else {
|
|
data.class += ' q-img__image--loaded';
|
|
}
|
|
|
|
return vue.h(
|
|
'div',
|
|
{ class: 'q-img__container absolute-full', key: 'img' + index },
|
|
vue.h('img', data)
|
|
)
|
|
}
|
|
|
|
function getContent () {
|
|
if (isLoading.value !== true) {
|
|
return vue.h('div', {
|
|
key: 'content',
|
|
class: 'q-img__content absolute-full q-anchor--skip'
|
|
}, hSlot(slots[ hasError.value === true ? 'error' : 'default' ]))
|
|
}
|
|
|
|
return vue.h('div', {
|
|
key: 'loading',
|
|
class: 'q-img__loading absolute-full flex flex-center'
|
|
}, (
|
|
slots.loading !== void 0
|
|
? slots.loading()
|
|
: (
|
|
props.noSpinner === true
|
|
? void 0
|
|
: [
|
|
vue.h(QSpinner, {
|
|
color: props.spinnerColor,
|
|
size: props.spinnerSize
|
|
})
|
|
]
|
|
)
|
|
))
|
|
}
|
|
|
|
{
|
|
{
|
|
addImage(getCurrentSrc());
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
isDestroyed = true;
|
|
|
|
if (loadTimer !== null) {
|
|
clearTimeout(loadTimer);
|
|
loadTimer = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
return () => {
|
|
const content = [];
|
|
|
|
if (ratioStyle.value !== null) {
|
|
content.push(
|
|
vue.h('div', { key: 'filler', style: ratioStyle.value })
|
|
);
|
|
}
|
|
|
|
if (hasError.value !== true) {
|
|
if (images[ 0 ].value !== null) {
|
|
content.push(getImage(0));
|
|
}
|
|
|
|
if (images[ 1 ].value !== null) {
|
|
content.push(getImage(1));
|
|
}
|
|
}
|
|
|
|
content.push(
|
|
vue.h(vue.Transition, { name: 'q-transition--fade' }, getContent)
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
role: 'img',
|
|
'aria-label': props.alt
|
|
}, content)
|
|
}
|
|
}
|
|
});
|
|
|
|
const { passive: passive$3 } = listenOpts;
|
|
|
|
var QInfiniteScroll = createComponent({
|
|
name: 'QInfiniteScroll',
|
|
|
|
props: {
|
|
offset: {
|
|
type: Number,
|
|
default: 500
|
|
},
|
|
|
|
debounce: {
|
|
type: [ String, Number ],
|
|
default: 100
|
|
},
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
},
|
|
|
|
initialIndex: Number,
|
|
|
|
disable: Boolean,
|
|
reverse: Boolean
|
|
},
|
|
|
|
emits: [ 'load' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const isFetching = vue.ref(false);
|
|
const isWorking = vue.ref(true);
|
|
const rootRef = vue.ref(null);
|
|
const loadingRef = vue.ref(null);
|
|
|
|
let index = props.initialIndex || 0;
|
|
let localScrollTarget, poll;
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-infinite-scroll__loading'
|
|
+ (isFetching.value === true ? '' : ' invisible')
|
|
);
|
|
|
|
function immediatePoll () {
|
|
if (props.disable === true || isFetching.value === true || isWorking.value === false) {
|
|
return
|
|
}
|
|
|
|
const
|
|
scrollHeight = getScrollHeight(localScrollTarget),
|
|
scrollPosition = getVerticalScrollPosition(localScrollTarget),
|
|
containerHeight = height(localScrollTarget);
|
|
|
|
if (props.reverse === false) {
|
|
if (Math.round(scrollPosition + containerHeight + props.offset) >= Math.round(scrollHeight)) {
|
|
trigger();
|
|
}
|
|
}
|
|
else if (Math.round(scrollPosition) <= props.offset) {
|
|
trigger();
|
|
}
|
|
}
|
|
|
|
function trigger () {
|
|
if (props.disable === true || isFetching.value === true || isWorking.value === false) {
|
|
return
|
|
}
|
|
|
|
index++;
|
|
isFetching.value = true;
|
|
|
|
const heightBefore = getScrollHeight(localScrollTarget);
|
|
|
|
emit('load', index, isDone => {
|
|
if (isWorking.value === true) {
|
|
isFetching.value = false;
|
|
vue.nextTick(() => {
|
|
if (props.reverse === true) {
|
|
const
|
|
heightAfter = getScrollHeight(localScrollTarget),
|
|
scrollPosition = getVerticalScrollPosition(localScrollTarget),
|
|
heightDifference = heightAfter - heightBefore;
|
|
|
|
setVerticalScrollPosition(localScrollTarget, scrollPosition + heightDifference);
|
|
}
|
|
|
|
if (isDone === true) {
|
|
stop();
|
|
}
|
|
else if (rootRef.value) {
|
|
rootRef.value.closest('body') && poll();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function reset () {
|
|
index = 0;
|
|
}
|
|
|
|
function resume () {
|
|
if (isWorking.value === false) {
|
|
isWorking.value = true;
|
|
localScrollTarget.addEventListener('scroll', poll, passive$3);
|
|
}
|
|
|
|
immediatePoll();
|
|
}
|
|
|
|
function stop () {
|
|
if (isWorking.value === true) {
|
|
isWorking.value = false;
|
|
isFetching.value = false;
|
|
localScrollTarget.removeEventListener('scroll', poll, passive$3);
|
|
if (poll !== void 0 && poll.cancel !== void 0) {
|
|
poll.cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateScrollTarget () {
|
|
if (localScrollTarget && isWorking.value === true) {
|
|
localScrollTarget.removeEventListener('scroll', poll, passive$3);
|
|
}
|
|
|
|
localScrollTarget = getScrollTarget(rootRef.value, props.scrollTarget);
|
|
|
|
if (isWorking.value === true) {
|
|
localScrollTarget.addEventListener('scroll', poll, passive$3);
|
|
|
|
if (props.reverse === true) {
|
|
const
|
|
scrollHeight = getScrollHeight(localScrollTarget),
|
|
containerHeight = height(localScrollTarget);
|
|
|
|
setVerticalScrollPosition(localScrollTarget, scrollHeight - containerHeight);
|
|
}
|
|
|
|
immediatePoll();
|
|
}
|
|
}
|
|
|
|
function setIndex (newIndex) {
|
|
index = newIndex;
|
|
}
|
|
|
|
function setDebounce (val) {
|
|
val = parseInt(val, 10);
|
|
|
|
const oldPoll = poll;
|
|
|
|
poll = val <= 0
|
|
? immediatePoll
|
|
: debounce(immediatePoll, isNaN(val) === true ? 100 : val);
|
|
|
|
if (localScrollTarget && isWorking.value === true) {
|
|
if (oldPoll !== void 0) {
|
|
localScrollTarget.removeEventListener('scroll', oldPoll, passive$3);
|
|
}
|
|
|
|
localScrollTarget.addEventListener('scroll', poll, passive$3);
|
|
}
|
|
}
|
|
|
|
function updateSvgAnimations (isRetry) {
|
|
if (renderLoadingSlot.value === true) {
|
|
if (loadingRef.value === null) {
|
|
isRetry !== true && vue.nextTick(() => { updateSvgAnimations(true); });
|
|
return
|
|
}
|
|
|
|
// we need to pause svg animations (if any) when hiding
|
|
// otherwise the browser will keep on recalculating the style
|
|
const action = `${ isFetching.value === true ? 'un' : '' }pauseAnimations`;
|
|
Array.from(loadingRef.value.getElementsByTagName('svg')).forEach(el => {
|
|
el[ action ]();
|
|
});
|
|
}
|
|
}
|
|
|
|
const renderLoadingSlot = vue.computed(() => props.disable !== true && isWorking.value === true);
|
|
|
|
vue.watch([ isFetching, renderLoadingSlot ], () => { updateSvgAnimations(); });
|
|
|
|
vue.watch(() => props.disable, val => {
|
|
if (val === true) { stop(); }
|
|
else { resume(); }
|
|
});
|
|
|
|
vue.watch(() => props.reverse, () => {
|
|
if (isFetching.value === false && isWorking.value === true) {
|
|
immediatePoll();
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.scrollTarget, updateScrollTarget);
|
|
vue.watch(() => props.debounce, setDebounce);
|
|
|
|
let scrollPos = false;
|
|
|
|
vue.onActivated(() => {
|
|
if (scrollPos !== false && localScrollTarget) {
|
|
setVerticalScrollPosition(localScrollTarget, scrollPos);
|
|
}
|
|
});
|
|
|
|
vue.onDeactivated(() => {
|
|
scrollPos = localScrollTarget
|
|
? getVerticalScrollPosition(localScrollTarget)
|
|
: false;
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
if (isWorking.value === true) {
|
|
localScrollTarget.removeEventListener('scroll', poll, passive$3);
|
|
}
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
setDebounce(props.debounce);
|
|
updateScrollTarget();
|
|
|
|
isFetching.value === false && updateSvgAnimations();
|
|
});
|
|
|
|
// expose public methods
|
|
const vm = vue.getCurrentInstance();
|
|
Object.assign(vm.proxy, {
|
|
poll: () => { poll !== void 0 && poll(); },
|
|
trigger, stop, reset, resume, setIndex
|
|
});
|
|
|
|
return () => {
|
|
const child = hUniqueSlot(slots.default, []);
|
|
|
|
if (renderLoadingSlot.value === true) {
|
|
child[ props.reverse === false ? 'push' : 'unshift' ](
|
|
vue.h('div', { ref: loadingRef, class: classes.value }, hSlot(slots.loading))
|
|
);
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: 'q-infinite-scroll',
|
|
ref: rootRef
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QInnerLoading = createComponent({
|
|
name: 'QInnerLoading',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useTransitionProps,
|
|
|
|
showing: Boolean,
|
|
color: String,
|
|
|
|
size: {
|
|
type: [ String, Number ],
|
|
default: 42
|
|
},
|
|
|
|
label: String,
|
|
labelClass: String,
|
|
labelStyle: [ String, Array, Object ]
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const { transitionProps, transitionStyle } = useTransition(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-inner-loading absolute-full column flex-center'
|
|
+ (isDark.value === true ? ' q-inner-loading--dark' : '')
|
|
);
|
|
|
|
const labelClass = vue.computed(() =>
|
|
'q-inner-loading__label'
|
|
+ (props.labelClass !== void 0 ? ` ${ props.labelClass }` : '')
|
|
);
|
|
|
|
function getInner () {
|
|
const child = [
|
|
vue.h(QSpinner, {
|
|
size: props.size,
|
|
color: props.color
|
|
})
|
|
];
|
|
|
|
if (props.label !== void 0) {
|
|
child.push(
|
|
vue.h('div', {
|
|
class: labelClass.value,
|
|
style: props.labelStyle
|
|
}, [ props.label ])
|
|
);
|
|
}
|
|
|
|
return child
|
|
}
|
|
|
|
function getContent () {
|
|
return props.showing === true
|
|
? vue.h(
|
|
'div',
|
|
{ class: classes.value, style: transitionStyle.value },
|
|
slots.default !== void 0
|
|
? slots.default()
|
|
: getInner()
|
|
)
|
|
: null
|
|
}
|
|
|
|
return () => vue.h(vue.Transition, transitionProps.value, getContent)
|
|
}
|
|
});
|
|
|
|
// leave NAMED_MASKS at top of file (code referenced from docs)
|
|
const NAMED_MASKS = {
|
|
date: '####/##/##',
|
|
datetime: '####/##/## ##:##',
|
|
time: '##:##',
|
|
fulltime: '##:##:##',
|
|
phone: '(###) ### - ####',
|
|
card: '#### #### #### ####'
|
|
};
|
|
|
|
const TOKENS = {
|
|
'#': { pattern: '[\\d]', negate: '[^\\d]' },
|
|
|
|
S: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]' },
|
|
N: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]' },
|
|
|
|
A: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleUpperCase() },
|
|
a: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleLowerCase() },
|
|
|
|
X: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleUpperCase() },
|
|
x: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleLowerCase() }
|
|
};
|
|
|
|
const KEYS = Object.keys(TOKENS);
|
|
KEYS.forEach(key => {
|
|
TOKENS[ key ].regex = new RegExp(TOKENS[ key ].pattern);
|
|
});
|
|
|
|
const
|
|
tokenRegexMask = new RegExp('\\\\([^.*+?^${}()|([\\]])|([.*+?^${}()|[\\]])|([' + KEYS.join('') + '])|(.)', 'g'),
|
|
escRegex = /[.*+?^${}()|[\]\\]/g;
|
|
|
|
const MARKER = String.fromCharCode(1);
|
|
|
|
const useMaskProps = {
|
|
mask: String,
|
|
reverseFillMask: Boolean,
|
|
fillMask: [ Boolean, String ],
|
|
unmaskedValue: Boolean
|
|
};
|
|
|
|
function useMask (props, emit, emitValue, inputRef) {
|
|
let maskMarked, maskReplaced, computedMask, computedUnmask, pastedTextStart, selectionAnchor;
|
|
|
|
const hasMask = vue.ref(null);
|
|
const innerValue = vue.ref(getInitialMaskedValue());
|
|
|
|
function getIsTypeText () {
|
|
return props.autogrow === true
|
|
|| [ 'textarea', 'text', 'search', 'url', 'tel', 'password' ].includes(props.type)
|
|
}
|
|
|
|
vue.watch(() => props.type + props.autogrow, updateMaskInternals);
|
|
|
|
vue.watch(() => props.mask, v => {
|
|
if (v !== void 0) {
|
|
updateMaskValue(innerValue.value, true);
|
|
}
|
|
else {
|
|
const val = unmaskValue(innerValue.value);
|
|
updateMaskInternals();
|
|
props.modelValue !== val && emit('update:modelValue', val);
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.fillMask + props.reverseFillMask, () => {
|
|
hasMask.value === true && updateMaskValue(innerValue.value, true);
|
|
});
|
|
|
|
vue.watch(() => props.unmaskedValue, () => {
|
|
hasMask.value === true && updateMaskValue(innerValue.value);
|
|
});
|
|
|
|
function getInitialMaskedValue () {
|
|
updateMaskInternals();
|
|
|
|
if (hasMask.value === true) {
|
|
const masked = maskValue(unmaskValue(props.modelValue));
|
|
|
|
return props.fillMask !== false
|
|
? fillWithMask(masked)
|
|
: masked
|
|
}
|
|
|
|
return props.modelValue
|
|
}
|
|
|
|
function getPaddedMaskMarked (size) {
|
|
if (size < maskMarked.length) {
|
|
return maskMarked.slice(-size)
|
|
}
|
|
|
|
let pad = '', localMaskMarked = maskMarked;
|
|
const padPos = localMaskMarked.indexOf(MARKER);
|
|
|
|
if (padPos > -1) {
|
|
for (let i = size - localMaskMarked.length; i > 0; i--) {
|
|
pad += MARKER;
|
|
}
|
|
|
|
localMaskMarked = localMaskMarked.slice(0, padPos) + pad + localMaskMarked.slice(padPos);
|
|
}
|
|
|
|
return localMaskMarked
|
|
}
|
|
|
|
function updateMaskInternals () {
|
|
hasMask.value = props.mask !== void 0
|
|
&& props.mask.length !== 0
|
|
&& getIsTypeText();
|
|
|
|
if (hasMask.value === false) {
|
|
computedUnmask = void 0;
|
|
maskMarked = '';
|
|
maskReplaced = '';
|
|
return
|
|
}
|
|
|
|
const
|
|
localComputedMask = NAMED_MASKS[ props.mask ] === void 0
|
|
? props.mask
|
|
: NAMED_MASKS[ props.mask ],
|
|
fillChar = typeof props.fillMask === 'string' && props.fillMask.length !== 0
|
|
? props.fillMask.slice(0, 1)
|
|
: '_',
|
|
fillCharEscaped = fillChar.replace(escRegex, '\\$&'),
|
|
unmask = [],
|
|
extract = [],
|
|
mask = [];
|
|
|
|
let
|
|
firstMatch = props.reverseFillMask === true,
|
|
unmaskChar = '',
|
|
negateChar = '';
|
|
|
|
localComputedMask.replace(tokenRegexMask, (_, char1, esc, token, char2) => {
|
|
if (token !== void 0) {
|
|
const c = TOKENS[ token ];
|
|
mask.push(c);
|
|
negateChar = c.negate;
|
|
if (firstMatch === true) {
|
|
extract.push('(?:' + negateChar + '+)?(' + c.pattern + '+)?(?:' + negateChar + '+)?(' + c.pattern + '+)?');
|
|
firstMatch = false;
|
|
}
|
|
extract.push('(?:' + negateChar + '+)?(' + c.pattern + ')?');
|
|
}
|
|
else if (esc !== void 0) {
|
|
unmaskChar = '\\' + (esc === '\\' ? '' : esc);
|
|
mask.push(esc);
|
|
unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?');
|
|
}
|
|
else {
|
|
const c = char1 !== void 0 ? char1 : char2;
|
|
unmaskChar = c === '\\' ? '\\\\\\\\' : c.replace(escRegex, '\\\\$&');
|
|
mask.push(c);
|
|
unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?');
|
|
}
|
|
});
|
|
|
|
const
|
|
unmaskMatcher = new RegExp(
|
|
'^'
|
|
+ unmask.join('')
|
|
+ '(' + (unmaskChar === '' ? '.' : '[^' + unmaskChar + ']') + '+)?'
|
|
+ (unmaskChar === '' ? '' : '[' + unmaskChar + ']*') + '$'
|
|
),
|
|
extractLast = extract.length - 1,
|
|
extractMatcher = extract.map((re, index) => {
|
|
if (index === 0 && props.reverseFillMask === true) {
|
|
return new RegExp('^' + fillCharEscaped + '*' + re)
|
|
}
|
|
else if (index === extractLast) {
|
|
return new RegExp(
|
|
'^' + re
|
|
+ '(' + (negateChar === '' ? '.' : negateChar) + '+)?'
|
|
+ (props.reverseFillMask === true ? '$' : fillCharEscaped + '*')
|
|
)
|
|
}
|
|
|
|
return new RegExp('^' + re)
|
|
});
|
|
|
|
computedMask = mask;
|
|
computedUnmask = val => {
|
|
const unmaskMatch = unmaskMatcher.exec(props.reverseFillMask === true ? val : val.slice(0, mask.length + 1));
|
|
if (unmaskMatch !== null) {
|
|
val = unmaskMatch.slice(1).join('');
|
|
}
|
|
|
|
const
|
|
extractMatch = [],
|
|
extractMatcherLength = extractMatcher.length;
|
|
|
|
for (let i = 0, str = val; i < extractMatcherLength; i++) {
|
|
const m = extractMatcher[ i ].exec(str);
|
|
|
|
if (m === null) {
|
|
break
|
|
}
|
|
|
|
str = str.slice(m.shift().length);
|
|
extractMatch.push(...m);
|
|
}
|
|
if (extractMatch.length !== 0) {
|
|
return extractMatch.join('')
|
|
}
|
|
|
|
return val
|
|
};
|
|
maskMarked = mask.map(v => (typeof v === 'string' ? v : MARKER)).join('');
|
|
maskReplaced = maskMarked.split(MARKER).join(fillChar);
|
|
}
|
|
|
|
function updateMaskValue (rawVal, updateMaskInternalsFlag, inputType) {
|
|
const
|
|
inp = inputRef.value,
|
|
end = inp.selectionEnd,
|
|
endReverse = inp.value.length - end,
|
|
unmasked = unmaskValue(rawVal);
|
|
|
|
// Update here so unmask uses the original fillChar
|
|
updateMaskInternalsFlag === true && updateMaskInternals();
|
|
|
|
const
|
|
preMasked = maskValue(unmasked),
|
|
masked = props.fillMask !== false
|
|
? fillWithMask(preMasked)
|
|
: preMasked,
|
|
changed = innerValue.value !== masked;
|
|
|
|
// We want to avoid "flickering" so we set value immediately
|
|
inp.value !== masked && (inp.value = masked);
|
|
|
|
changed === true && (innerValue.value = masked);
|
|
|
|
document.activeElement === inp && vue.nextTick(() => {
|
|
if (masked === maskReplaced) {
|
|
const cursor = props.reverseFillMask === true ? maskReplaced.length : 0;
|
|
inp.setSelectionRange(cursor, cursor, 'forward');
|
|
|
|
return
|
|
}
|
|
|
|
if (inputType === 'insertFromPaste' && props.reverseFillMask !== true) {
|
|
const maxEnd = inp.selectionEnd;
|
|
let cursor = end - 1;
|
|
// each non-marker char means we move once to right
|
|
for (let i = pastedTextStart; i <= cursor && i < maxEnd; i++) {
|
|
if (maskMarked[ i ] !== MARKER) {
|
|
cursor++;
|
|
}
|
|
}
|
|
moveCursor.right(inp, cursor);
|
|
|
|
return
|
|
}
|
|
|
|
if ([ 'deleteContentBackward', 'deleteContentForward' ].indexOf(inputType) > -1) {
|
|
const cursor = props.reverseFillMask === true
|
|
? (
|
|
end === 0
|
|
? (masked.length > preMasked.length ? 1 : 0)
|
|
: Math.max(0, masked.length - (masked === maskReplaced ? 0 : Math.min(preMasked.length, endReverse) + 1)) + 1
|
|
)
|
|
: end;
|
|
|
|
inp.setSelectionRange(cursor, cursor, 'forward');
|
|
return
|
|
}
|
|
|
|
if (props.reverseFillMask === true) {
|
|
if (changed === true) {
|
|
const cursor = Math.max(0, masked.length - (masked === maskReplaced ? 0 : Math.min(preMasked.length, endReverse + 1)));
|
|
|
|
if (cursor === 1 && end === 1) {
|
|
inp.setSelectionRange(cursor, cursor, 'forward');
|
|
}
|
|
else {
|
|
moveCursor.rightReverse(inp, cursor);
|
|
}
|
|
}
|
|
else {
|
|
const cursor = masked.length - endReverse;
|
|
inp.setSelectionRange(cursor, cursor, 'backward');
|
|
}
|
|
}
|
|
else {
|
|
if (changed === true) {
|
|
const cursor = Math.max(0, maskMarked.indexOf(MARKER), Math.min(preMasked.length, end) - 1);
|
|
moveCursor.right(inp, cursor);
|
|
}
|
|
else {
|
|
const cursor = end - 1;
|
|
moveCursor.right(inp, cursor);
|
|
}
|
|
}
|
|
});
|
|
|
|
const val = props.unmaskedValue === true
|
|
? unmaskValue(masked)
|
|
: masked;
|
|
|
|
String(props.modelValue) !== val && emitValue(val, true);
|
|
}
|
|
|
|
function moveCursorForPaste (inp, start, end) {
|
|
const preMasked = maskValue(unmaskValue(inp.value));
|
|
|
|
start = Math.max(0, maskMarked.indexOf(MARKER), Math.min(preMasked.length, start));
|
|
pastedTextStart = start;
|
|
|
|
inp.setSelectionRange(start, end, 'forward');
|
|
}
|
|
|
|
const moveCursor = {
|
|
left (inp, cursor) {
|
|
const noMarkBefore = maskMarked.slice(cursor - 1).indexOf(MARKER) === -1;
|
|
let i = Math.max(0, cursor - 1);
|
|
|
|
for (; i >= 0; i--) {
|
|
if (maskMarked[ i ] === MARKER) {
|
|
cursor = i;
|
|
noMarkBefore === true && cursor++;
|
|
break
|
|
}
|
|
}
|
|
|
|
if (
|
|
i < 0
|
|
&& maskMarked[ cursor ] !== void 0
|
|
&& maskMarked[ cursor ] !== MARKER
|
|
) {
|
|
return moveCursor.right(inp, 0)
|
|
}
|
|
|
|
cursor >= 0 && inp.setSelectionRange(cursor, cursor, 'backward');
|
|
},
|
|
|
|
right (inp, cursor) {
|
|
const limit = inp.value.length;
|
|
let i = Math.min(limit, cursor + 1);
|
|
|
|
for (; i <= limit; i++) {
|
|
if (maskMarked[ i ] === MARKER) {
|
|
cursor = i;
|
|
break
|
|
}
|
|
else if (maskMarked[ i - 1 ] === MARKER) {
|
|
cursor = i;
|
|
}
|
|
}
|
|
|
|
if (
|
|
i > limit
|
|
&& maskMarked[ cursor - 1 ] !== void 0
|
|
&& maskMarked[ cursor - 1 ] !== MARKER
|
|
) {
|
|
return moveCursor.left(inp, limit)
|
|
}
|
|
|
|
inp.setSelectionRange(cursor, cursor, 'forward');
|
|
},
|
|
|
|
leftReverse (inp, cursor) {
|
|
const
|
|
localMaskMarked = getPaddedMaskMarked(inp.value.length);
|
|
let i = Math.max(0, cursor - 1);
|
|
|
|
for (; i >= 0; i--) {
|
|
if (localMaskMarked[ i - 1 ] === MARKER) {
|
|
cursor = i;
|
|
break
|
|
}
|
|
else if (localMaskMarked[ i ] === MARKER) {
|
|
cursor = i;
|
|
if (i === 0) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
i < 0
|
|
&& localMaskMarked[ cursor ] !== void 0
|
|
&& localMaskMarked[ cursor ] !== MARKER
|
|
) {
|
|
return moveCursor.rightReverse(inp, 0)
|
|
}
|
|
|
|
cursor >= 0 && inp.setSelectionRange(cursor, cursor, 'backward');
|
|
},
|
|
|
|
rightReverse (inp, cursor) {
|
|
const
|
|
limit = inp.value.length,
|
|
localMaskMarked = getPaddedMaskMarked(limit),
|
|
noMarkBefore = localMaskMarked.slice(0, cursor + 1).indexOf(MARKER) === -1;
|
|
let i = Math.min(limit, cursor + 1);
|
|
|
|
for (; i <= limit; i++) {
|
|
if (localMaskMarked[ i - 1 ] === MARKER) {
|
|
cursor = i;
|
|
cursor > 0 && noMarkBefore === true && cursor--;
|
|
break
|
|
}
|
|
}
|
|
|
|
if (
|
|
i > limit
|
|
&& localMaskMarked[ cursor - 1 ] !== void 0
|
|
&& localMaskMarked[ cursor - 1 ] !== MARKER
|
|
) {
|
|
return moveCursor.leftReverse(inp, limit)
|
|
}
|
|
|
|
inp.setSelectionRange(cursor, cursor, 'forward');
|
|
}
|
|
};
|
|
|
|
function onMaskedClick (e) {
|
|
emit('click', e);
|
|
|
|
selectionAnchor = void 0;
|
|
}
|
|
|
|
function onMaskedKeydown (e) {
|
|
emit('keydown', e);
|
|
|
|
if (
|
|
shouldIgnoreKey(e) === true
|
|
|| e.altKey === true // let browser handle these
|
|
) {
|
|
return
|
|
}
|
|
|
|
const
|
|
inp = inputRef.value,
|
|
start = inp.selectionStart,
|
|
end = inp.selectionEnd;
|
|
|
|
if (!e.shiftKey) {
|
|
selectionAnchor = void 0;
|
|
}
|
|
|
|
if (e.keyCode === 37 || e.keyCode === 39) { // Left / Right
|
|
if (e.shiftKey && selectionAnchor === void 0) {
|
|
selectionAnchor = inp.selectionDirection === 'forward' ? start : end;
|
|
}
|
|
|
|
const fn = moveCursor[ (e.keyCode === 39 ? 'right' : 'left') + (props.reverseFillMask === true ? 'Reverse' : '') ];
|
|
|
|
e.preventDefault();
|
|
fn(inp, selectionAnchor === start ? end : start);
|
|
|
|
if (e.shiftKey) {
|
|
const cursor = inp.selectionStart;
|
|
inp.setSelectionRange(Math.min(selectionAnchor, cursor), Math.max(selectionAnchor, cursor), 'forward');
|
|
}
|
|
}
|
|
else if (
|
|
e.keyCode === 8 // Backspace
|
|
&& props.reverseFillMask !== true
|
|
&& start === end
|
|
) {
|
|
moveCursor.left(inp, start);
|
|
inp.setSelectionRange(inp.selectionStart, end, 'backward');
|
|
}
|
|
else if (
|
|
e.keyCode === 46 // Delete
|
|
&& props.reverseFillMask === true
|
|
&& start === end
|
|
) {
|
|
moveCursor.rightReverse(inp, end);
|
|
inp.setSelectionRange(start, inp.selectionEnd, 'forward');
|
|
}
|
|
}
|
|
|
|
function maskValue (val) {
|
|
if (val === void 0 || val === null || val === '') { return '' }
|
|
|
|
if (props.reverseFillMask === true) {
|
|
return maskValueReverse(val)
|
|
}
|
|
|
|
const mask = computedMask;
|
|
|
|
let valIndex = 0, output = '';
|
|
|
|
for (let maskIndex = 0; maskIndex < mask.length; maskIndex++) {
|
|
const
|
|
valChar = val[ valIndex ],
|
|
maskDef = mask[ maskIndex ];
|
|
|
|
if (typeof maskDef === 'string') {
|
|
output += maskDef;
|
|
valChar === maskDef && valIndex++;
|
|
}
|
|
else if (valChar !== void 0 && maskDef.regex.test(valChar)) {
|
|
output += maskDef.transform !== void 0
|
|
? maskDef.transform(valChar)
|
|
: valChar;
|
|
valIndex++;
|
|
}
|
|
else {
|
|
return output
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
function maskValueReverse (val) {
|
|
const
|
|
mask = computedMask,
|
|
firstTokenIndex = maskMarked.indexOf(MARKER);
|
|
|
|
let valIndex = val.length - 1, output = '';
|
|
|
|
for (let maskIndex = mask.length - 1; maskIndex >= 0 && valIndex > -1; maskIndex--) {
|
|
const maskDef = mask[ maskIndex ];
|
|
|
|
let valChar = val[ valIndex ];
|
|
|
|
if (typeof maskDef === 'string') {
|
|
output = maskDef + output;
|
|
valChar === maskDef && valIndex--;
|
|
}
|
|
else if (valChar !== void 0 && maskDef.regex.test(valChar)) {
|
|
do {
|
|
output = (maskDef.transform !== void 0 ? maskDef.transform(valChar) : valChar) + output;
|
|
valIndex--;
|
|
valChar = val[ valIndex ];
|
|
// eslint-disable-next-line no-unmodified-loop-condition
|
|
} while (firstTokenIndex === maskIndex && valChar !== void 0 && maskDef.regex.test(valChar))
|
|
}
|
|
else {
|
|
return output
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
function unmaskValue (val) {
|
|
return typeof val !== 'string' || computedUnmask === void 0
|
|
? (typeof val === 'number' ? computedUnmask('' + val) : val)
|
|
: computedUnmask(val)
|
|
}
|
|
|
|
function fillWithMask (val) {
|
|
if (maskReplaced.length - val.length <= 0) {
|
|
return val
|
|
}
|
|
|
|
return props.reverseFillMask === true && val.length !== 0
|
|
? maskReplaced.slice(0, -val.length) + val
|
|
: val + maskReplaced.slice(val.length)
|
|
}
|
|
|
|
return {
|
|
innerValue,
|
|
hasMask,
|
|
moveCursorForPaste,
|
|
updateMaskValue,
|
|
onMaskedKeydown,
|
|
onMaskedClick
|
|
}
|
|
}
|
|
|
|
const isJapanese = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/;
|
|
const isChinese = /[\u4e00-\u9fff\u3400-\u4dbf\u{20000}-\u{2a6df}\u{2a700}-\u{2b73f}\u{2b740}-\u{2b81f}\u{2b820}-\u{2ceaf}\uf900-\ufaff\u3300-\u33ff\ufe30-\ufe4f\uf900-\ufaff\u{2f800}-\u{2fa1f}]/u;
|
|
const isKorean = /[\u3131-\u314e\u314f-\u3163\uac00-\ud7a3]/;
|
|
const isPlainText = /[a-z0-9_ -]$/i;
|
|
|
|
function useKeyComposition (onInput) {
|
|
return function onComposition (e) {
|
|
if (e.type === 'compositionend' || e.type === 'change') {
|
|
if (e.target.qComposing !== true) { return }
|
|
e.target.qComposing = false;
|
|
onInput(e);
|
|
}
|
|
else if (
|
|
e.type === 'compositionupdate'
|
|
&& e.target.qComposing !== true
|
|
&& typeof e.data === 'string'
|
|
) {
|
|
const isComposing = client.is.firefox === true
|
|
? isPlainText.test(e.data) === false
|
|
: isJapanese.test(e.data) === true || isChinese.test(e.data) === true || isKorean.test(e.data) === true;
|
|
|
|
if (isComposing === true) {
|
|
e.target.qComposing = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var QInput = createComponent({
|
|
name: 'QInput',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useFieldProps,
|
|
...useMaskProps,
|
|
...useFormProps,
|
|
|
|
modelValue: { required: false },
|
|
|
|
shadowText: String,
|
|
|
|
type: {
|
|
type: String,
|
|
default: 'text'
|
|
},
|
|
|
|
debounce: [ String, Number ],
|
|
|
|
autogrow: Boolean, // makes a textarea
|
|
|
|
inputClass: [ Array, String, Object ],
|
|
inputStyle: [ Array, String, Object ]
|
|
},
|
|
|
|
emits: [
|
|
...useFieldEmits,
|
|
'paste', 'change',
|
|
'keydown', 'click', 'animationend'
|
|
],
|
|
|
|
setup (props, { emit, attrs }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const temp = {};
|
|
let emitCachedValue = NaN, typedNumber, stopValueWatcher, emitTimer = null, emitValueFn;
|
|
|
|
const inputRef = vue.ref(null);
|
|
const nameProp = useFormInputNameAttr(props);
|
|
|
|
const {
|
|
innerValue,
|
|
hasMask,
|
|
moveCursorForPaste,
|
|
updateMaskValue,
|
|
onMaskedKeydown,
|
|
onMaskedClick
|
|
} = useMask(props, emit, emitValue, inputRef);
|
|
|
|
const formDomProps = useFileFormDomProps(props, /* type guard */ true);
|
|
const hasValue = vue.computed(() => fieldValueIsFilled(innerValue.value));
|
|
|
|
const onComposition = useKeyComposition(onInput);
|
|
|
|
const state = useFieldState();
|
|
|
|
const isTextarea = vue.computed(() =>
|
|
props.type === 'textarea' || props.autogrow === true
|
|
);
|
|
|
|
const isTypeText = vue.computed(() =>
|
|
isTextarea.value === true
|
|
|| [ 'text', 'search', 'url', 'tel', 'password' ].includes(props.type)
|
|
);
|
|
|
|
const onEvents = vue.computed(() => {
|
|
const evt = {
|
|
...state.splitAttrs.listeners.value,
|
|
onInput,
|
|
onPaste,
|
|
// Safari < 10.2 & UIWebView doesn't fire compositionend when
|
|
// switching focus before confirming composition choice
|
|
// this also fixes the issue where some browsers e.g. iOS Chrome
|
|
// fires "change" instead of "input" on autocomplete.
|
|
onChange,
|
|
onBlur: onFinishEditing,
|
|
onFocus: stop
|
|
};
|
|
|
|
evt.onCompositionstart = evt.onCompositionupdate = evt.onCompositionend = onComposition;
|
|
|
|
if (hasMask.value === true) {
|
|
evt.onKeydown = onMaskedKeydown;
|
|
// reset selection anchor on pointer selection
|
|
evt.onClick = onMaskedClick;
|
|
}
|
|
|
|
if (props.autogrow === true) {
|
|
evt.onAnimationend = onAnimationend;
|
|
}
|
|
|
|
return evt
|
|
});
|
|
|
|
const inputAttrs = vue.computed(() => {
|
|
const attrs = {
|
|
tabindex: 0,
|
|
'data-autofocus': props.autofocus === true || void 0,
|
|
rows: props.type === 'textarea' ? 6 : void 0,
|
|
'aria-label': props.label,
|
|
name: nameProp.value,
|
|
...state.splitAttrs.attributes.value,
|
|
id: state.targetUid.value,
|
|
maxlength: props.maxlength,
|
|
disabled: props.disable === true,
|
|
readonly: props.readonly === true
|
|
};
|
|
|
|
if (isTextarea.value === false) {
|
|
attrs.type = props.type;
|
|
}
|
|
|
|
if (props.autogrow === true) {
|
|
attrs.rows = 1;
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
// some browsers lose the native input value
|
|
// so we need to reattach it dynamically
|
|
// (like type="password" <-> type="text"; see #12078)
|
|
vue.watch(() => props.type, () => {
|
|
if (inputRef.value) {
|
|
inputRef.value.value = props.modelValue;
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
if (hasMask.value === true) {
|
|
if (stopValueWatcher === true) {
|
|
stopValueWatcher = false;
|
|
|
|
if (String(v) === emitCachedValue) {
|
|
return
|
|
}
|
|
}
|
|
|
|
updateMaskValue(v);
|
|
}
|
|
else if (innerValue.value !== v) {
|
|
innerValue.value = v;
|
|
|
|
if (
|
|
props.type === 'number'
|
|
&& temp.hasOwnProperty('value') === true
|
|
) {
|
|
if (typedNumber === true) {
|
|
typedNumber = false;
|
|
}
|
|
else {
|
|
delete temp.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// textarea only
|
|
props.autogrow === true && vue.nextTick(adjustHeight);
|
|
});
|
|
|
|
vue.watch(() => props.autogrow, val => {
|
|
// textarea only
|
|
if (val === true) {
|
|
vue.nextTick(adjustHeight);
|
|
}
|
|
// if it has a number of rows set respect it
|
|
else if (inputRef.value !== null && attrs.rows > 0) {
|
|
inputRef.value.style.height = 'auto';
|
|
}
|
|
});
|
|
|
|
vue.watch(() => props.dense, () => {
|
|
props.autogrow === true && vue.nextTick(adjustHeight);
|
|
});
|
|
|
|
function focus () {
|
|
addFocusFn(() => {
|
|
const el = document.activeElement;
|
|
if (
|
|
inputRef.value !== null
|
|
&& inputRef.value !== el
|
|
&& (el === null || el.id !== state.targetUid.value)
|
|
) {
|
|
inputRef.value.focus({ preventScroll: true });
|
|
}
|
|
});
|
|
}
|
|
|
|
function select () {
|
|
inputRef.value !== null && inputRef.value.select();
|
|
}
|
|
|
|
function onPaste (e) {
|
|
if (hasMask.value === true && props.reverseFillMask !== true) {
|
|
const inp = e.target;
|
|
moveCursorForPaste(inp, inp.selectionStart, inp.selectionEnd);
|
|
}
|
|
|
|
emit('paste', e);
|
|
}
|
|
|
|
function onInput (e) {
|
|
if (!e || !e.target) {
|
|
return
|
|
}
|
|
|
|
if (props.type === 'file') {
|
|
emit('update:modelValue', e.target.files);
|
|
return
|
|
}
|
|
|
|
const val = e.target.value;
|
|
|
|
if (e.target.qComposing === true) {
|
|
temp.value = val;
|
|
|
|
return
|
|
}
|
|
|
|
if (hasMask.value === true) {
|
|
updateMaskValue(val, false, e.inputType);
|
|
}
|
|
else {
|
|
emitValue(val);
|
|
|
|
if (isTypeText.value === true && e.target === document.activeElement) {
|
|
const { selectionStart, selectionEnd } = e.target;
|
|
|
|
if (selectionStart !== void 0 && selectionEnd !== void 0) {
|
|
vue.nextTick(() => {
|
|
if (e.target === document.activeElement && val.indexOf(e.target.value) === 0) {
|
|
e.target.setSelectionRange(selectionStart, selectionEnd);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// we need to trigger it immediately too,
|
|
// to avoid "flickering"
|
|
props.autogrow === true && adjustHeight();
|
|
}
|
|
|
|
function onAnimationend (e) {
|
|
emit('animationend', e);
|
|
adjustHeight();
|
|
}
|
|
|
|
function emitValue (val, stopWatcher) {
|
|
emitValueFn = () => {
|
|
emitTimer = null;
|
|
|
|
if (
|
|
props.type !== 'number'
|
|
&& temp.hasOwnProperty('value') === true
|
|
) {
|
|
delete temp.value;
|
|
}
|
|
|
|
if (props.modelValue !== val && emitCachedValue !== val) {
|
|
emitCachedValue = val;
|
|
|
|
stopWatcher === true && (stopValueWatcher = true);
|
|
emit('update:modelValue', val);
|
|
|
|
vue.nextTick(() => {
|
|
emitCachedValue === val && (emitCachedValue = NaN);
|
|
});
|
|
}
|
|
|
|
emitValueFn = void 0;
|
|
};
|
|
|
|
if (props.type === 'number') {
|
|
typedNumber = true;
|
|
temp.value = val;
|
|
}
|
|
|
|
if (props.debounce !== void 0) {
|
|
emitTimer !== null && clearTimeout(emitTimer);
|
|
temp.value = val;
|
|
emitTimer = setTimeout(emitValueFn, props.debounce);
|
|
}
|
|
else {
|
|
emitValueFn();
|
|
}
|
|
}
|
|
|
|
// textarea only
|
|
function adjustHeight () {
|
|
requestAnimationFrame(() => {
|
|
const inp = inputRef.value;
|
|
if (inp !== null) {
|
|
const parentStyle = inp.parentNode.style;
|
|
// chrome does not keep scroll #15498
|
|
const { scrollTop } = inp;
|
|
// chrome calculates a smaller scrollHeight when in a .column container
|
|
const { overflowY, maxHeight } = $q.platform.is.firefox === true
|
|
? {}
|
|
: window.getComputedStyle(inp);
|
|
// on firefox or if overflowY is specified as scroll #14263, #14344
|
|
// we don't touch overflow
|
|
// firefox is not so bad in the end
|
|
const changeOverflow = overflowY !== void 0 && overflowY !== 'scroll';
|
|
|
|
// reset height of textarea to a small size to detect the real height
|
|
// but keep the total control size the same
|
|
changeOverflow === true && (inp.style.overflowY = 'hidden');
|
|
parentStyle.marginBottom = (inp.scrollHeight - 1) + 'px';
|
|
inp.style.height = '1px';
|
|
|
|
inp.style.height = inp.scrollHeight + 'px';
|
|
// we should allow scrollbars only
|
|
// if there is maxHeight and content is taller than maxHeight
|
|
changeOverflow === true && (inp.style.overflowY = parseInt(maxHeight, 10) < inp.scrollHeight ? 'auto' : 'hidden');
|
|
parentStyle.marginBottom = '';
|
|
inp.scrollTop = scrollTop;
|
|
}
|
|
});
|
|
}
|
|
|
|
function onChange (e) {
|
|
onComposition(e);
|
|
|
|
if (emitTimer !== null) {
|
|
clearTimeout(emitTimer);
|
|
emitTimer = null;
|
|
}
|
|
|
|
emitValueFn !== void 0 && emitValueFn();
|
|
|
|
emit('change', e.target.value);
|
|
}
|
|
|
|
function onFinishEditing (e) {
|
|
e !== void 0 && stop(e);
|
|
|
|
if (emitTimer !== null) {
|
|
clearTimeout(emitTimer);
|
|
emitTimer = null;
|
|
}
|
|
|
|
emitValueFn !== void 0 && emitValueFn();
|
|
|
|
typedNumber = false;
|
|
stopValueWatcher = false;
|
|
delete temp.value;
|
|
|
|
// we need to use setTimeout instead of this.$nextTick
|
|
// to avoid a bug where focusout is not emitted for type date/time/week/...
|
|
props.type !== 'file' && setTimeout(() => {
|
|
if (inputRef.value !== null) {
|
|
inputRef.value.value = innerValue.value !== void 0 ? innerValue.value : '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function getCurValue () {
|
|
return temp.hasOwnProperty('value') === true
|
|
? temp.value
|
|
: (innerValue.value !== void 0 ? innerValue.value : '')
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
onFinishEditing();
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
// textarea only
|
|
props.autogrow === true && adjustHeight();
|
|
});
|
|
|
|
Object.assign(state, {
|
|
innerValue,
|
|
|
|
fieldClass: vue.computed(() =>
|
|
`q-${ isTextarea.value === true ? 'textarea' : 'input' }`
|
|
+ (props.autogrow === true ? ' q-textarea--autogrow' : '')
|
|
),
|
|
|
|
hasShadow: vue.computed(() =>
|
|
props.type !== 'file'
|
|
&& typeof props.shadowText === 'string'
|
|
&& props.shadowText.length !== 0
|
|
),
|
|
|
|
inputRef,
|
|
|
|
emitValue,
|
|
|
|
hasValue,
|
|
|
|
floatingLabel: vue.computed(() =>
|
|
(
|
|
hasValue.value === true
|
|
&& (props.type !== 'number' || isNaN(innerValue.value) === false)
|
|
)
|
|
|| fieldValueIsFilled(props.displayValue)
|
|
),
|
|
|
|
getControl: () => {
|
|
return vue.h(isTextarea.value === true ? 'textarea' : 'input', {
|
|
ref: inputRef,
|
|
class: [
|
|
'q-field__native q-placeholder',
|
|
props.inputClass
|
|
],
|
|
style: props.inputStyle,
|
|
...inputAttrs.value,
|
|
...onEvents.value,
|
|
...(
|
|
props.type !== 'file'
|
|
? { value: getCurValue() }
|
|
: formDomProps.value
|
|
)
|
|
})
|
|
},
|
|
|
|
getShadowControl: () => {
|
|
return vue.h('div', {
|
|
class: 'q-field__native q-field__shadow absolute-bottom no-pointer-events'
|
|
+ (isTextarea.value === true ? '' : ' text-no-wrap')
|
|
}, [
|
|
vue.h('span', { class: 'invisible' }, getCurValue()),
|
|
vue.h('span', props.shadowText)
|
|
])
|
|
}
|
|
});
|
|
|
|
const renderFn = useField(state);
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
focus,
|
|
select,
|
|
getNativeElement: () => inputRef.value // deprecated
|
|
});
|
|
|
|
injectProp(proxy, 'nativeEl', () => inputRef.value);
|
|
|
|
return renderFn
|
|
}
|
|
});
|
|
|
|
const defaultCfg$1 = {
|
|
threshold: 0,
|
|
root: null,
|
|
rootMargin: '0px'
|
|
};
|
|
|
|
function update$3 (el, ctx, value) {
|
|
let handler, cfg, changed;
|
|
|
|
if (typeof value === 'function') {
|
|
handler = value;
|
|
cfg = defaultCfg$1;
|
|
changed = ctx.cfg === void 0;
|
|
}
|
|
else {
|
|
handler = value.handler;
|
|
cfg = Object.assign({}, defaultCfg$1, value.cfg);
|
|
changed = ctx.cfg === void 0 || isDeepEqual(ctx.cfg, cfg) === false;
|
|
}
|
|
|
|
if (ctx.handler !== handler) {
|
|
ctx.handler = handler;
|
|
}
|
|
|
|
if (changed === true) {
|
|
ctx.cfg = cfg;
|
|
ctx.observer !== void 0 && ctx.observer.unobserve(el);
|
|
|
|
ctx.observer = new IntersectionObserver(([ entry ]) => {
|
|
if (typeof ctx.handler === 'function') {
|
|
// if observed element is part of a vue transition
|
|
// then we need to be careful...
|
|
if (
|
|
entry.rootBounds === null
|
|
&& document.body.contains(el) === true
|
|
) {
|
|
ctx.observer.unobserve(el);
|
|
ctx.observer.observe(el);
|
|
return
|
|
}
|
|
|
|
const res = ctx.handler(entry, ctx.observer);
|
|
|
|
if (
|
|
res === false
|
|
|| (ctx.once === true && entry.isIntersecting === true)
|
|
) {
|
|
destroy$1(el);
|
|
}
|
|
}
|
|
}, cfg);
|
|
|
|
ctx.observer.observe(el);
|
|
}
|
|
}
|
|
|
|
function destroy$1 (el) {
|
|
const ctx = el.__qvisible;
|
|
|
|
if (ctx !== void 0) {
|
|
ctx.observer !== void 0 && ctx.observer.unobserve(el);
|
|
delete el.__qvisible;
|
|
}
|
|
}
|
|
|
|
var Intersection = createDirective({
|
|
name: 'intersection',
|
|
|
|
mounted (el, { modifiers, value }) {
|
|
const ctx = {
|
|
once: modifiers.once === true
|
|
};
|
|
|
|
update$3(el, ctx, value);
|
|
|
|
el.__qvisible = ctx;
|
|
},
|
|
|
|
updated (el, binding) {
|
|
const ctx = el.__qvisible;
|
|
ctx !== void 0 && update$3(el, ctx, binding.value);
|
|
},
|
|
|
|
beforeUnmount: destroy$1
|
|
}
|
|
);
|
|
|
|
var QIntersection = createComponent({
|
|
name: 'QIntersection',
|
|
|
|
props: {
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
once: Boolean,
|
|
transition: String,
|
|
transitionDuration: {
|
|
type: [ String, Number ],
|
|
default: 300
|
|
},
|
|
|
|
ssrPrerender: Boolean,
|
|
|
|
margin: String,
|
|
threshold: [ Number, Array ],
|
|
root: {
|
|
default: null
|
|
},
|
|
|
|
disable: Boolean,
|
|
|
|
onVisibility: Function
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
const showing = vue.ref(isRuntimeSsrPreHydration.value === true ? props.ssrPrerender : false);
|
|
|
|
const intersectionProps = vue.computed(() => (
|
|
props.root !== void 0 || props.margin !== void 0 || props.threshold !== void 0
|
|
? {
|
|
handler: trigger,
|
|
cfg: {
|
|
root: props.root,
|
|
rootMargin: props.margin,
|
|
threshold: props.threshold
|
|
}
|
|
}
|
|
: trigger
|
|
));
|
|
|
|
const hasDirective = vue.computed(() =>
|
|
props.disable !== true
|
|
&& (isRuntimeSsrPreHydration.value !== true || props.once !== true || props.ssrPrerender !== true)
|
|
);
|
|
|
|
const directives = vue.computed(() => {
|
|
// if hasDirective.value === true
|
|
return [ [
|
|
Intersection,
|
|
intersectionProps.value,
|
|
void 0,
|
|
{ once: props.once }
|
|
] ]
|
|
});
|
|
|
|
const transitionStyle = vue.computed(
|
|
() => `--q-transition-duration: ${ props.transitionDuration }ms`
|
|
);
|
|
|
|
function trigger (entry) {
|
|
if (showing.value !== entry.isIntersecting) {
|
|
showing.value = entry.isIntersecting;
|
|
props.onVisibility !== void 0 && emit('visibility', showing.value);
|
|
}
|
|
}
|
|
|
|
function getContent () {
|
|
if (showing.value === true) {
|
|
return [ vue.h('div', { key: 'content', style: transitionStyle.value }, hSlot(slots.default)) ]
|
|
}
|
|
|
|
if (slots.hidden !== void 0) {
|
|
return [ vue.h('div', { key: 'hidden', style: transitionStyle.value }, slots.hidden()) ]
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
const child = props.transition
|
|
? [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--' + props.transition
|
|
}, getContent)
|
|
]
|
|
: getContent();
|
|
|
|
return hDir(
|
|
props.tag,
|
|
{ class: 'q-intersection' },
|
|
child,
|
|
'main',
|
|
hasDirective.value,
|
|
() => directives.value
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QList = createComponent({
|
|
name: 'QList',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
bordered: Boolean,
|
|
dense: Boolean,
|
|
separator: Boolean,
|
|
padding: Boolean,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-list'
|
|
+ (props.bordered === true ? ' q-list--bordered' : '')
|
|
+ (props.dense === true ? ' q-list--dense' : '')
|
|
+ (props.separator === true ? ' q-list--separator' : '')
|
|
+ (isDark.value === true ? ' q-list--dark' : '')
|
|
+ (props.padding === true ? ' q-list--padding' : '')
|
|
);
|
|
|
|
return () => vue.h(props.tag, { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
// PGDOWN, LEFT, DOWN, PGUP, RIGHT, UP
|
|
const keyCodes$1 = [ 34, 37, 40, 33, 39, 38 ];
|
|
const commonPropsName = Object.keys(useCircularCommonProps);
|
|
|
|
var QKnob = createComponent({
|
|
name: 'QKnob',
|
|
|
|
props: {
|
|
...useFormProps,
|
|
...useCircularCommonProps,
|
|
|
|
modelValue: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
|
|
innerMin: Number,
|
|
innerMax: Number,
|
|
|
|
step: {
|
|
type: Number,
|
|
default: 1,
|
|
validator: v => v >= 0
|
|
},
|
|
|
|
tabindex: {
|
|
type: [ Number, String ],
|
|
default: 0
|
|
},
|
|
|
|
disable: Boolean,
|
|
readonly: Boolean
|
|
},
|
|
|
|
emits: [ 'update:modelValue', 'change', 'dragValue' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const model = vue.ref(props.modelValue);
|
|
const dragging = vue.ref(false);
|
|
|
|
const innerMin = vue.computed(() => (
|
|
isNaN(props.innerMin) === true || props.innerMin < props.min
|
|
? props.min
|
|
: props.innerMin
|
|
));
|
|
const innerMax = vue.computed(() => (
|
|
isNaN(props.innerMax) === true || props.innerMax > props.max
|
|
? props.max
|
|
: props.innerMax
|
|
));
|
|
|
|
let centerPosition;
|
|
|
|
function normalizeModel () {
|
|
model.value = props.modelValue === null
|
|
? innerMin.value
|
|
: between(props.modelValue, innerMin.value, innerMax.value);
|
|
|
|
updateValue(true);
|
|
}
|
|
|
|
vue.watch(
|
|
() => `${ props.modelValue }|${ innerMin.value }|${ innerMax.value }`,
|
|
normalizeModel
|
|
);
|
|
|
|
normalizeModel();
|
|
|
|
const editable = vue.computed(() => props.disable === false && props.readonly === false);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-knob non-selectable' + (
|
|
editable.value === true
|
|
? ' q-knob--editable'
|
|
: (props.disable === true ? ' disabled' : '')
|
|
)
|
|
);
|
|
|
|
const decimals = vue.computed(() => (String(props.step).trim().split('.')[ 1 ] || '').length);
|
|
const step = vue.computed(() => (props.step === 0 ? 1 : props.step));
|
|
const instantFeedback = vue.computed(() => props.instantFeedback === true || dragging.value === true);
|
|
|
|
const onEvents = $q.platform.is.mobile === true
|
|
? vue.computed(() => (editable.value === true ? { onClick } : {}))
|
|
: vue.computed(() => (
|
|
editable.value === true
|
|
? {
|
|
onMousedown,
|
|
onClick,
|
|
onKeydown,
|
|
onKeyup
|
|
}
|
|
: {}
|
|
));
|
|
|
|
const attrs = vue.computed(() => (
|
|
editable.value === true
|
|
? { tabindex: props.tabindex }
|
|
: { [ `aria-${ props.disable === true ? 'disabled' : 'readonly' }` ]: 'true' }
|
|
));
|
|
|
|
const circularProps = vue.computed(() => {
|
|
const agg = {};
|
|
commonPropsName.forEach(name => {
|
|
agg[ name ] = props[ name ];
|
|
});
|
|
return agg
|
|
});
|
|
|
|
function pan (event) {
|
|
if (event.isFinal) {
|
|
updatePosition(event.evt, true);
|
|
dragging.value = false;
|
|
}
|
|
else if (event.isFirst) {
|
|
updateCenterPosition();
|
|
dragging.value = true;
|
|
updatePosition(event.evt);
|
|
}
|
|
else {
|
|
updatePosition(event.evt);
|
|
}
|
|
}
|
|
|
|
const directives = vue.computed(() => {
|
|
return [ [
|
|
TouchPan,
|
|
pan,
|
|
void 0,
|
|
{ prevent: true, stop: true, mouse: true }
|
|
] ]
|
|
});
|
|
|
|
function updateCenterPosition () {
|
|
const { top, left, width, height } = proxy.$el.getBoundingClientRect();
|
|
centerPosition = {
|
|
top: top + height / 2,
|
|
left: left + width / 2
|
|
};
|
|
}
|
|
|
|
function onMousedown (evt) {
|
|
updateCenterPosition();
|
|
updatePosition(evt);
|
|
}
|
|
|
|
function onClick (evt) {
|
|
updateCenterPosition();
|
|
updatePosition(evt, true);
|
|
}
|
|
|
|
function onKeydown (evt) {
|
|
if (!keyCodes$1.includes(evt.keyCode)) {
|
|
return
|
|
}
|
|
|
|
stopAndPrevent(evt);
|
|
|
|
const
|
|
stepVal = ([ 34, 33 ].includes(evt.keyCode) ? 10 : 1) * step.value,
|
|
offset = [ 34, 37, 40 ].includes(evt.keyCode) ? -stepVal : stepVal;
|
|
|
|
model.value = between(
|
|
parseFloat((model.value + offset).toFixed(decimals.value)),
|
|
innerMin.value,
|
|
innerMax.value
|
|
);
|
|
|
|
updateValue();
|
|
}
|
|
|
|
function updatePosition (evt, change) {
|
|
const
|
|
pos = position(evt),
|
|
height = Math.abs(pos.top - centerPosition.top),
|
|
distance = Math.sqrt(
|
|
height ** 2
|
|
+ Math.abs(pos.left - centerPosition.left) ** 2
|
|
);
|
|
|
|
let angle = Math.asin(height / distance) * (180 / Math.PI);
|
|
|
|
if (pos.top < centerPosition.top) {
|
|
angle = centerPosition.left < pos.left ? 90 - angle : 270 + angle;
|
|
}
|
|
else {
|
|
angle = centerPosition.left < pos.left ? angle + 90 : 270 - angle;
|
|
}
|
|
|
|
if ($q.lang.rtl === true) {
|
|
angle = normalizeToInterval(-angle - props.angle, 0, 360);
|
|
}
|
|
else if (props.angle) {
|
|
angle = normalizeToInterval(angle - props.angle, 0, 360);
|
|
}
|
|
|
|
if (props.reverse === true) {
|
|
angle = 360 - angle;
|
|
}
|
|
|
|
let newModel = props.min + (angle / 360) * (props.max - props.min);
|
|
|
|
if (step.value !== 0) {
|
|
const modulo = newModel % step.value;
|
|
|
|
newModel = newModel - modulo
|
|
+ (Math.abs(modulo) >= step.value / 2 ? (modulo < 0 ? -1 : 1) * step.value : 0);
|
|
|
|
newModel = parseFloat(newModel.toFixed(decimals.value));
|
|
}
|
|
|
|
newModel = between(newModel, innerMin.value, innerMax.value);
|
|
|
|
emit('dragValue', newModel);
|
|
|
|
if (model.value !== newModel) {
|
|
model.value = newModel;
|
|
}
|
|
|
|
updateValue(change);
|
|
}
|
|
|
|
function onKeyup (evt) {
|
|
if (keyCodes$1.includes(evt.keyCode)) {
|
|
updateValue(true);
|
|
}
|
|
}
|
|
|
|
function updateValue (change) {
|
|
props.modelValue !== model.value && emit('update:modelValue', model.value);
|
|
change === true && emit('change', model.value);
|
|
}
|
|
|
|
const formAttrs = useFormAttrs(props);
|
|
|
|
function getNameInput () {
|
|
return vue.h('input', formAttrs.value)
|
|
}
|
|
|
|
return () => {
|
|
const data = {
|
|
class: classes.value,
|
|
role: 'slider',
|
|
'aria-valuemin': innerMin.value,
|
|
'aria-valuemax': innerMax.value,
|
|
'aria-valuenow': props.modelValue,
|
|
...attrs.value,
|
|
...circularProps.value,
|
|
value: model.value,
|
|
instantFeedback: instantFeedback.value,
|
|
...onEvents.value
|
|
};
|
|
|
|
const child = {
|
|
default: slots.default
|
|
};
|
|
|
|
if (editable.value === true && props.name !== void 0) {
|
|
child.internal = getNameInput;
|
|
}
|
|
|
|
return hDir(
|
|
QCircularProgress,
|
|
data,
|
|
child,
|
|
'knob',
|
|
editable.value,
|
|
() => directives.value
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
const { passive: passive$2 } = listenOpts;
|
|
const axisValues = [ 'both', 'horizontal', 'vertical' ];
|
|
|
|
var QScrollObserver = createComponent({
|
|
name: 'QScrollObserver',
|
|
|
|
props: {
|
|
axis: {
|
|
type: String,
|
|
validator: v => axisValues.includes(v),
|
|
default: 'vertical'
|
|
},
|
|
|
|
debounce: [ String, Number ],
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
}
|
|
},
|
|
|
|
emits: [ 'scroll' ],
|
|
|
|
setup (props, { emit }) {
|
|
const scroll = {
|
|
position: {
|
|
top: 0,
|
|
left: 0
|
|
},
|
|
|
|
direction: 'down',
|
|
directionChanged: false,
|
|
|
|
delta: {
|
|
top: 0,
|
|
left: 0
|
|
},
|
|
|
|
inflectionPoint: {
|
|
top: 0,
|
|
left: 0
|
|
}
|
|
};
|
|
|
|
let clearTimer = null, localScrollTarget, parentEl;
|
|
|
|
vue.watch(() => props.scrollTarget, () => {
|
|
unconfigureScrollTarget();
|
|
configureScrollTarget();
|
|
});
|
|
|
|
function emitEvent () {
|
|
clearTimer !== null && clearTimer();
|
|
|
|
const top = Math.max(0, getVerticalScrollPosition(localScrollTarget));
|
|
const left = getHorizontalScrollPosition(localScrollTarget);
|
|
|
|
const delta = {
|
|
top: top - scroll.position.top,
|
|
left: left - scroll.position.left
|
|
};
|
|
|
|
if (
|
|
(props.axis === 'vertical' && delta.top === 0)
|
|
|| (props.axis === 'horizontal' && delta.left === 0)
|
|
) {
|
|
return
|
|
}
|
|
|
|
const curDir = Math.abs(delta.top) >= Math.abs(delta.left)
|
|
? (delta.top < 0 ? 'up' : 'down')
|
|
: (delta.left < 0 ? 'left' : 'right');
|
|
|
|
scroll.position = { top, left };
|
|
scroll.directionChanged = scroll.direction !== curDir;
|
|
scroll.delta = delta;
|
|
|
|
if (scroll.directionChanged === true) {
|
|
scroll.direction = curDir;
|
|
scroll.inflectionPoint = scroll.position;
|
|
}
|
|
|
|
emit('scroll', { ...scroll });
|
|
}
|
|
|
|
function configureScrollTarget () {
|
|
localScrollTarget = getScrollTarget(parentEl, props.scrollTarget);
|
|
localScrollTarget.addEventListener('scroll', trigger, passive$2);
|
|
trigger(true);
|
|
}
|
|
|
|
function unconfigureScrollTarget () {
|
|
if (localScrollTarget !== void 0) {
|
|
localScrollTarget.removeEventListener('scroll', trigger, passive$2);
|
|
localScrollTarget = void 0;
|
|
}
|
|
}
|
|
|
|
function trigger (immediately) {
|
|
if (immediately === true || props.debounce === 0 || props.debounce === '0') {
|
|
emitEvent();
|
|
}
|
|
else if (clearTimer === null) {
|
|
const [ timer, fn ] = props.debounce
|
|
? [ setTimeout(emitEvent, props.debounce), clearTimeout ]
|
|
: [ requestAnimationFrame(emitEvent), cancelAnimationFrame ];
|
|
|
|
clearTimer = () => {
|
|
fn(timer);
|
|
clearTimer = null;
|
|
};
|
|
}
|
|
}
|
|
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
vue.watch(() => proxy.$q.lang.rtl, emitEvent);
|
|
|
|
vue.onMounted(() => {
|
|
parentEl = proxy.$el.parentNode;
|
|
configureScrollTarget();
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
clearTimer !== null && clearTimer();
|
|
unconfigureScrollTarget();
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
trigger,
|
|
getPosition: () => scroll
|
|
});
|
|
|
|
return noop
|
|
}
|
|
});
|
|
|
|
var QLayout = createComponent({
|
|
name: 'QLayout',
|
|
|
|
props: {
|
|
container: Boolean,
|
|
view: {
|
|
type: String,
|
|
default: 'hhh lpr fff',
|
|
validator: v => /^(h|l)h(h|r) lpr (f|l)f(f|r)$/.test(v.toLowerCase())
|
|
},
|
|
|
|
onScroll: Function,
|
|
onScrollHeight: Function,
|
|
onResize: Function
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const rootRef = vue.ref(null);
|
|
|
|
// page related
|
|
const height = vue.ref($q.screen.height);
|
|
const width = vue.ref(props.container === true ? 0 : $q.screen.width);
|
|
const scroll = vue.ref({ position: 0, direction: 'down', inflectionPoint: 0 });
|
|
|
|
// container only prop
|
|
const containerHeight = vue.ref(0);
|
|
const scrollbarWidth = vue.ref(isRuntimeSsrPreHydration.value === true ? 0 : getScrollbarWidth());
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-layout q-layout--'
|
|
+ (props.container === true ? 'containerized' : 'standard')
|
|
);
|
|
|
|
const style = vue.computed(() => (
|
|
props.container === false
|
|
? { minHeight: $q.screen.height + 'px' }
|
|
: null
|
|
));
|
|
|
|
// used by container only
|
|
const targetStyle = vue.computed(() => (
|
|
scrollbarWidth.value !== 0
|
|
? { [ $q.lang.rtl === true ? 'left' : 'right' ]: `${ scrollbarWidth.value }px` }
|
|
: null
|
|
));
|
|
|
|
const targetChildStyle = vue.computed(() => (
|
|
scrollbarWidth.value !== 0
|
|
? {
|
|
[ $q.lang.rtl === true ? 'right' : 'left' ]: 0,
|
|
[ $q.lang.rtl === true ? 'left' : 'right' ]: `-${ scrollbarWidth.value }px`,
|
|
width: `calc(100% + ${ scrollbarWidth.value }px)`
|
|
}
|
|
: null
|
|
));
|
|
|
|
function onPageScroll (data) {
|
|
if (props.container === true || document.qScrollPrevented !== true) {
|
|
const info = {
|
|
position: data.position.top,
|
|
direction: data.direction,
|
|
directionChanged: data.directionChanged,
|
|
inflectionPoint: data.inflectionPoint.top,
|
|
delta: data.delta.top
|
|
};
|
|
|
|
scroll.value = info;
|
|
props.onScroll !== void 0 && emit('scroll', info);
|
|
}
|
|
}
|
|
|
|
function onPageResize (data) {
|
|
const { height: newHeight, width: newWidth } = data;
|
|
let resized = false;
|
|
|
|
if (height.value !== newHeight) {
|
|
resized = true;
|
|
height.value = newHeight;
|
|
props.onScrollHeight !== void 0 && emit('scrollHeight', newHeight);
|
|
updateScrollbarWidth();
|
|
}
|
|
if (width.value !== newWidth) {
|
|
resized = true;
|
|
width.value = newWidth;
|
|
}
|
|
|
|
if (resized === true && props.onResize !== void 0) {
|
|
emit('resize', data);
|
|
}
|
|
}
|
|
|
|
function onContainerResize ({ height }) {
|
|
if (containerHeight.value !== height) {
|
|
containerHeight.value = height;
|
|
updateScrollbarWidth();
|
|
}
|
|
}
|
|
|
|
function updateScrollbarWidth () {
|
|
if (props.container === true) {
|
|
const width = height.value > containerHeight.value
|
|
? getScrollbarWidth()
|
|
: 0;
|
|
|
|
if (scrollbarWidth.value !== width) {
|
|
scrollbarWidth.value = width;
|
|
}
|
|
}
|
|
}
|
|
|
|
let animateTimer = null;
|
|
|
|
const $layout = {
|
|
instances: {},
|
|
view: vue.computed(() => props.view),
|
|
isContainer: vue.computed(() => props.container),
|
|
|
|
rootRef,
|
|
|
|
height,
|
|
containerHeight,
|
|
scrollbarWidth,
|
|
totalWidth: vue.computed(() => width.value + scrollbarWidth.value),
|
|
|
|
rows: vue.computed(() => {
|
|
const rows = props.view.toLowerCase().split(' ');
|
|
return {
|
|
top: rows[ 0 ].split(''),
|
|
middle: rows[ 1 ].split(''),
|
|
bottom: rows[ 2 ].split('')
|
|
}
|
|
}),
|
|
|
|
header: vue.reactive({ size: 0, offset: 0, space: false }),
|
|
right: vue.reactive({ size: 300, offset: 0, space: false }),
|
|
footer: vue.reactive({ size: 0, offset: 0, space: false }),
|
|
left: vue.reactive({ size: 300, offset: 0, space: false }),
|
|
|
|
scroll,
|
|
|
|
animate () {
|
|
if (animateTimer !== null) {
|
|
clearTimeout(animateTimer);
|
|
}
|
|
else {
|
|
document.body.classList.add('q-body--layout-animate');
|
|
}
|
|
|
|
animateTimer = setTimeout(() => {
|
|
animateTimer = null;
|
|
document.body.classList.remove('q-body--layout-animate');
|
|
}, 155);
|
|
},
|
|
|
|
update (part, prop, val) {
|
|
$layout[ part ][ prop ] = val;
|
|
}
|
|
};
|
|
|
|
vue.provide(layoutKey, $layout);
|
|
|
|
// prevent scrollbar flicker while resizing window height
|
|
// if no page scrollbar is already present
|
|
if (getScrollbarWidth() > 0) {
|
|
let timer = null;
|
|
const el = document.body;
|
|
|
|
function restoreScrollbar () {
|
|
timer = null;
|
|
el.classList.remove('hide-scrollbar');
|
|
}
|
|
|
|
function hideScrollbar () {
|
|
if (timer === null) {
|
|
// if it has no scrollbar then there's nothing to do
|
|
|
|
if (el.scrollHeight > $q.screen.height) {
|
|
return
|
|
}
|
|
|
|
el.classList.add('hide-scrollbar');
|
|
}
|
|
else {
|
|
clearTimeout(timer);
|
|
}
|
|
|
|
timer = setTimeout(restoreScrollbar, 300);
|
|
}
|
|
|
|
function updateScrollEvent (action) {
|
|
if (timer !== null && action === 'remove') {
|
|
clearTimeout(timer);
|
|
restoreScrollbar();
|
|
}
|
|
|
|
window[ `${ action }EventListener` ]('resize', hideScrollbar);
|
|
}
|
|
|
|
vue.watch(
|
|
() => (props.container !== true ? 'add' : 'remove'),
|
|
updateScrollEvent
|
|
);
|
|
|
|
props.container !== true && updateScrollEvent('add');
|
|
|
|
vue.onUnmounted(() => {
|
|
updateScrollEvent('remove');
|
|
});
|
|
}
|
|
|
|
return () => {
|
|
const content = hMergeSlot(slots.default, [
|
|
vue.h(QScrollObserver, { onScroll: onPageScroll }),
|
|
vue.h(QResizeObserver, { onResize: onPageResize })
|
|
]);
|
|
|
|
const layout = vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
ref: props.container === true ? void 0 : rootRef,
|
|
tabindex: -1
|
|
}, content);
|
|
|
|
if (props.container === true) {
|
|
return vue.h('div', {
|
|
class: 'q-layout-container overflow-hidden',
|
|
ref: rootRef
|
|
}, [
|
|
vue.h(QResizeObserver, { onResize: onContainerResize }),
|
|
vue.h('div', {
|
|
class: 'absolute-full',
|
|
style: targetStyle.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'scroll',
|
|
style: targetChildStyle.value
|
|
}, [ layout ])
|
|
])
|
|
])
|
|
}
|
|
|
|
return layout
|
|
}
|
|
}
|
|
});
|
|
|
|
const separatorValues = [ 'horizontal', 'vertical', 'cell', 'none' ];
|
|
|
|
var QMarkupTable = createComponent({
|
|
name: 'QMarkupTable',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
dense: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
square: Boolean,
|
|
wrapCells: Boolean,
|
|
|
|
separator: {
|
|
type: String,
|
|
default: 'horizontal',
|
|
validator: v => separatorValues.includes(v)
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-markup-table q-table__container q-table__card'
|
|
+ ` q-table--${ props.separator }-separator`
|
|
+ (isDark.value === true ? ' q-table--dark q-table__card--dark q-dark' : '')
|
|
+ (props.dense === true ? ' q-table--dense' : '')
|
|
+ (props.flat === true ? ' q-table--flat' : '')
|
|
+ (props.bordered === true ? ' q-table--bordered' : '')
|
|
+ (props.square === true ? ' q-table--square' : '')
|
|
+ (props.wrapCells === false ? ' q-table--no-wrap' : '')
|
|
);
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value
|
|
}, [
|
|
vue.h('table', { class: 'q-table' }, hSlot(slots.default))
|
|
])
|
|
}
|
|
});
|
|
|
|
var QNoSsr = createComponent({
|
|
name: 'QNoSsr',
|
|
|
|
props: {
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
placeholder: String
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const canRender = useCanRender();
|
|
|
|
return () => {
|
|
const data = {};
|
|
|
|
if (canRender.value === true) {
|
|
const node = hSlot(slots.default);
|
|
return node === void 0
|
|
? node
|
|
: (node.length > 1 ? vue.h(props.tag, data, node) : node[ 0 ])
|
|
}
|
|
|
|
data.class = 'q-no-ssr-placeholder';
|
|
|
|
const node = hSlot(slots.placeholder);
|
|
if (node !== void 0) {
|
|
return node.length > 1
|
|
? vue.h(props.tag, data, node)
|
|
: node[ 0 ]
|
|
}
|
|
|
|
if (props.placeholder !== void 0) {
|
|
return vue.h(props.tag, data, props.placeholder)
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const svg$m = vue.h('svg', {
|
|
key: 'svg',
|
|
class: 'q-radio__bg absolute non-selectable',
|
|
viewBox: '0 0 24 24'
|
|
}, [
|
|
vue.h('path', {
|
|
d: 'M12,22a10,10 0 0 1 -10,-10a10,10 0 0 1 10,-10a10,10 0 0 1 10,10a10,10 0 0 1 -10,10m0,-22a12,12 0 0 0 -12,12a12,12 0 0 0 12,12a12,12 0 0 0 12,-12a12,12 0 0 0 -12,-12'
|
|
}),
|
|
|
|
vue.h('path', {
|
|
class: 'q-radio__check',
|
|
d: 'M12,6a6,6 0 0 0 -6,6a6,6 0 0 0 6,6a6,6 0 0 0 6,-6a6,6 0 0 0 -6,-6'
|
|
})
|
|
]);
|
|
|
|
var QRadio = createComponent({
|
|
name: 'QRadio',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useSizeProps,
|
|
...useFormProps,
|
|
|
|
modelValue: { required: true },
|
|
val: { required: true },
|
|
|
|
label: String,
|
|
leftLabel: Boolean,
|
|
|
|
checkedIcon: String,
|
|
uncheckedIcon: String,
|
|
|
|
color: String,
|
|
keepColor: Boolean,
|
|
dense: Boolean,
|
|
|
|
disable: Boolean,
|
|
tabindex: [ String, Number ]
|
|
},
|
|
|
|
emits: [ 'update:modelValue' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, proxy.$q);
|
|
const sizeStyle = useSize(props, optionSizes);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const { refocusTargetEl, refocusTarget } = useRefocusTarget(props, rootRef);
|
|
|
|
const isTrue = vue.computed(() => vue.toRaw(props.modelValue) === vue.toRaw(props.val));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-radio cursor-pointer no-outline row inline no-wrap items-center'
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (isDark.value === true ? ' q-radio--dark' : '')
|
|
+ (props.dense === true ? ' q-radio--dense' : '')
|
|
+ (props.leftLabel === true ? ' reverse' : '')
|
|
);
|
|
|
|
const innerClass = vue.computed(() => {
|
|
const color = props.color !== void 0 && (
|
|
props.keepColor === true
|
|
|| isTrue.value === true
|
|
)
|
|
? ` text-${ props.color }`
|
|
: '';
|
|
|
|
return 'q-radio__inner relative-position '
|
|
+ `q-radio__inner--${ isTrue.value === true ? 'truthy' : 'falsy' }${ color }`
|
|
});
|
|
|
|
const icon = vue.computed(() =>
|
|
(isTrue.value === true
|
|
? props.checkedIcon
|
|
: props.uncheckedIcon
|
|
) || null
|
|
);
|
|
|
|
const tabindex = vue.computed(() => (
|
|
props.disable === true ? -1 : props.tabindex || 0
|
|
));
|
|
|
|
const formAttrs = vue.computed(() => {
|
|
const prop = { type: 'radio' };
|
|
|
|
props.name !== void 0 && Object.assign(prop, {
|
|
// see https://vuejs.org/guide/extras/render-function.html#creating-vnodes (.prop)
|
|
'.checked': isTrue.value === true,
|
|
'^checked': isTrue.value === true ? 'checked' : void 0,
|
|
name: props.name,
|
|
value: props.val
|
|
});
|
|
|
|
return prop
|
|
});
|
|
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
function onClick (e) {
|
|
if (e !== void 0) {
|
|
stopAndPrevent(e);
|
|
refocusTarget(e);
|
|
}
|
|
|
|
if (props.disable !== true && isTrue.value !== true) {
|
|
emit('update:modelValue', props.val, e);
|
|
}
|
|
}
|
|
|
|
function onKeydown (e) {
|
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
stopAndPrevent(e);
|
|
}
|
|
}
|
|
|
|
function onKeyup (e) {
|
|
if (e.keyCode === 13 || e.keyCode === 32) {
|
|
onClick(e);
|
|
}
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { set: onClick });
|
|
|
|
return () => {
|
|
const content = icon.value !== null
|
|
? [
|
|
vue.h('div', {
|
|
key: 'icon',
|
|
class: 'q-radio__icon-container absolute-full flex flex-center no-wrap'
|
|
}, [
|
|
vue.h(QIcon, {
|
|
class: 'q-radio__icon',
|
|
name: icon.value
|
|
})
|
|
])
|
|
]
|
|
: [ svg$m ];
|
|
|
|
props.disable !== true && injectFormInput(
|
|
content,
|
|
'unshift',
|
|
' q-radio__native q-ma-none q-pa-none'
|
|
);
|
|
|
|
const child = [
|
|
vue.h('div', {
|
|
class: innerClass.value,
|
|
style: sizeStyle.value,
|
|
'aria-hidden': 'true'
|
|
}, content)
|
|
];
|
|
|
|
if (refocusTargetEl.value !== null) {
|
|
child.push(refocusTargetEl.value);
|
|
}
|
|
|
|
const label = props.label !== void 0
|
|
? hMergeSlot(slots.default, [ props.label ])
|
|
: hSlot(slots.default);
|
|
|
|
label !== void 0 && child.push(
|
|
vue.h('div', {
|
|
class: 'q-radio__label q-anchor--skip'
|
|
}, label)
|
|
);
|
|
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: classes.value,
|
|
tabindex: tabindex.value,
|
|
role: 'radio',
|
|
'aria-label': props.label,
|
|
'aria-checked': isTrue.value === true ? 'true' : 'false',
|
|
'aria-disabled': props.disable === true ? 'true' : void 0,
|
|
onClick,
|
|
onKeydown,
|
|
onKeyup
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QToggle = createComponent({
|
|
name: 'QToggle',
|
|
|
|
props: {
|
|
...useCheckboxProps,
|
|
|
|
icon: String,
|
|
iconColor: String
|
|
},
|
|
|
|
emits: useCheckboxEmits,
|
|
|
|
setup (props) {
|
|
function getInner (isTrue, isIndeterminate) {
|
|
const icon = vue.computed(() =>
|
|
(isTrue.value === true
|
|
? props.checkedIcon
|
|
: (isIndeterminate.value === true ? props.indeterminateIcon : props.uncheckedIcon)
|
|
) || props.icon
|
|
);
|
|
|
|
const color = vue.computed(() => (isTrue.value === true ? props.iconColor : null));
|
|
|
|
return () => [
|
|
vue.h('div', { class: 'q-toggle__track' }),
|
|
|
|
vue.h('div', {
|
|
class: 'q-toggle__thumb absolute flex flex-center no-wrap'
|
|
}, icon.value !== void 0
|
|
? [
|
|
vue.h(QIcon, {
|
|
name: icon.value,
|
|
color: color.value
|
|
})
|
|
]
|
|
: void 0
|
|
)
|
|
]
|
|
}
|
|
|
|
return useCheckbox('toggle', getInner)
|
|
}
|
|
});
|
|
|
|
const components$1 = {
|
|
radio: QRadio,
|
|
checkbox: QCheckbox,
|
|
toggle: QToggle
|
|
};
|
|
|
|
const typeValues = Object.keys(components$1);
|
|
|
|
var QOptionGroup = createComponent({
|
|
name: 'QOptionGroup',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
options: {
|
|
type: Array,
|
|
validator: opts => opts.every(opt => 'value' in opt && 'label' in opt)
|
|
},
|
|
|
|
name: String,
|
|
|
|
type: {
|
|
default: 'radio',
|
|
validator: v => typeValues.includes(v)
|
|
},
|
|
|
|
color: String,
|
|
keepColor: Boolean,
|
|
dense: Boolean,
|
|
|
|
size: String,
|
|
|
|
leftLabel: Boolean,
|
|
inline: Boolean,
|
|
disable: Boolean
|
|
},
|
|
|
|
emits: [ 'update:modelValue' ],
|
|
|
|
setup (props, { emit, slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const arrayModel = Array.isArray(props.modelValue);
|
|
|
|
if (props.type === 'radio') {
|
|
if (arrayModel === true) {
|
|
console.error('q-option-group: model should not be array');
|
|
}
|
|
}
|
|
else if (arrayModel === false) {
|
|
console.error('q-option-group: model should be array in your case');
|
|
}
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
const component = vue.computed(() => components$1[ props.type ]);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-option-group q-gutter-x-sm'
|
|
+ (props.inline === true ? ' q-option-group--inline' : '')
|
|
);
|
|
|
|
const attrs = vue.computed(() => {
|
|
const attrs = { role: 'group' };
|
|
|
|
if (props.type === 'radio') {
|
|
attrs.role = 'radiogroup';
|
|
|
|
if (props.disable === true) {
|
|
attrs[ 'aria-disabled' ] = 'true';
|
|
}
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
function onUpdateModelValue (value) {
|
|
emit('update:modelValue', value);
|
|
}
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
...attrs.value
|
|
}, props.options.map((opt, i) => {
|
|
// TODO: (Qv3) Make the 'opt' a separate property instead of
|
|
// the whole scope for consistency and flexibility
|
|
// (e.g. { opt } instead of opt)
|
|
const child = slots[ 'label-' + i ] !== void 0
|
|
? () => slots[ 'label-' + i ](opt)
|
|
: (
|
|
slots.label !== void 0
|
|
? () => slots.label(opt)
|
|
: void 0
|
|
);
|
|
|
|
return vue.h('div', [
|
|
vue.h(component.value, {
|
|
modelValue: props.modelValue,
|
|
val: opt.value,
|
|
name: opt.name === void 0 ? props.name : opt.name,
|
|
disable: props.disable || opt.disable,
|
|
label: child === void 0 ? opt.label : null,
|
|
leftLabel: opt.leftLabel === void 0 ? props.leftLabel : opt.leftLabel,
|
|
color: opt.color === void 0 ? props.color : opt.color,
|
|
checkedIcon: opt.checkedIcon,
|
|
uncheckedIcon: opt.uncheckedIcon,
|
|
dark: opt.dark || isDark.value,
|
|
size: opt.size === void 0 ? props.size : opt.size,
|
|
dense: props.dense,
|
|
keepColor: opt.keepColor === void 0 ? props.keepColor : opt.keepColor,
|
|
'onUpdate:modelValue': onUpdateModelValue
|
|
}, child)
|
|
])
|
|
}))
|
|
}
|
|
});
|
|
|
|
var QPage = createComponent({
|
|
name: 'QPage',
|
|
|
|
props: {
|
|
padding: Boolean,
|
|
styleFn: Function
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QPage needs to be a deep child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const $pageContainer = vue.inject(pageContainerKey, emptyRenderFn);
|
|
if ($pageContainer === emptyRenderFn) {
|
|
console.error('QPage needs to be child of QPageContainer');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const style = vue.computed(() => {
|
|
const offset
|
|
= ($layout.header.space === true ? $layout.header.size : 0)
|
|
+ ($layout.footer.space === true ? $layout.footer.size : 0);
|
|
|
|
if (typeof props.styleFn === 'function') {
|
|
const height = $layout.isContainer.value === true
|
|
? $layout.containerHeight.value
|
|
: $q.screen.height;
|
|
|
|
return props.styleFn(offset, height)
|
|
}
|
|
|
|
return {
|
|
minHeight: $layout.isContainer.value === true
|
|
? ($layout.containerHeight.value - offset) + 'px'
|
|
: (
|
|
$q.screen.height === 0
|
|
? (offset !== 0 ? `calc(100vh - ${ offset }px)` : '100vh')
|
|
: ($q.screen.height - offset) + 'px'
|
|
)
|
|
}
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-page${ props.padding === true ? ' q-layout-padding' : '' }`
|
|
);
|
|
|
|
return () => vue.h('main', {
|
|
class: classes.value,
|
|
style: style.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QPageContainer = createComponent({
|
|
name: 'QPageContainer',
|
|
|
|
setup (_, { slots }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QPageContainer needs to be child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
vue.provide(pageContainerKey, true);
|
|
|
|
const style = vue.computed(() => {
|
|
const css = {};
|
|
|
|
if ($layout.header.space === true) {
|
|
css.paddingTop = `${ $layout.header.size }px`;
|
|
}
|
|
if ($layout.right.space === true) {
|
|
css[ `padding${ $q.lang.rtl === true ? 'Left' : 'Right' }` ] = `${ $layout.right.size }px`;
|
|
}
|
|
if ($layout.footer.space === true) {
|
|
css.paddingBottom = `${ $layout.footer.size }px`;
|
|
}
|
|
if ($layout.left.space === true) {
|
|
css[ `padding${ $q.lang.rtl === true ? 'Right' : 'Left' }` ] = `${ $layout.left.size }px`;
|
|
}
|
|
|
|
return css
|
|
});
|
|
|
|
return () => vue.h('div', {
|
|
class: 'q-page-container',
|
|
style: style.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
const usePageStickyProps = {
|
|
position: {
|
|
type: String,
|
|
default: 'bottom-right',
|
|
validator: v => [
|
|
'top-right', 'top-left',
|
|
'bottom-right', 'bottom-left',
|
|
'top', 'right', 'bottom', 'left'
|
|
].includes(v)
|
|
},
|
|
offset: {
|
|
type: Array,
|
|
validator: v => v.length === 2
|
|
},
|
|
expand: Boolean
|
|
};
|
|
|
|
function usePageSticky () {
|
|
const { props, proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $layout = vue.inject(layoutKey, emptyRenderFn);
|
|
if ($layout === emptyRenderFn) {
|
|
console.error('QPageSticky needs to be child of QLayout');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const attach = vue.computed(() => {
|
|
const pos = props.position;
|
|
|
|
return {
|
|
top: pos.indexOf('top') > -1,
|
|
right: pos.indexOf('right') > -1,
|
|
bottom: pos.indexOf('bottom') > -1,
|
|
left: pos.indexOf('left') > -1,
|
|
vertical: pos === 'top' || pos === 'bottom',
|
|
horizontal: pos === 'left' || pos === 'right'
|
|
}
|
|
});
|
|
|
|
const top = vue.computed(() => $layout.header.offset);
|
|
const right = vue.computed(() => $layout.right.offset);
|
|
const bottom = vue.computed(() => $layout.footer.offset);
|
|
const left = vue.computed(() => $layout.left.offset);
|
|
|
|
const style = vue.computed(() => {
|
|
let posX = 0, posY = 0;
|
|
|
|
const side = attach.value;
|
|
const dir = $q.lang.rtl === true ? -1 : 1;
|
|
|
|
if (side.top === true && top.value !== 0) {
|
|
posY = `${ top.value }px`;
|
|
}
|
|
else if (side.bottom === true && bottom.value !== 0) {
|
|
posY = `${ -bottom.value }px`;
|
|
}
|
|
|
|
if (side.left === true && left.value !== 0) {
|
|
posX = `${ dir * left.value }px`;
|
|
}
|
|
else if (side.right === true && right.value !== 0) {
|
|
posX = `${ -dir * right.value }px`;
|
|
}
|
|
|
|
const css = { transform: `translate(${ posX }, ${ posY })` };
|
|
|
|
if (props.offset) {
|
|
css.margin = `${ props.offset[ 1 ] }px ${ props.offset[ 0 ] }px`;
|
|
}
|
|
|
|
if (side.vertical === true) {
|
|
if (left.value !== 0) {
|
|
css[ $q.lang.rtl === true ? 'right' : 'left' ] = `${ left.value }px`;
|
|
}
|
|
if (right.value !== 0) {
|
|
css[ $q.lang.rtl === true ? 'left' : 'right' ] = `${ right.value }px`;
|
|
}
|
|
}
|
|
else if (side.horizontal === true) {
|
|
if (top.value !== 0) {
|
|
css.top = `${ top.value }px`;
|
|
}
|
|
if (bottom.value !== 0) {
|
|
css.bottom = `${ bottom.value }px`;
|
|
}
|
|
}
|
|
|
|
return css
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-page-sticky row flex-center fixed-${ props.position }`
|
|
+ ` q-page-sticky--${ props.expand === true ? 'expand' : 'shrink' }`
|
|
);
|
|
|
|
function getStickyContent (slots) {
|
|
const content = hSlot(slots.default);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value
|
|
},
|
|
props.expand === true
|
|
? content
|
|
: [ vue.h('div', content) ]
|
|
)
|
|
}
|
|
|
|
return {
|
|
$layout,
|
|
getStickyContent
|
|
}
|
|
}
|
|
|
|
var QPageScroller = createComponent({
|
|
name: 'QPageScroller',
|
|
|
|
props: {
|
|
...usePageStickyProps,
|
|
|
|
scrollOffset: {
|
|
type: Number,
|
|
default: 1000
|
|
},
|
|
|
|
reverse: Boolean,
|
|
|
|
duration: {
|
|
type: Number,
|
|
default: 300
|
|
},
|
|
|
|
offset: {
|
|
default: () => [ 18, 18 ]
|
|
}
|
|
},
|
|
|
|
emits: [ 'click' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const { $layout, getStickyContent } = usePageSticky();
|
|
const rootRef = vue.ref(null);
|
|
|
|
let heightWatcher;
|
|
|
|
const scrollHeight = vue.computed(() => $layout.height.value - (
|
|
$layout.isContainer.value === true
|
|
? $layout.containerHeight.value
|
|
: $q.screen.height
|
|
));
|
|
|
|
function isVisible () {
|
|
return props.reverse === true
|
|
? scrollHeight.value - $layout.scroll.value.position > props.scrollOffset
|
|
: $layout.scroll.value.position > props.scrollOffset
|
|
}
|
|
|
|
const showing = vue.ref(isVisible());
|
|
|
|
function updateVisibility () {
|
|
const newVal = isVisible();
|
|
if (showing.value !== newVal) {
|
|
showing.value = newVal;
|
|
}
|
|
}
|
|
|
|
function updateReverse () {
|
|
if (props.reverse === true) {
|
|
if (heightWatcher === void 0) {
|
|
heightWatcher = vue.watch(scrollHeight, updateVisibility);
|
|
}
|
|
}
|
|
else {
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
vue.watch($layout.scroll, updateVisibility);
|
|
vue.watch(() => props.reverse, updateReverse);
|
|
|
|
function cleanup () {
|
|
if (heightWatcher !== void 0) {
|
|
heightWatcher();
|
|
heightWatcher = void 0;
|
|
}
|
|
}
|
|
|
|
function onClick (e) {
|
|
const target = getScrollTarget(
|
|
$layout.isContainer.value === true
|
|
? rootRef.value
|
|
: $layout.rootRef.value
|
|
);
|
|
|
|
setVerticalScrollPosition(
|
|
target,
|
|
props.reverse === true ? $layout.height.value : 0,
|
|
props.duration
|
|
);
|
|
|
|
emit('click', e);
|
|
}
|
|
|
|
function getContent () {
|
|
return showing.value === true
|
|
? vue.h('div', {
|
|
ref: rootRef,
|
|
class: 'q-page-scroller',
|
|
onClick
|
|
}, getStickyContent(slots))
|
|
: null
|
|
}
|
|
|
|
updateReverse();
|
|
|
|
vue.onBeforeUnmount(cleanup);
|
|
|
|
return () => vue.h(
|
|
vue.Transition,
|
|
{ name: 'q-transition--fade' },
|
|
getContent
|
|
)
|
|
}
|
|
});
|
|
|
|
var QPageSticky = createComponent({
|
|
name: 'QPageSticky',
|
|
|
|
props: usePageStickyProps,
|
|
|
|
setup (_, { slots }) {
|
|
const { getStickyContent } = usePageSticky();
|
|
return () => getStickyContent(slots)
|
|
}
|
|
});
|
|
|
|
function getBool (val, otherwise) {
|
|
return [ true, false ].includes(val)
|
|
? val
|
|
: otherwise
|
|
}
|
|
|
|
var QPagination = createComponent({
|
|
name: 'QPagination',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
modelValue: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
min: {
|
|
type: [ Number, String ],
|
|
default: 1
|
|
},
|
|
max: {
|
|
type: [ Number, String ],
|
|
required: true
|
|
},
|
|
maxPages: {
|
|
type: [ Number, String ],
|
|
default: 0,
|
|
validator: v => (
|
|
(typeof v === 'string' ? parseInt(v, 10) : v) >= 0
|
|
)
|
|
},
|
|
|
|
inputStyle: [ Array, String, Object ],
|
|
inputClass: [ Array, String, Object ],
|
|
|
|
size: String,
|
|
|
|
disable: Boolean,
|
|
|
|
input: Boolean,
|
|
|
|
iconPrev: String,
|
|
iconNext: String,
|
|
iconFirst: String,
|
|
iconLast: String,
|
|
|
|
toFn: Function,
|
|
|
|
boundaryLinks: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
boundaryNumbers: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
directionLinks: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
ellipses: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
ripple: {
|
|
type: [ Boolean, Object ],
|
|
default: null
|
|
},
|
|
|
|
round: Boolean,
|
|
rounded: Boolean,
|
|
|
|
flat: Boolean,
|
|
outline: Boolean,
|
|
unelevated: Boolean,
|
|
push: Boolean,
|
|
glossy: Boolean,
|
|
|
|
color: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
textColor: String,
|
|
|
|
activeDesign: {
|
|
type: String,
|
|
default: '',
|
|
values: v => v === '' || btnDesignOptions.includes(v)
|
|
},
|
|
activeColor: String,
|
|
activeTextColor: String,
|
|
|
|
gutter: String,
|
|
padding: {
|
|
type: String,
|
|
default: '3px 2px'
|
|
}
|
|
},
|
|
|
|
emits: [ 'update:modelValue' ],
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
const minProp = vue.computed(() => parseInt(props.min, 10));
|
|
const maxProp = vue.computed(() => parseInt(props.max, 10));
|
|
const maxPagesProp = vue.computed(() => parseInt(props.maxPages, 10));
|
|
|
|
const inputPlaceholder = vue.computed(() => model.value + ' / ' + maxProp.value);
|
|
const boundaryLinksProp = vue.computed(() => getBool(props.boundaryLinks, props.input));
|
|
const boundaryNumbersProp = vue.computed(() => getBool(props.boundaryNumbers, !props.input));
|
|
const directionLinksProp = vue.computed(() => getBool(props.directionLinks, props.input));
|
|
const ellipsesProp = vue.computed(() => getBool(props.ellipses, !props.input));
|
|
|
|
const newPage = vue.ref(null);
|
|
const model = vue.computed({
|
|
get: () => props.modelValue,
|
|
set: val => {
|
|
val = parseInt(val, 10);
|
|
if (props.disable || isNaN(val)) {
|
|
return
|
|
}
|
|
const value = between(val, minProp.value, maxProp.value);
|
|
if (props.modelValue !== value) {
|
|
emit('update:modelValue', value);
|
|
}
|
|
}
|
|
});
|
|
|
|
vue.watch(() => `${ minProp.value }|${ maxProp.value }`, () => {
|
|
model.value = props.modelValue;
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-pagination row no-wrap items-center'
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
);
|
|
|
|
const gutterProp = vue.computed(() => (
|
|
props.gutter in btnPadding
|
|
? `${ btnPadding[ props.gutter ] }px`
|
|
: props.gutter || null
|
|
));
|
|
const gutterStyle = vue.computed(() => (
|
|
gutterProp.value !== null
|
|
? `--q-pagination-gutter-parent:-${ gutterProp.value };--q-pagination-gutter-child:${ gutterProp.value }`
|
|
: null
|
|
));
|
|
|
|
const icons = vue.computed(() => {
|
|
const ico = [
|
|
props.iconFirst || $q.iconSet.pagination.first,
|
|
props.iconPrev || $q.iconSet.pagination.prev,
|
|
props.iconNext || $q.iconSet.pagination.next,
|
|
props.iconLast || $q.iconSet.pagination.last
|
|
];
|
|
return $q.lang.rtl === true ? ico.reverse() : ico
|
|
});
|
|
|
|
const attrs = vue.computed(() => ({
|
|
'aria-disabled': props.disable === true ? 'true' : 'false',
|
|
role: 'navigation'
|
|
}));
|
|
|
|
const btnDesignProp = vue.computed(() => getBtnDesign(props, 'flat'));
|
|
const btnProps = vue.computed(() => ({
|
|
[ btnDesignProp.value ]: true,
|
|
|
|
round: props.round,
|
|
rounded: props.rounded,
|
|
|
|
padding: props.padding,
|
|
|
|
color: props.color,
|
|
textColor: props.textColor,
|
|
|
|
size: props.size,
|
|
ripple: props.ripple !== null
|
|
? props.ripple
|
|
: true
|
|
}));
|
|
|
|
const btnActiveDesignProp = vue.computed(() => {
|
|
// we also reset non-active design
|
|
const acc = { [ btnDesignProp.value ]: false };
|
|
if (props.activeDesign !== '') {
|
|
acc[ props.activeDesign ] = true;
|
|
}
|
|
return acc
|
|
});
|
|
const activeBtnProps = vue.computed(() => ({
|
|
...btnActiveDesignProp.value,
|
|
color: props.activeColor || props.color,
|
|
textColor: props.activeTextColor || props.textColor
|
|
}));
|
|
|
|
const btnConfig = vue.computed(() => {
|
|
let maxPages = Math.max(
|
|
maxPagesProp.value,
|
|
1 + (ellipsesProp.value ? 2 : 0) + (boundaryNumbersProp.value ? 2 : 0)
|
|
);
|
|
|
|
const acc = {
|
|
pgFrom: minProp.value,
|
|
pgTo: maxProp.value,
|
|
ellipsesStart: false,
|
|
ellipsesEnd: false,
|
|
boundaryStart: false,
|
|
boundaryEnd: false,
|
|
marginalStyle: {
|
|
minWidth: `${ Math.max(2, String(maxProp.value).length) }em`
|
|
}
|
|
};
|
|
|
|
if (maxPagesProp.value && maxPages < (maxProp.value - minProp.value + 1)) {
|
|
maxPages = 1 + Math.floor(maxPages / 2) * 2;
|
|
acc.pgFrom = Math.max(minProp.value, Math.min(maxProp.value - maxPages + 1, props.modelValue - Math.floor(maxPages / 2)));
|
|
acc.pgTo = Math.min(maxProp.value, acc.pgFrom + maxPages - 1);
|
|
|
|
if (boundaryNumbersProp.value) {
|
|
acc.boundaryStart = true;
|
|
acc.pgFrom++;
|
|
}
|
|
|
|
if (ellipsesProp.value && acc.pgFrom > (minProp.value + (boundaryNumbersProp.value ? 1 : 0))) {
|
|
acc.ellipsesStart = true;
|
|
acc.pgFrom++;
|
|
}
|
|
|
|
if (boundaryNumbersProp.value) {
|
|
acc.boundaryEnd = true;
|
|
acc.pgTo--;
|
|
}
|
|
|
|
if (ellipsesProp.value && acc.pgTo < (maxProp.value - (boundaryNumbersProp.value ? 1 : 0))) {
|
|
acc.ellipsesEnd = true;
|
|
acc.pgTo--;
|
|
}
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
function set (value) {
|
|
model.value = value;
|
|
}
|
|
|
|
function setByOffset (offset) {
|
|
model.value = model.value + offset;
|
|
}
|
|
|
|
const inputEvents = vue.computed(() => {
|
|
function updateModel () {
|
|
model.value = newPage.value;
|
|
newPage.value = null;
|
|
}
|
|
|
|
return {
|
|
'onUpdate:modelValue': val => { newPage.value = val; },
|
|
onKeyup: e => { isKeyCode(e, 13) === true && updateModel(); },
|
|
onBlur: updateModel
|
|
}
|
|
});
|
|
|
|
function getBtn (cfg, page, active) {
|
|
const data = {
|
|
'aria-label': page,
|
|
'aria-current': 'false',
|
|
...btnProps.value,
|
|
...cfg
|
|
};
|
|
|
|
if (active === true) {
|
|
Object.assign(data, {
|
|
'aria-current': 'true',
|
|
...activeBtnProps.value
|
|
});
|
|
}
|
|
|
|
if (page !== void 0) {
|
|
if (props.toFn !== void 0) {
|
|
data.to = props.toFn(page);
|
|
}
|
|
else {
|
|
data.onClick = () => { set(page); };
|
|
}
|
|
}
|
|
|
|
return vue.h(QBtn, data)
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { set, setByOffset });
|
|
|
|
return () => {
|
|
const contentStart = [];
|
|
const contentEnd = [];
|
|
let contentMiddle;
|
|
|
|
if (boundaryLinksProp.value === true) {
|
|
contentStart.push(
|
|
getBtn({
|
|
key: 'bls',
|
|
disable: props.disable || props.modelValue <= minProp.value,
|
|
icon: icons.value[ 0 ]
|
|
}, minProp.value)
|
|
);
|
|
|
|
contentEnd.unshift(
|
|
getBtn({
|
|
key: 'ble',
|
|
disable: props.disable || props.modelValue >= maxProp.value,
|
|
icon: icons.value[ 3 ]
|
|
}, maxProp.value)
|
|
);
|
|
}
|
|
|
|
if (directionLinksProp.value === true) {
|
|
contentStart.push(
|
|
getBtn({
|
|
key: 'bdp',
|
|
disable: props.disable || props.modelValue <= minProp.value,
|
|
icon: icons.value[ 1 ]
|
|
}, props.modelValue - 1)
|
|
);
|
|
|
|
contentEnd.unshift(
|
|
getBtn({
|
|
key: 'bdn',
|
|
disable: props.disable || props.modelValue >= maxProp.value,
|
|
icon: icons.value[ 2 ]
|
|
}, props.modelValue + 1)
|
|
);
|
|
}
|
|
|
|
if (props.input !== true) { // has buttons instead of inputbox
|
|
contentMiddle = [];
|
|
const { pgFrom, pgTo, marginalStyle: style } = btnConfig.value;
|
|
|
|
if (btnConfig.value.boundaryStart === true) {
|
|
const active = minProp.value === props.modelValue;
|
|
contentStart.push(
|
|
getBtn({
|
|
key: 'bns',
|
|
style,
|
|
disable: props.disable,
|
|
label: minProp.value
|
|
}, minProp.value, active)
|
|
);
|
|
}
|
|
|
|
if (btnConfig.value.boundaryEnd === true) {
|
|
const active = maxProp.value === props.modelValue;
|
|
contentEnd.unshift(
|
|
getBtn({
|
|
key: 'bne',
|
|
style,
|
|
disable: props.disable,
|
|
label: maxProp.value
|
|
}, maxProp.value, active)
|
|
);
|
|
}
|
|
|
|
if (btnConfig.value.ellipsesStart === true) {
|
|
contentStart.push(
|
|
getBtn({
|
|
key: 'bes',
|
|
style,
|
|
disable: props.disable,
|
|
label: '…',
|
|
ripple: false
|
|
}, pgFrom - 1)
|
|
);
|
|
}
|
|
|
|
if (btnConfig.value.ellipsesEnd === true) {
|
|
contentEnd.unshift(
|
|
getBtn({
|
|
key: 'bee',
|
|
style,
|
|
disable: props.disable,
|
|
label: '…',
|
|
ripple: false
|
|
}, pgTo + 1)
|
|
);
|
|
}
|
|
|
|
for (let i = pgFrom; i <= pgTo; i++) {
|
|
contentMiddle.push(
|
|
getBtn({
|
|
key: `bpg${ i }`,
|
|
style,
|
|
disable: props.disable,
|
|
label: i
|
|
}, i, i === props.modelValue)
|
|
);
|
|
}
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
...attrs.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-pagination__content row no-wrap items-center',
|
|
style: gutterStyle.value
|
|
}, [
|
|
...contentStart,
|
|
|
|
props.input === true
|
|
? vue.h(QInput, {
|
|
class: 'inline',
|
|
style: { width: `${ inputPlaceholder.value.length / 1.5 }em` },
|
|
type: 'number',
|
|
dense: true,
|
|
value: newPage.value,
|
|
disable: props.disable,
|
|
dark: isDark.value,
|
|
borderless: true,
|
|
inputClass: props.inputClass,
|
|
inputStyle: props.inputStyle,
|
|
placeholder: inputPlaceholder.value,
|
|
min: minProp.value,
|
|
max: maxProp.value,
|
|
...inputEvents.value
|
|
})
|
|
: vue.h('div', {
|
|
class: 'q-pagination__middle row justify-center'
|
|
}, contentMiddle),
|
|
|
|
...contentEnd
|
|
])
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
function frameDebounce (fn) {
|
|
let wait = false, frame, callArgs;
|
|
|
|
function debounced (/* ...args */) {
|
|
callArgs = arguments;
|
|
if (wait === true) { return }
|
|
|
|
wait = true;
|
|
frame = requestAnimationFrame(() => {
|
|
fn.apply(this, callArgs);
|
|
callArgs = void 0;
|
|
wait = false;
|
|
});
|
|
}
|
|
|
|
debounced.cancel = () => {
|
|
window.cancelAnimationFrame(frame);
|
|
wait = false;
|
|
};
|
|
|
|
return debounced
|
|
}
|
|
|
|
const { passive: passive$1 } = listenOpts;
|
|
|
|
var QParallax = createComponent({
|
|
name: 'QParallax',
|
|
|
|
props: {
|
|
src: String,
|
|
height: {
|
|
type: Number,
|
|
default: 500
|
|
},
|
|
speed: {
|
|
type: Number,
|
|
default: 1,
|
|
validator: v => v >= 0 && v <= 1
|
|
},
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
},
|
|
|
|
onScroll: Function
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
const percentScrolled = vue.ref(0);
|
|
const rootRef = vue.ref(null);
|
|
const mediaParentRef = vue.ref(null);
|
|
const mediaRef = vue.ref(null);
|
|
|
|
let isWorking, mediaEl, mediaHeight, resizeHandler, observer, localScrollTarget;
|
|
|
|
vue.watch(() => props.height, () => {
|
|
isWorking === true && updatePos();
|
|
});
|
|
|
|
vue.watch(() => props.scrollTarget, () => {
|
|
if (isWorking === true) {
|
|
stop();
|
|
start();
|
|
}
|
|
});
|
|
|
|
let update = percentage => {
|
|
percentScrolled.value = percentage;
|
|
props.onScroll !== void 0 && emit('scroll', percentage);
|
|
};
|
|
|
|
function updatePos () {
|
|
let containerTop, containerHeight, containerBottom;
|
|
|
|
if (localScrollTarget === window) {
|
|
containerTop = 0;
|
|
containerBottom = containerHeight = window.innerHeight;
|
|
}
|
|
else {
|
|
containerTop = offset(localScrollTarget).top;
|
|
containerHeight = height(localScrollTarget);
|
|
containerBottom = containerTop + containerHeight;
|
|
}
|
|
|
|
const top = offset(rootRef.value).top;
|
|
const bottom = top + props.height;
|
|
|
|
if (observer !== void 0 || (bottom > containerTop && top < containerBottom)) {
|
|
const percent = (containerBottom - top) / (props.height + containerHeight);
|
|
setPos((mediaHeight - props.height) * percent * props.speed);
|
|
update(percent);
|
|
}
|
|
}
|
|
|
|
let setPos = offset => {
|
|
// apply it immediately without any delay
|
|
mediaEl.style.transform = `translate3d(-50%,${ Math.round(offset) }px,0)`;
|
|
};
|
|
|
|
function onResize () {
|
|
mediaHeight = mediaEl.naturalHeight || mediaEl.videoHeight || height(mediaEl);
|
|
isWorking === true && updatePos();
|
|
}
|
|
|
|
function start () {
|
|
isWorking = true;
|
|
localScrollTarget = getScrollTarget(rootRef.value, props.scrollTarget);
|
|
localScrollTarget.addEventListener('scroll', updatePos, passive$1);
|
|
window.addEventListener('resize', resizeHandler, passive$1);
|
|
updatePos();
|
|
}
|
|
|
|
function stop () {
|
|
if (isWorking === true) {
|
|
isWorking = false;
|
|
localScrollTarget.removeEventListener('scroll', updatePos, passive$1);
|
|
window.removeEventListener('resize', resizeHandler, passive$1);
|
|
localScrollTarget = void 0;
|
|
setPos.cancel();
|
|
update.cancel();
|
|
resizeHandler.cancel();
|
|
}
|
|
}
|
|
|
|
vue.onMounted(() => {
|
|
setPos = frameDebounce(setPos);
|
|
update = frameDebounce(update);
|
|
resizeHandler = frameDebounce(onResize);
|
|
|
|
mediaEl = slots.media !== void 0
|
|
? mediaParentRef.value.children[ 0 ]
|
|
: mediaRef.value;
|
|
|
|
mediaEl.onload = mediaEl.onloadstart = mediaEl.loadedmetadata = onResize;
|
|
onResize();
|
|
mediaEl.style.display = 'initial';
|
|
|
|
if (window.IntersectionObserver !== void 0) {
|
|
observer = new IntersectionObserver(entries => {
|
|
const fn = entries[ 0 ].isIntersecting === true ? start : stop;
|
|
fn();
|
|
});
|
|
|
|
observer.observe(rootRef.value);
|
|
}
|
|
else {
|
|
start();
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
stop();
|
|
observer !== void 0 && observer.disconnect();
|
|
mediaEl.onload = mediaEl.onloadstart = mediaEl.loadedmetadata = null;
|
|
});
|
|
|
|
return () => {
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: 'q-parallax',
|
|
style: { height: `${ props.height }px` }
|
|
}, [
|
|
vue.h('div', {
|
|
ref: mediaParentRef,
|
|
class: 'q-parallax__media absolute-full'
|
|
}, slots.media !== void 0 ? slots.media() : [
|
|
vue.h('img', {
|
|
ref: mediaRef,
|
|
src: props.src
|
|
})
|
|
]),
|
|
|
|
vue.h(
|
|
'div',
|
|
{ class: 'q-parallax__content absolute-full column flex-center' },
|
|
slots.content !== void 0
|
|
? slots.content({ percentScrolled: percentScrolled.value })
|
|
: hSlot(slots.default)
|
|
)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
// adapted from https://stackoverflow.com/a/40294058
|
|
|
|
function cloneDeep (data, hash = new WeakMap()) {
|
|
if (Object(data) !== data) return data
|
|
if (hash.has(data)) return hash.get(data)
|
|
|
|
const result = data instanceof Date
|
|
? new Date(data)
|
|
: (data instanceof RegExp
|
|
? new RegExp(data.source, data.flags)
|
|
: (data instanceof Set
|
|
? new Set()
|
|
: (data instanceof Map
|
|
? new Map()
|
|
: (typeof data.constructor !== 'function'
|
|
? Object.create(null)
|
|
: (data.prototype !== void 0 && typeof data.prototype.constructor === 'function'
|
|
? data
|
|
: new data.constructor()
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
if (typeof data.constructor === 'function' && typeof data.valueOf === 'function') {
|
|
const val = data.valueOf();
|
|
|
|
if (Object(val) !== val) {
|
|
const result = new data.constructor(val);
|
|
|
|
hash.set(data, result);
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
hash.set(data, result);
|
|
|
|
if (data instanceof Set) {
|
|
data.forEach(val => {
|
|
result.add(cloneDeep(val, hash));
|
|
});
|
|
}
|
|
else if (data instanceof Map) {
|
|
data.forEach((val, key) => {
|
|
result.set(key, cloneDeep(val, hash));
|
|
});
|
|
}
|
|
|
|
return Object.assign(
|
|
result,
|
|
...Object.keys(data).map(key => ({ [ key ]: cloneDeep(data[ key ], hash) }))
|
|
)
|
|
}
|
|
|
|
var QPopupEdit = createComponent({
|
|
name: 'QPopupEdit',
|
|
|
|
props: {
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
title: String,
|
|
buttons: Boolean,
|
|
labelSet: String,
|
|
labelCancel: String,
|
|
|
|
color: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
validate: {
|
|
type: Function,
|
|
default: () => true
|
|
},
|
|
|
|
autoSave: Boolean,
|
|
|
|
/* menu props overrides */
|
|
cover: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/* end of menu props */
|
|
|
|
disable: Boolean
|
|
},
|
|
|
|
emits: [
|
|
'update:modelValue', 'save', 'cancel',
|
|
'beforeShow', 'show', 'beforeHide', 'hide'
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const menuRef = vue.ref(null);
|
|
|
|
const initialValue = vue.ref('');
|
|
const currentModel = vue.ref('');
|
|
|
|
let validated = false;
|
|
|
|
const scope = vue.computed(() => {
|
|
return injectProp({
|
|
initialValue: initialValue.value,
|
|
validate: props.validate,
|
|
set,
|
|
cancel,
|
|
updatePosition
|
|
}, 'value', () => currentModel.value, val => { currentModel.value = val; })
|
|
});
|
|
|
|
function set () {
|
|
if (props.validate(currentModel.value) === false) {
|
|
return
|
|
}
|
|
|
|
if (hasModelChanged() === true) {
|
|
emit('save', currentModel.value, initialValue.value);
|
|
emit('update:modelValue', currentModel.value);
|
|
}
|
|
|
|
closeMenu();
|
|
}
|
|
|
|
function cancel () {
|
|
if (hasModelChanged() === true) {
|
|
emit('cancel', currentModel.value, initialValue.value);
|
|
}
|
|
|
|
closeMenu();
|
|
}
|
|
|
|
function updatePosition () {
|
|
vue.nextTick(() => {
|
|
menuRef.value.updatePosition();
|
|
});
|
|
}
|
|
|
|
function hasModelChanged () {
|
|
return isDeepEqual(currentModel.value, initialValue.value) === false
|
|
}
|
|
|
|
function closeMenu () {
|
|
validated = true;
|
|
menuRef.value.hide();
|
|
}
|
|
|
|
function onBeforeShow () {
|
|
validated = false;
|
|
initialValue.value = cloneDeep(props.modelValue);
|
|
currentModel.value = cloneDeep(props.modelValue);
|
|
emit('beforeShow');
|
|
}
|
|
|
|
function onShow () {
|
|
emit('show');
|
|
}
|
|
|
|
function onBeforeHide () {
|
|
if (validated === false && hasModelChanged() === true) {
|
|
if (props.autoSave === true && props.validate(currentModel.value) === true) {
|
|
emit('save', currentModel.value, initialValue.value);
|
|
emit('update:modelValue', currentModel.value);
|
|
}
|
|
else {
|
|
emit('cancel', currentModel.value, initialValue.value);
|
|
}
|
|
}
|
|
|
|
emit('beforeHide');
|
|
}
|
|
|
|
function onHide () {
|
|
emit('hide');
|
|
}
|
|
|
|
function getContent () {
|
|
const child = slots.default !== void 0
|
|
? [].concat(slots.default(scope.value))
|
|
: [];
|
|
|
|
props.title && child.unshift(
|
|
vue.h('div', { class: 'q-dialog__title q-mt-sm q-mb-sm' }, props.title)
|
|
);
|
|
|
|
props.buttons === true && child.push(
|
|
vue.h('div', { class: 'q-popup-edit__buttons row justify-center no-wrap' }, [
|
|
vue.h(QBtn, {
|
|
flat: true,
|
|
color: props.color,
|
|
label: props.labelCancel || $q.lang.label.cancel,
|
|
onClick: cancel
|
|
}),
|
|
vue.h(QBtn, {
|
|
flat: true,
|
|
color: props.color,
|
|
label: props.labelSet || $q.lang.label.set,
|
|
onClick: set
|
|
})
|
|
])
|
|
);
|
|
|
|
return child
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
set,
|
|
cancel,
|
|
show (e) { menuRef.value !== null && menuRef.value.show(e); },
|
|
hide (e) { menuRef.value !== null && menuRef.value.hide(e); },
|
|
updatePosition
|
|
});
|
|
|
|
return () => {
|
|
if (props.disable === true) { return }
|
|
|
|
return vue.h(QMenu, {
|
|
ref: menuRef,
|
|
class: 'q-popup-edit',
|
|
cover: props.cover,
|
|
onBeforeShow,
|
|
onShow,
|
|
onBeforeHide,
|
|
onHide,
|
|
onEscapeKey: cancel
|
|
}, getContent)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QPopupProxy = createComponent({
|
|
name: 'QPopupProxy',
|
|
|
|
props: {
|
|
...useAnchorProps,
|
|
|
|
breakpoint: {
|
|
type: [ String, Number ],
|
|
default: 450
|
|
}
|
|
},
|
|
|
|
emits: [ 'show', 'hide' ],
|
|
|
|
setup (props, { slots, emit, attrs }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const showing = vue.ref(false);
|
|
const popupRef = vue.ref(null);
|
|
const breakpoint = vue.computed(() => parseInt(props.breakpoint, 10));
|
|
|
|
const { canShow } = useAnchor({ showing });
|
|
|
|
function getType () {
|
|
return $q.screen.width < breakpoint.value || $q.screen.height < breakpoint.value
|
|
? 'dialog'
|
|
: 'menu'
|
|
}
|
|
|
|
const type = vue.ref(getType());
|
|
|
|
const popupProps = vue.computed(() => (
|
|
type.value === 'menu' ? { maxHeight: '99vh' } : {})
|
|
);
|
|
|
|
vue.watch(() => getType(), val => {
|
|
if (showing.value !== true) {
|
|
type.value = val;
|
|
}
|
|
});
|
|
|
|
function onShow (evt) {
|
|
showing.value = true;
|
|
emit('show', evt);
|
|
}
|
|
|
|
function onHide (evt) {
|
|
showing.value = false;
|
|
type.value = getType();
|
|
emit('hide', evt);
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
show (evt) { canShow(evt) === true && popupRef.value.show(evt); },
|
|
hide (evt) { popupRef.value.hide(evt); },
|
|
toggle (evt) { popupRef.value.toggle(evt); }
|
|
});
|
|
|
|
injectProp(proxy, 'currentComponent', () => ({
|
|
type: type.value,
|
|
ref: popupRef.value
|
|
}));
|
|
|
|
return () => {
|
|
const data = {
|
|
ref: popupRef,
|
|
...popupProps.value,
|
|
...attrs,
|
|
onShow,
|
|
onHide
|
|
};
|
|
|
|
let component;
|
|
|
|
if (type.value === 'dialog') {
|
|
component = QDialog;
|
|
}
|
|
else {
|
|
component = QMenu;
|
|
Object.assign(data, {
|
|
target: props.target,
|
|
contextMenu: props.contextMenu,
|
|
noParentEvent: true,
|
|
separateClosePopup: true
|
|
});
|
|
}
|
|
|
|
return vue.h(component, data, slots.default)
|
|
}
|
|
}
|
|
});
|
|
|
|
const defaultSizes = {
|
|
xs: 2,
|
|
sm: 4,
|
|
md: 6,
|
|
lg: 10,
|
|
xl: 14
|
|
};
|
|
|
|
function width (val, reverse, $q) {
|
|
return {
|
|
transform: reverse === true
|
|
? `translateX(${ $q.lang.rtl === true ? '-' : '' }100%) scale3d(${ -val },1,1)`
|
|
: `scale3d(${ val },1,1)`
|
|
}
|
|
}
|
|
|
|
var QLinearProgress = createComponent({
|
|
name: 'QLinearProgress',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useSizeProps,
|
|
|
|
value: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
buffer: Number,
|
|
|
|
color: String,
|
|
trackColor: String,
|
|
|
|
reverse: Boolean,
|
|
stripe: Boolean,
|
|
indeterminate: Boolean,
|
|
query: Boolean,
|
|
rounded: Boolean,
|
|
|
|
animationSpeed: {
|
|
type: [ String, Number ],
|
|
default: 2100
|
|
},
|
|
|
|
instantFeedback: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, proxy.$q);
|
|
const sizeStyle = useSize(props, defaultSizes);
|
|
|
|
const motion = vue.computed(() => props.indeterminate === true || props.query === true);
|
|
const widthReverse = vue.computed(() => props.reverse !== props.query);
|
|
const style = vue.computed(() => ({
|
|
...(sizeStyle.value !== null ? sizeStyle.value : {}),
|
|
'--q-linear-progress-speed': `${ props.animationSpeed }ms`
|
|
}));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-linear-progress'
|
|
+ (props.color !== void 0 ? ` text-${ props.color }` : '')
|
|
+ (props.reverse === true || props.query === true ? ' q-linear-progress--reverse' : '')
|
|
+ (props.rounded === true ? ' rounded-borders' : '')
|
|
);
|
|
|
|
const trackStyle = vue.computed(() => width(props.buffer !== void 0 ? props.buffer : 1, widthReverse.value, proxy.$q));
|
|
const transitionSuffix = vue.computed(() => `with${ props.instantFeedback === true ? 'out' : '' }-transition`);
|
|
|
|
const trackClass = vue.computed(() =>
|
|
'q-linear-progress__track absolute-full'
|
|
+ ` q-linear-progress__track--${ transitionSuffix.value }`
|
|
+ ` q-linear-progress__track--${ isDark.value === true ? 'dark' : 'light' }`
|
|
+ (props.trackColor !== void 0 ? ` bg-${ props.trackColor }` : '')
|
|
);
|
|
|
|
const modelStyle = vue.computed(() => width(motion.value === true ? 1 : props.value, widthReverse.value, proxy.$q));
|
|
const modelClass = vue.computed(() =>
|
|
'q-linear-progress__model absolute-full'
|
|
+ ` q-linear-progress__model--${ transitionSuffix.value }`
|
|
+ ` q-linear-progress__model--${ motion.value === true ? 'in' : '' }determinate`
|
|
);
|
|
|
|
const stripeStyle = vue.computed(() => ({ width: `${ props.value * 100 }%` }));
|
|
const stripeClass = vue.computed(() =>
|
|
`q-linear-progress__stripe absolute-${ props.reverse === true ? 'right' : 'left' }`
|
|
+ ` q-linear-progress__stripe--${ transitionSuffix.value }`
|
|
);
|
|
|
|
return () => {
|
|
const child = [
|
|
vue.h('div', {
|
|
class: trackClass.value,
|
|
style: trackStyle.value
|
|
}),
|
|
|
|
vue.h('div', {
|
|
class: modelClass.value,
|
|
style: modelStyle.value
|
|
})
|
|
];
|
|
|
|
props.stripe === true && motion.value === false && child.push(
|
|
vue.h('div', {
|
|
class: stripeClass.value,
|
|
style: stripeStyle.value
|
|
})
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value,
|
|
role: 'progressbar',
|
|
'aria-valuemin': 0,
|
|
'aria-valuemax': 1,
|
|
'aria-valuenow': props.indeterminate === true
|
|
? void 0
|
|
: props.value
|
|
}, hMergeSlot(slots.default, child))
|
|
}
|
|
}
|
|
});
|
|
|
|
const
|
|
PULLER_HEIGHT = 40,
|
|
OFFSET_TOP = 20;
|
|
|
|
var QPullToRefresh = createComponent({
|
|
name: 'QPullToRefresh',
|
|
|
|
props: {
|
|
color: String,
|
|
bgColor: String,
|
|
icon: String,
|
|
noMouse: Boolean,
|
|
disable: Boolean,
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
}
|
|
},
|
|
|
|
emits: [ 'refresh' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const state = vue.ref('pull');
|
|
const pullRatio = vue.ref(0);
|
|
const pulling = vue.ref(false);
|
|
const pullPosition = vue.ref(-PULLER_HEIGHT);
|
|
const animating = vue.ref(false);
|
|
const positionCSS = vue.ref({});
|
|
|
|
const style = vue.computed(() => ({
|
|
opacity: pullRatio.value,
|
|
transform: `translateY(${ pullPosition.value }px) rotate(${ pullRatio.value * 360 }deg)`
|
|
}));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-pull-to-refresh__puller row flex-center'
|
|
+ (animating.value === true ? ' q-pull-to-refresh__puller--animating' : '')
|
|
+ (props.bgColor !== void 0 ? ` bg-${ props.bgColor }` : '')
|
|
);
|
|
|
|
function pull (event) {
|
|
if (event.isFinal === true) {
|
|
if (pulling.value === true) {
|
|
pulling.value = false;
|
|
|
|
if (state.value === 'pulled') {
|
|
state.value = 'refreshing';
|
|
animateTo({ pos: OFFSET_TOP });
|
|
trigger();
|
|
}
|
|
else if (state.value === 'pull') {
|
|
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 });
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if (animating.value === true || state.value === 'refreshing') {
|
|
return false
|
|
}
|
|
|
|
if (event.isFirst === true) {
|
|
if (getVerticalScrollPosition(localScrollTarget) !== 0 || event.direction !== 'down') {
|
|
if (pulling.value === true) {
|
|
pulling.value = false;
|
|
state.value = 'pull';
|
|
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 });
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
pulling.value = true;
|
|
|
|
const { top, left } = $el.getBoundingClientRect();
|
|
positionCSS.value = {
|
|
top: top + 'px',
|
|
left: left + 'px',
|
|
width: window.getComputedStyle($el).getPropertyValue('width')
|
|
};
|
|
}
|
|
|
|
prevent(event.evt);
|
|
|
|
const distance = Math.min(140, Math.max(0, event.distance.y));
|
|
pullPosition.value = distance - PULLER_HEIGHT;
|
|
pullRatio.value = between(distance / (OFFSET_TOP + PULLER_HEIGHT), 0, 1);
|
|
|
|
const newState = pullPosition.value > OFFSET_TOP ? 'pulled' : 'pull';
|
|
|
|
if (state.value !== newState) {
|
|
state.value = newState;
|
|
}
|
|
}
|
|
|
|
const directives = vue.computed(() => {
|
|
// if props.disable === false
|
|
const modifiers = { down: true };
|
|
|
|
if (props.noMouse !== true) {
|
|
modifiers.mouse = true;
|
|
}
|
|
|
|
return [ [
|
|
TouchPan,
|
|
pull,
|
|
void 0,
|
|
modifiers
|
|
] ]
|
|
});
|
|
|
|
const contentClass = vue.computed(() =>
|
|
`q-pull-to-refresh__content${ pulling.value === true ? ' no-pointer-events' : '' }`
|
|
);
|
|
|
|
function trigger () {
|
|
emit('refresh', () => {
|
|
animateTo({ pos: -PULLER_HEIGHT, ratio: 0 }, () => {
|
|
state.value = 'pull';
|
|
});
|
|
});
|
|
}
|
|
|
|
let $el, localScrollTarget, timer = null;
|
|
|
|
function animateTo ({ pos, ratio }, done) {
|
|
animating.value = true;
|
|
pullPosition.value = pos;
|
|
|
|
if (ratio !== void 0) {
|
|
pullRatio.value = ratio;
|
|
}
|
|
|
|
timer !== null && clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
animating.value = false;
|
|
done && done();
|
|
}, 300);
|
|
}
|
|
|
|
function updateScrollTarget () {
|
|
localScrollTarget = getScrollTarget($el, props.scrollTarget);
|
|
}
|
|
|
|
vue.watch(() => props.scrollTarget, updateScrollTarget);
|
|
|
|
vue.onMounted(() => {
|
|
$el = proxy.$el;
|
|
updateScrollTarget();
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
timer !== null && clearTimeout(timer);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { trigger, updateScrollTarget });
|
|
|
|
return () => {
|
|
const child = [
|
|
vue.h('div', { class: contentClass.value }, hSlot(slots.default)),
|
|
|
|
vue.h('div', {
|
|
class: 'q-pull-to-refresh__puller-container fixed row flex-center no-pointer-events z-top',
|
|
style: positionCSS.value
|
|
}, [
|
|
vue.h('div', {
|
|
class: classes.value,
|
|
style: style.value
|
|
}, [
|
|
state.value !== 'refreshing'
|
|
? vue.h(QIcon, {
|
|
name: props.icon || $q.iconSet.pullToRefresh.icon,
|
|
color: props.color,
|
|
size: '32px'
|
|
})
|
|
: vue.h(QSpinner, {
|
|
size: '24px',
|
|
color: props.color
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
return hDir(
|
|
'div',
|
|
{ class: 'q-pull-to-refresh' },
|
|
child,
|
|
'main',
|
|
props.disable === false,
|
|
() => directives.value
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
const dragType = {
|
|
MIN: 0,
|
|
RANGE: 1,
|
|
MAX: 2
|
|
};
|
|
|
|
var QRange = createComponent({
|
|
name: 'QRange',
|
|
|
|
props: {
|
|
...useSliderProps,
|
|
|
|
modelValue: {
|
|
type: Object,
|
|
default: () => ({ min: null, max: null }),
|
|
validator: v => 'min' in v && 'max' in v
|
|
},
|
|
|
|
dragRange: Boolean,
|
|
dragOnlyRange: Boolean,
|
|
|
|
leftLabelColor: String,
|
|
leftLabelTextColor: String,
|
|
rightLabelColor: String,
|
|
rightLabelTextColor: String,
|
|
|
|
leftLabelValue: [ String, Number ],
|
|
rightLabelValue: [ String, Number ],
|
|
|
|
leftThumbColor: String,
|
|
rightThumbColor: String
|
|
},
|
|
|
|
emits: useSliderEmits,
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const { state, methods } = useSlider({
|
|
updateValue, updatePosition, getDragging,
|
|
formAttrs: vue.computed(() => ({
|
|
type: 'hidden',
|
|
name: props.name,
|
|
value: `${ props.modelValue.min }|${ props.modelValue.max }`
|
|
}))
|
|
});
|
|
|
|
const rootRef = vue.ref(null);
|
|
const curMinRatio = vue.ref(0);
|
|
const curMaxRatio = vue.ref(0);
|
|
const model = vue.ref({ min: 0, max: 0 });
|
|
|
|
function normalizeModel () {
|
|
model.value.min = props.modelValue.min === null
|
|
? state.innerMin.value
|
|
: between(props.modelValue.min, state.innerMin.value, state.innerMax.value);
|
|
|
|
model.value.max = props.modelValue.max === null
|
|
? state.innerMax.value
|
|
: between(props.modelValue.max, state.innerMin.value, state.innerMax.value);
|
|
}
|
|
|
|
vue.watch(
|
|
() => `${ props.modelValue.min }|${ props.modelValue.max }|${ state.innerMin.value }|${ state.innerMax.value }`,
|
|
normalizeModel
|
|
);
|
|
|
|
normalizeModel();
|
|
|
|
const modelMinRatio = vue.computed(() => methods.convertModelToRatio(model.value.min));
|
|
const modelMaxRatio = vue.computed(() => methods.convertModelToRatio(model.value.max));
|
|
|
|
const ratioMin = vue.computed(() => (
|
|
state.active.value === true ? curMinRatio.value : modelMinRatio.value
|
|
));
|
|
const ratioMax = vue.computed(() => (
|
|
state.active.value === true ? curMaxRatio.value : modelMaxRatio.value
|
|
));
|
|
|
|
const selectionBarStyle = vue.computed(() => {
|
|
const acc = {
|
|
[ state.positionProp.value ]: `${ 100 * ratioMin.value }%`,
|
|
[ state.sizeProp.value ]: `${ 100 * (ratioMax.value - ratioMin.value) }%`
|
|
};
|
|
if (props.selectionImg !== void 0) {
|
|
acc.backgroundImage = `url(${ props.selectionImg }) !important`;
|
|
}
|
|
return acc
|
|
});
|
|
|
|
const trackContainerEvents = vue.computed(() => {
|
|
if (state.editable.value !== true) {
|
|
return {}
|
|
}
|
|
|
|
if ($q.platform.is.mobile === true) {
|
|
return { onClick: methods.onMobileClick }
|
|
}
|
|
|
|
const evt = { onMousedown: methods.onActivate };
|
|
|
|
if (props.dragRange === true || props.dragOnlyRange === true) {
|
|
Object.assign(evt, {
|
|
onFocus: () => { state.focus.value = 'both'; },
|
|
onBlur: methods.onBlur,
|
|
onKeydown,
|
|
onKeyup: methods.onKeyup
|
|
});
|
|
}
|
|
|
|
return evt
|
|
});
|
|
|
|
function getEvents (side) {
|
|
return $q.platform.is.mobile !== true && state.editable.value === true && props.dragOnlyRange !== true
|
|
? {
|
|
onFocus: () => { state.focus.value = side; },
|
|
onBlur: methods.onBlur,
|
|
onKeydown,
|
|
onKeyup: methods.onKeyup
|
|
}
|
|
: {}
|
|
}
|
|
|
|
const thumbTabindex = vue.computed(() => (props.dragOnlyRange !== true ? state.tabindex.value : null));
|
|
const trackContainerTabindex = vue.computed(() => (
|
|
$q.platform.is.mobile !== true && (props.dragRange || props.dragOnlyRange === true)
|
|
? state.tabindex.value
|
|
: null
|
|
));
|
|
|
|
const minThumbRef = vue.ref(null);
|
|
const minEvents = vue.computed(() => getEvents('min'));
|
|
const getMinThumb = methods.getThumbRenderFn({
|
|
focusValue: 'min',
|
|
getNodeData: () => ({
|
|
ref: minThumbRef,
|
|
key: 'tmin',
|
|
...minEvents.value,
|
|
tabindex: thumbTabindex.value
|
|
}),
|
|
ratio: ratioMin,
|
|
label: vue.computed(() => (
|
|
props.leftLabelValue !== void 0
|
|
? props.leftLabelValue
|
|
: model.value.min
|
|
)),
|
|
thumbColor: vue.computed(() => props.leftThumbColor || props.thumbColor || props.color),
|
|
labelColor: vue.computed(() => props.leftLabelColor || props.labelColor),
|
|
labelTextColor: vue.computed(() => props.leftLabelTextColor || props.labelTextColor)
|
|
});
|
|
|
|
const maxEvents = vue.computed(() => getEvents('max'));
|
|
const getMaxThumb = methods.getThumbRenderFn({
|
|
focusValue: 'max',
|
|
getNodeData: () => ({
|
|
...maxEvents.value,
|
|
key: 'tmax',
|
|
tabindex: thumbTabindex.value
|
|
}),
|
|
ratio: ratioMax,
|
|
label: vue.computed(() => (
|
|
props.rightLabelValue !== void 0
|
|
? props.rightLabelValue
|
|
: model.value.max
|
|
)),
|
|
thumbColor: vue.computed(() => props.rightThumbColor || props.thumbColor || props.color),
|
|
labelColor: vue.computed(() => props.rightLabelColor || props.labelColor),
|
|
labelTextColor: vue.computed(() => props.rightLabelTextColor || props.labelTextColor)
|
|
});
|
|
|
|
function updateValue (change) {
|
|
if (model.value.min !== props.modelValue.min || model.value.max !== props.modelValue.max) {
|
|
emit('update:modelValue', { ...model.value });
|
|
}
|
|
change === true && emit('change', { ...model.value });
|
|
}
|
|
|
|
function getDragging (event) {
|
|
const
|
|
{ left, top, width, height } = rootRef.value.getBoundingClientRect(),
|
|
sensitivity = props.dragOnlyRange === true
|
|
? 0
|
|
: (props.vertical === true
|
|
? minThumbRef.value.offsetHeight / (2 * height)
|
|
: minThumbRef.value.offsetWidth / (2 * width)
|
|
);
|
|
|
|
const dragging = {
|
|
left,
|
|
top,
|
|
width,
|
|
height,
|
|
valueMin: model.value.min,
|
|
valueMax: model.value.max,
|
|
ratioMin: modelMinRatio.value,
|
|
ratioMax: modelMaxRatio.value
|
|
};
|
|
|
|
const ratio = methods.getDraggingRatio(event, dragging);
|
|
|
|
if (props.dragOnlyRange !== true && ratio < dragging.ratioMin + sensitivity) {
|
|
dragging.type = dragType.MIN;
|
|
}
|
|
else if (props.dragOnlyRange === true || ratio < dragging.ratioMax - sensitivity) {
|
|
if (props.dragRange === true || props.dragOnlyRange === true) {
|
|
dragging.type = dragType.RANGE;
|
|
Object.assign(dragging, {
|
|
offsetRatio: ratio,
|
|
offsetModel: methods.convertRatioToModel(ratio),
|
|
rangeValue: dragging.valueMax - dragging.valueMin,
|
|
rangeRatio: dragging.ratioMax - dragging.ratioMin
|
|
});
|
|
}
|
|
else {
|
|
dragging.type = dragging.ratioMax - ratio < ratio - dragging.ratioMin
|
|
? dragType.MAX
|
|
: dragType.MIN;
|
|
}
|
|
}
|
|
else {
|
|
dragging.type = dragType.MAX;
|
|
}
|
|
|
|
return dragging
|
|
}
|
|
|
|
function updatePosition (event, dragging = state.dragging.value) {
|
|
let pos;
|
|
const ratio = methods.getDraggingRatio(event, dragging);
|
|
const localModel = methods.convertRatioToModel(ratio);
|
|
|
|
switch (dragging.type) {
|
|
case dragType.MIN:
|
|
if (ratio <= dragging.ratioMax) {
|
|
pos = {
|
|
minR: ratio,
|
|
maxR: dragging.ratioMax,
|
|
min: localModel,
|
|
max: dragging.valueMax
|
|
};
|
|
state.focus.value = 'min';
|
|
}
|
|
else {
|
|
pos = {
|
|
minR: dragging.ratioMax,
|
|
maxR: ratio,
|
|
min: dragging.valueMax,
|
|
max: localModel
|
|
};
|
|
state.focus.value = 'max';
|
|
}
|
|
break
|
|
|
|
case dragType.MAX:
|
|
if (ratio >= dragging.ratioMin) {
|
|
pos = {
|
|
minR: dragging.ratioMin,
|
|
maxR: ratio,
|
|
min: dragging.valueMin,
|
|
max: localModel
|
|
};
|
|
state.focus.value = 'max';
|
|
}
|
|
else {
|
|
pos = {
|
|
minR: ratio,
|
|
maxR: dragging.ratioMin,
|
|
min: localModel,
|
|
max: dragging.valueMin
|
|
};
|
|
state.focus.value = 'min';
|
|
}
|
|
break
|
|
|
|
case dragType.RANGE:
|
|
const
|
|
ratioDelta = ratio - dragging.offsetRatio,
|
|
minR = between(dragging.ratioMin + ratioDelta, 0, 1 - dragging.rangeRatio),
|
|
modelDelta = localModel - dragging.offsetModel,
|
|
min = between(dragging.valueMin + modelDelta, props.min, props.max - dragging.rangeValue);
|
|
|
|
pos = {
|
|
minR,
|
|
maxR: minR + dragging.rangeRatio,
|
|
min: parseFloat(min.toFixed(state.decimals.value)),
|
|
max: parseFloat((min + dragging.rangeValue).toFixed(state.decimals.value))
|
|
};
|
|
|
|
state.focus.value = 'both';
|
|
break
|
|
}
|
|
|
|
// If either of the values to be emitted are null, set them to the defaults the user has entered.
|
|
model.value = model.value.min === null || model.value.max === null
|
|
? { min: pos.min || props.min, max: pos.max || props.max }
|
|
: { min: pos.min, max: pos.max };
|
|
|
|
if (props.snap !== true || props.step === 0) {
|
|
curMinRatio.value = pos.minR;
|
|
curMaxRatio.value = pos.maxR;
|
|
}
|
|
else {
|
|
curMinRatio.value = methods.convertModelToRatio(model.value.min);
|
|
curMaxRatio.value = methods.convertModelToRatio(model.value.max);
|
|
}
|
|
}
|
|
|
|
function onKeydown (evt) {
|
|
if (!keyCodes$2.includes(evt.keyCode)) {
|
|
return
|
|
}
|
|
|
|
stopAndPrevent(evt);
|
|
|
|
const
|
|
stepVal = ([ 34, 33 ].includes(evt.keyCode) ? 10 : 1) * state.step.value,
|
|
offset = (
|
|
([ 34, 37, 40 ].includes(evt.keyCode) ? -1 : 1)
|
|
* (state.isReversed.value === true ? -1 : 1)
|
|
* (props.vertical === true ? -1 : 1) * stepVal
|
|
);
|
|
|
|
if (state.focus.value === 'both') {
|
|
const interval = model.value.max - model.value.min;
|
|
const min = between(
|
|
parseFloat((model.value.min + offset).toFixed(state.decimals.value)),
|
|
state.innerMin.value,
|
|
state.innerMax.value - interval
|
|
);
|
|
|
|
model.value = {
|
|
min,
|
|
max: parseFloat((min + interval).toFixed(state.decimals.value))
|
|
};
|
|
}
|
|
else if (state.focus.value === false) {
|
|
return
|
|
}
|
|
else {
|
|
const which = state.focus.value;
|
|
|
|
model.value = {
|
|
...model.value,
|
|
[ which ]: between(
|
|
parseFloat((model.value[ which ] + offset).toFixed(state.decimals.value)),
|
|
which === 'min' ? state.innerMin.value : model.value.min,
|
|
which === 'max' ? state.innerMax.value : model.value.max
|
|
)
|
|
};
|
|
}
|
|
|
|
updateValue();
|
|
}
|
|
|
|
return () => {
|
|
const content = methods.getContent(
|
|
selectionBarStyle,
|
|
trackContainerTabindex,
|
|
trackContainerEvents,
|
|
node => {
|
|
node.push(
|
|
getMinThumb(),
|
|
getMaxThumb()
|
|
);
|
|
}
|
|
);
|
|
|
|
return vue.h('div', {
|
|
ref: rootRef,
|
|
class: 'q-range ' + state.classes.value + (
|
|
props.modelValue.min === null || props.modelValue.max === null
|
|
? ' q-slider--no-value'
|
|
: ''
|
|
),
|
|
...state.attributes.value,
|
|
'aria-valuenow': props.modelValue.min + '|' + props.modelValue.max
|
|
}, content)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QRating = createComponent({
|
|
name: 'QRating',
|
|
|
|
props: {
|
|
...useSizeProps,
|
|
...useFormProps,
|
|
|
|
modelValue: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
|
|
max: {
|
|
type: [ String, Number ],
|
|
default: 5
|
|
},
|
|
|
|
icon: [ String, Array ],
|
|
iconHalf: [ String, Array ],
|
|
iconSelected: [ String, Array ],
|
|
|
|
iconAriaLabel: [ String, Array ],
|
|
|
|
color: [ String, Array ],
|
|
colorHalf: [ String, Array ],
|
|
colorSelected: [ String, Array ],
|
|
|
|
noReset: Boolean,
|
|
noDimming: Boolean,
|
|
|
|
readonly: Boolean,
|
|
disable: Boolean
|
|
},
|
|
|
|
emits: [ 'update:modelValue' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const sizeStyle = useSize(props);
|
|
const formAttrs = useFormAttrs(props);
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
const mouseModel = vue.ref(0);
|
|
|
|
let iconRefs = {};
|
|
|
|
const editable = vue.computed(() =>
|
|
props.readonly !== true && props.disable !== true
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-rating row inline items-center'
|
|
+ ` q-rating--${ editable.value === true ? '' : 'non-' }editable`
|
|
+ (props.noDimming === true ? ' q-rating--no-dimming' : '')
|
|
+ (props.disable === true ? ' disabled' : '')
|
|
+ (
|
|
props.color !== void 0 && Array.isArray(props.color) === false
|
|
? ` text-${ props.color }`
|
|
: ''
|
|
)
|
|
);
|
|
|
|
const iconData = vue.computed(() => {
|
|
const
|
|
iconLen = Array.isArray(props.icon) === true ? props.icon.length : 0,
|
|
selIconLen = Array.isArray(props.iconSelected) === true ? props.iconSelected.length : 0,
|
|
halfIconLen = Array.isArray(props.iconHalf) === true ? props.iconHalf.length : 0,
|
|
colorLen = Array.isArray(props.color) === true ? props.color.length : 0,
|
|
selColorLen = Array.isArray(props.colorSelected) === true ? props.colorSelected.length : 0,
|
|
halfColorLen = Array.isArray(props.colorHalf) === true ? props.colorHalf.length : 0;
|
|
|
|
return {
|
|
iconLen,
|
|
icon: iconLen > 0 ? props.icon[ iconLen - 1 ] : props.icon,
|
|
selIconLen,
|
|
selIcon: selIconLen > 0 ? props.iconSelected[ selIconLen - 1 ] : props.iconSelected,
|
|
halfIconLen,
|
|
halfIcon: halfIconLen > 0 ? props.iconHalf[ selIconLen - 1 ] : props.iconHalf,
|
|
colorLen,
|
|
color: colorLen > 0 ? props.color[ colorLen - 1 ] : props.color,
|
|
selColorLen,
|
|
selColor: selColorLen > 0 ? props.colorSelected[ selColorLen - 1 ] : props.colorSelected,
|
|
halfColorLen,
|
|
halfColor: halfColorLen > 0 ? props.colorHalf[ halfColorLen - 1 ] : props.colorHalf
|
|
}
|
|
});
|
|
|
|
const iconLabel = vue.computed(() => {
|
|
if (typeof props.iconAriaLabel === 'string') {
|
|
const label = props.iconAriaLabel.length !== 0 ? `${ props.iconAriaLabel } ` : '';
|
|
return i => `${ label }${ i }`
|
|
}
|
|
|
|
if (Array.isArray(props.iconAriaLabel) === true) {
|
|
const iMax = props.iconAriaLabel.length;
|
|
|
|
if (iMax > 0) {
|
|
return i => props.iconAriaLabel[ Math.min(i, iMax) - 1 ]
|
|
}
|
|
}
|
|
|
|
return (i, label) => `${ label } ${ i }`
|
|
});
|
|
|
|
const stars = vue.computed(() => {
|
|
const
|
|
acc = [],
|
|
icons = iconData.value,
|
|
ceil = Math.ceil(props.modelValue),
|
|
tabindex = editable.value === true ? 0 : null;
|
|
|
|
const halfIndex = props.iconHalf === void 0 || ceil === props.modelValue
|
|
? -1
|
|
: ceil;
|
|
|
|
for (let i = 1; i <= props.max; i++) {
|
|
const
|
|
active = (mouseModel.value === 0 && props.modelValue >= i) || (mouseModel.value > 0 && mouseModel.value >= i),
|
|
half = halfIndex === i && mouseModel.value < i,
|
|
exSelected = mouseModel.value > 0 && (half === true ? ceil : props.modelValue) >= i && mouseModel.value < i,
|
|
color = half === true
|
|
? (i <= icons.halfColorLen ? props.colorHalf[ i - 1 ] : icons.halfColor)
|
|
: (
|
|
icons.selColor !== void 0 && active === true
|
|
? (i <= icons.selColorLen ? props.colorSelected[ i - 1 ] : icons.selColor)
|
|
: (i <= icons.colorLen ? props.color[ i - 1 ] : icons.color)
|
|
),
|
|
name = (
|
|
half === true
|
|
? (i <= icons.halfIconLen ? props.iconHalf[ i - 1 ] : icons.halfIcon)
|
|
: (
|
|
icons.selIcon !== void 0 && (active === true || exSelected === true)
|
|
? (i <= icons.selIconLen ? props.iconSelected[ i - 1 ] : icons.selIcon)
|
|
: (i <= icons.iconLen ? props.icon[ i - 1 ] : icons.icon)
|
|
)
|
|
) || $q.iconSet.rating.icon;
|
|
|
|
acc.push({
|
|
name: (
|
|
half === true
|
|
? (i <= icons.halfIconLen ? props.iconHalf[ i - 1 ] : icons.halfIcon)
|
|
: (
|
|
icons.selIcon !== void 0 && (active === true || exSelected === true)
|
|
? (i <= icons.selIconLen ? props.iconSelected[ i - 1 ] : icons.selIcon)
|
|
: (i <= icons.iconLen ? props.icon[ i - 1 ] : icons.icon)
|
|
)
|
|
) || $q.iconSet.rating.icon,
|
|
|
|
attrs: {
|
|
tabindex,
|
|
role: 'radio',
|
|
'aria-checked': props.modelValue === i ? 'true' : 'false',
|
|
'aria-label': iconLabel.value(i, name)
|
|
},
|
|
|
|
iconClass: 'q-rating__icon'
|
|
+ (active === true || half === true ? ' q-rating__icon--active' : '')
|
|
+ (exSelected === true ? ' q-rating__icon--exselected' : '')
|
|
+ (mouseModel.value === i ? ' q-rating__icon--hovered' : '')
|
|
+ (color !== void 0 ? ` text-${ color }` : '')
|
|
});
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
const attributes = vue.computed(() => {
|
|
const attrs = { role: 'radiogroup' };
|
|
|
|
if (props.disable === true) {
|
|
attrs[ 'aria-disabled' ] = 'true';
|
|
}
|
|
if (props.readonly === true) {
|
|
attrs[ 'aria-readonly' ] = 'true';
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
function set (value) {
|
|
if (editable.value === true) {
|
|
const
|
|
model = between(parseInt(value, 10), 1, parseInt(props.max, 10)),
|
|
newVal = props.noReset !== true && props.modelValue === model ? 0 : model;
|
|
|
|
newVal !== props.modelValue && emit('update:modelValue', newVal);
|
|
mouseModel.value = 0;
|
|
}
|
|
}
|
|
|
|
function setHoverValue (value) {
|
|
if (editable.value === true) {
|
|
mouseModel.value = value;
|
|
}
|
|
}
|
|
|
|
function onKeyup (e, i) {
|
|
switch (e.keyCode) {
|
|
case 13:
|
|
case 32:
|
|
set(i);
|
|
return stopAndPrevent(e)
|
|
case 37: // LEFT ARROW
|
|
case 40: // DOWN ARROW
|
|
if (iconRefs[ `rt${ i - 1 }` ]) {
|
|
iconRefs[ `rt${ i - 1 }` ].focus();
|
|
}
|
|
return stopAndPrevent(e)
|
|
case 39: // RIGHT ARROW
|
|
case 38: // UP ARROW
|
|
if (iconRefs[ `rt${ i + 1 }` ]) {
|
|
iconRefs[ `rt${ i + 1 }` ].focus();
|
|
}
|
|
return stopAndPrevent(e)
|
|
}
|
|
}
|
|
|
|
function resetMouseModel () {
|
|
mouseModel.value = 0;
|
|
}
|
|
|
|
vue.onBeforeUpdate(() => {
|
|
iconRefs = {};
|
|
});
|
|
|
|
return () => {
|
|
const child = [];
|
|
|
|
stars.value.forEach(({ iconClass, name, attrs }, index) => {
|
|
const i = index + 1;
|
|
|
|
child.push(
|
|
vue.h('div', {
|
|
key: i,
|
|
ref: el => { iconRefs[ `rt${ i }` ] = el; },
|
|
class: 'q-rating__icon-container flex flex-center',
|
|
...attrs,
|
|
onClick () { set(i); },
|
|
onMouseover () { setHoverValue(i); },
|
|
onMouseout: resetMouseModel,
|
|
onFocus () { setHoverValue(i); },
|
|
onBlur: resetMouseModel,
|
|
onKeyup (e) { onKeyup(e, i); }
|
|
}, hMergeSlot(
|
|
slots[ `tip-${ i }` ],
|
|
[ vue.h(QIcon, { class: iconClass, name }) ]
|
|
))
|
|
);
|
|
});
|
|
|
|
if (props.name !== void 0 && props.disable !== true) {
|
|
injectFormInput(child, 'push');
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
style: sizeStyle.value,
|
|
...attributes.value
|
|
}, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QResponsive = createComponent({
|
|
name: 'QResponsive',
|
|
|
|
props: useRatioProps,
|
|
|
|
setup (props, { slots }) {
|
|
const ratioStyle = useRatio(props);
|
|
|
|
return () => vue.h('div', {
|
|
class: 'q-responsive'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-responsive__filler overflow-hidden'
|
|
}, [
|
|
vue.h('div', { style: ratioStyle.value })
|
|
]),
|
|
|
|
vue.h('div', {
|
|
class: 'q-responsive__content absolute-full fit'
|
|
}, hSlot(slots.default))
|
|
])
|
|
}
|
|
});
|
|
|
|
const axisList = [ 'vertical', 'horizontal' ];
|
|
const dirProps = {
|
|
vertical: { offset: 'offsetY', scroll: 'scrollTop', dir: 'down', dist: 'y' },
|
|
horizontal: { offset: 'offsetX', scroll: 'scrollLeft', dir: 'right', dist: 'x' }
|
|
};
|
|
const panOpts = {
|
|
prevent: true,
|
|
mouse: true,
|
|
mouseAllDir: true
|
|
};
|
|
|
|
const getMinThumbSize = size => (size >= 250 ? 50 : Math.ceil(size / 5));
|
|
|
|
var QScrollArea = createComponent({
|
|
name: 'QScrollArea',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
thumbStyle: Object,
|
|
verticalThumbStyle: Object,
|
|
horizontalThumbStyle: Object,
|
|
|
|
barStyle: [ Array, String, Object ],
|
|
verticalBarStyle: [ Array, String, Object ],
|
|
horizontalBarStyle: [ Array, String, Object ],
|
|
|
|
contentStyle: [ Array, String, Object ],
|
|
contentActiveStyle: [ Array, String, Object ],
|
|
|
|
delay: {
|
|
type: [ String, Number ],
|
|
default: 1000
|
|
},
|
|
|
|
visible: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
tabindex: [ String, Number ],
|
|
|
|
onScroll: Function
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
// state management
|
|
const tempShowing = vue.ref(false);
|
|
const panning = vue.ref(false);
|
|
const hover = vue.ref(false);
|
|
|
|
// other...
|
|
const container = {
|
|
vertical: vue.ref(0),
|
|
horizontal: vue.ref(0)
|
|
};
|
|
|
|
const scroll = {
|
|
vertical: {
|
|
ref: vue.ref(null),
|
|
position: vue.ref(0),
|
|
size: vue.ref(0)
|
|
},
|
|
|
|
horizontal: {
|
|
ref: vue.ref(null),
|
|
position: vue.ref(0),
|
|
size: vue.ref(0)
|
|
}
|
|
};
|
|
|
|
const { proxy } = vue.getCurrentInstance();
|
|
|
|
const isDark = useDark(props, proxy.$q);
|
|
|
|
let timer = null, panRefPos;
|
|
|
|
const targetRef = vue.ref(null);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-scrollarea'
|
|
+ (isDark.value === true ? ' q-scrollarea--dark' : '')
|
|
);
|
|
|
|
scroll.vertical.percentage = vue.computed(() => {
|
|
const diff = scroll.vertical.size.value - container.vertical.value;
|
|
if (diff <= 0) { return 0 }
|
|
const p = between(scroll.vertical.position.value / diff, 0, 1);
|
|
return Math.round(p * 10000) / 10000
|
|
});
|
|
scroll.vertical.thumbHidden = vue.computed(() =>
|
|
(
|
|
(props.visible === null ? hover.value : props.visible) !== true
|
|
&& tempShowing.value === false
|
|
&& panning.value === false
|
|
) || scroll.vertical.size.value <= container.vertical.value + 1
|
|
);
|
|
scroll.vertical.thumbStart = vue.computed(() =>
|
|
scroll.vertical.percentage.value * (container.vertical.value - scroll.vertical.thumbSize.value)
|
|
);
|
|
scroll.vertical.thumbSize = vue.computed(() =>
|
|
Math.round(
|
|
between(
|
|
container.vertical.value * container.vertical.value / scroll.vertical.size.value,
|
|
getMinThumbSize(container.vertical.value),
|
|
container.vertical.value
|
|
)
|
|
)
|
|
);
|
|
scroll.vertical.style = vue.computed(() => {
|
|
return {
|
|
...props.thumbStyle,
|
|
...props.verticalThumbStyle,
|
|
top: `${ scroll.vertical.thumbStart.value }px`,
|
|
height: `${ scroll.vertical.thumbSize.value }px`
|
|
}
|
|
});
|
|
scroll.vertical.thumbClass = vue.computed(() =>
|
|
'q-scrollarea__thumb q-scrollarea__thumb--v absolute-right'
|
|
+ (scroll.vertical.thumbHidden.value === true ? ' q-scrollarea__thumb--invisible' : '')
|
|
);
|
|
scroll.vertical.barClass = vue.computed(() =>
|
|
'q-scrollarea__bar q-scrollarea__bar--v absolute-right'
|
|
+ (scroll.vertical.thumbHidden.value === true ? ' q-scrollarea__bar--invisible' : '')
|
|
);
|
|
|
|
scroll.horizontal.percentage = vue.computed(() => {
|
|
const diff = scroll.horizontal.size.value - container.horizontal.value;
|
|
if (diff <= 0) { return 0 }
|
|
const p = between(Math.abs(scroll.horizontal.position.value) / diff, 0, 1);
|
|
return Math.round(p * 10000) / 10000
|
|
});
|
|
scroll.horizontal.thumbHidden = vue.computed(() =>
|
|
(
|
|
(props.visible === null ? hover.value : props.visible) !== true
|
|
&& tempShowing.value === false
|
|
&& panning.value === false
|
|
) || scroll.horizontal.size.value <= container.horizontal.value + 1
|
|
);
|
|
scroll.horizontal.thumbStart = vue.computed(() =>
|
|
scroll.horizontal.percentage.value * (container.horizontal.value - scroll.horizontal.thumbSize.value)
|
|
);
|
|
scroll.horizontal.thumbSize = vue.computed(() =>
|
|
Math.round(
|
|
between(
|
|
container.horizontal.value * container.horizontal.value / scroll.horizontal.size.value,
|
|
getMinThumbSize(container.horizontal.value),
|
|
container.horizontal.value
|
|
)
|
|
)
|
|
);
|
|
scroll.horizontal.style = vue.computed(() => {
|
|
return {
|
|
...props.thumbStyle,
|
|
...props.horizontalThumbStyle,
|
|
[ proxy.$q.lang.rtl === true ? 'right' : 'left' ]: `${ scroll.horizontal.thumbStart.value }px`,
|
|
width: `${ scroll.horizontal.thumbSize.value }px`
|
|
}
|
|
});
|
|
scroll.horizontal.thumbClass = vue.computed(() =>
|
|
'q-scrollarea__thumb q-scrollarea__thumb--h absolute-bottom'
|
|
+ (scroll.horizontal.thumbHidden.value === true ? ' q-scrollarea__thumb--invisible' : '')
|
|
);
|
|
scroll.horizontal.barClass = vue.computed(() =>
|
|
'q-scrollarea__bar q-scrollarea__bar--h absolute-bottom'
|
|
+ (scroll.horizontal.thumbHidden.value === true ? ' q-scrollarea__bar--invisible' : '')
|
|
);
|
|
|
|
const mainStyle = vue.computed(() => (
|
|
scroll.vertical.thumbHidden.value === true && scroll.horizontal.thumbHidden.value === true
|
|
? props.contentStyle
|
|
: props.contentActiveStyle
|
|
));
|
|
|
|
const thumbVertDir = [ [
|
|
TouchPan,
|
|
e => { onPanThumb(e, 'vertical'); },
|
|
void 0,
|
|
{ vertical: true, ...panOpts }
|
|
] ];
|
|
|
|
const thumbHorizDir = [ [
|
|
TouchPan,
|
|
e => { onPanThumb(e, 'horizontal'); },
|
|
void 0,
|
|
{ horizontal: true, ...panOpts }
|
|
] ];
|
|
|
|
function getScroll () {
|
|
const info = {};
|
|
|
|
axisList.forEach(axis => {
|
|
const data = scroll[ axis ];
|
|
|
|
info[ axis + 'Position' ] = data.position.value;
|
|
info[ axis + 'Percentage' ] = data.percentage.value;
|
|
info[ axis + 'Size' ] = data.size.value;
|
|
info[ axis + 'ContainerSize' ] = container[ axis ].value;
|
|
});
|
|
|
|
return info
|
|
}
|
|
|
|
// we have lots of listeners, so
|
|
// ensure we're not emitting same info
|
|
// multiple times
|
|
const emitScroll = debounce(() => {
|
|
const info = getScroll();
|
|
info.ref = proxy;
|
|
emit('scroll', info);
|
|
}, 0);
|
|
|
|
function localSetScrollPosition (axis, offset, duration) {
|
|
if (axisList.includes(axis) === false) {
|
|
console.error('[QScrollArea]: wrong first param of setScrollPosition (vertical/horizontal)');
|
|
return
|
|
}
|
|
|
|
const fn = axis === 'vertical'
|
|
? setVerticalScrollPosition
|
|
: setHorizontalScrollPosition;
|
|
|
|
fn(targetRef.value, offset, duration);
|
|
}
|
|
|
|
function updateContainer ({ height, width }) {
|
|
let change = false;
|
|
|
|
if (container.vertical.value !== height) {
|
|
container.vertical.value = height;
|
|
change = true;
|
|
}
|
|
|
|
if (container.horizontal.value !== width) {
|
|
container.horizontal.value = width;
|
|
change = true;
|
|
}
|
|
|
|
change === true && startTimer();
|
|
}
|
|
|
|
function updateScroll ({ position }) {
|
|
let change = false;
|
|
|
|
if (scroll.vertical.position.value !== position.top) {
|
|
scroll.vertical.position.value = position.top;
|
|
change = true;
|
|
}
|
|
|
|
if (scroll.horizontal.position.value !== position.left) {
|
|
scroll.horizontal.position.value = position.left;
|
|
change = true;
|
|
}
|
|
|
|
change === true && startTimer();
|
|
}
|
|
|
|
function updateScrollSize ({ height, width }) {
|
|
if (scroll.horizontal.size.value !== width) {
|
|
scroll.horizontal.size.value = width;
|
|
startTimer();
|
|
}
|
|
|
|
if (scroll.vertical.size.value !== height) {
|
|
scroll.vertical.size.value = height;
|
|
startTimer();
|
|
}
|
|
}
|
|
|
|
function onPanThumb (e, axis) {
|
|
const data = scroll[ axis ];
|
|
|
|
if (e.isFirst === true) {
|
|
if (data.thumbHidden.value === true) {
|
|
return
|
|
}
|
|
|
|
panRefPos = data.position.value;
|
|
panning.value = true;
|
|
}
|
|
else if (panning.value !== true) {
|
|
return
|
|
}
|
|
|
|
if (e.isFinal === true) {
|
|
panning.value = false;
|
|
}
|
|
|
|
const dProp = dirProps[ axis ];
|
|
const containerSize = container[ axis ].value;
|
|
|
|
const multiplier = (data.size.value - containerSize) / (containerSize - data.thumbSize.value);
|
|
const distance = e.distance[ dProp.dist ];
|
|
const pos = panRefPos + (e.direction === dProp.dir ? 1 : -1) * distance * multiplier;
|
|
|
|
setScroll(pos, axis);
|
|
}
|
|
|
|
function onMousedown (evt, axis) {
|
|
const data = scroll[ axis ];
|
|
|
|
if (data.thumbHidden.value !== true) {
|
|
const offset = evt[ dirProps[ axis ].offset ];
|
|
if (offset < data.thumbStart.value || offset > data.thumbStart.value + data.thumbSize.value) {
|
|
const pos = offset - data.thumbSize.value / 2;
|
|
setScroll(pos / container[ axis ].value * data.size.value, axis);
|
|
}
|
|
|
|
// activate thumb pan
|
|
if (data.ref.value !== null) {
|
|
data.ref.value.dispatchEvent(new MouseEvent(evt.type, evt));
|
|
}
|
|
}
|
|
}
|
|
|
|
function onVerticalMousedown (evt) {
|
|
onMousedown(evt, 'vertical');
|
|
}
|
|
|
|
function onHorizontalMousedown (evt) {
|
|
onMousedown(evt, 'horizontal');
|
|
}
|
|
|
|
function startTimer () {
|
|
tempShowing.value = true;
|
|
|
|
timer !== null && clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
tempShowing.value = false;
|
|
}, props.delay);
|
|
|
|
props.onScroll !== void 0 && emitScroll();
|
|
}
|
|
|
|
function setScroll (offset, axis) {
|
|
targetRef.value[ dirProps[ axis ].scroll ] = offset;
|
|
}
|
|
|
|
function onMouseenter () {
|
|
hover.value = true;
|
|
}
|
|
|
|
function onMouseleave () {
|
|
hover.value = false;
|
|
}
|
|
|
|
let scrollPosition = null;
|
|
|
|
vue.watch(() => proxy.$q.lang.rtl, rtl => {
|
|
if (targetRef.value !== null) {
|
|
setHorizontalScrollPosition(
|
|
targetRef.value,
|
|
Math.abs(scroll.horizontal.position.value) * (rtl === true ? -1 : 1)
|
|
);
|
|
}
|
|
});
|
|
|
|
vue.onDeactivated(() => {
|
|
scrollPosition = {
|
|
top: scroll.vertical.position.value,
|
|
left: scroll.horizontal.position.value
|
|
};
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
if (scrollPosition === null) { return }
|
|
|
|
const scrollTarget = targetRef.value;
|
|
|
|
if (scrollTarget !== null) {
|
|
setHorizontalScrollPosition(scrollTarget, scrollPosition.left);
|
|
setVerticalScrollPosition(scrollTarget, scrollPosition.top);
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(emitScroll.cancel);
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
getScrollTarget: () => targetRef.value,
|
|
getScroll,
|
|
getScrollPosition: () => ({
|
|
top: scroll.vertical.position.value,
|
|
left: scroll.horizontal.position.value
|
|
}),
|
|
getScrollPercentage: () => ({
|
|
top: scroll.vertical.percentage.value,
|
|
left: scroll.horizontal.percentage.value
|
|
}),
|
|
setScrollPosition: localSetScrollPosition,
|
|
setScrollPercentage (axis, percentage, duration) {
|
|
localSetScrollPosition(
|
|
axis,
|
|
percentage
|
|
* (scroll[ axis ].size.value - container[ axis ].value)
|
|
* (axis === 'horizontal' && proxy.$q.lang.rtl === true ? -1 : 1),
|
|
duration
|
|
);
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
onMouseenter,
|
|
onMouseleave
|
|
}, [
|
|
vue.h('div', {
|
|
ref: targetRef,
|
|
class: 'q-scrollarea__container scroll relative-position fit hide-scrollbar',
|
|
tabindex: props.tabindex !== void 0 ? props.tabindex : void 0
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-scrollarea__content absolute',
|
|
style: mainStyle.value
|
|
}, hMergeSlot(slots.default, [
|
|
vue.h(QResizeObserver, {
|
|
debounce: 0,
|
|
onResize: updateScrollSize
|
|
})
|
|
])),
|
|
|
|
vue.h(QScrollObserver, {
|
|
axis: 'both',
|
|
onScroll: updateScroll
|
|
})
|
|
]),
|
|
|
|
vue.h(QResizeObserver, {
|
|
debounce: 0,
|
|
onResize: updateContainer
|
|
}),
|
|
|
|
vue.h('div', {
|
|
class: scroll.vertical.barClass.value,
|
|
style: [ props.barStyle, props.verticalBarStyle ],
|
|
'aria-hidden': 'true',
|
|
onMousedown: onVerticalMousedown
|
|
}),
|
|
|
|
vue.h('div', {
|
|
class: scroll.horizontal.barClass.value,
|
|
style: [ props.barStyle, props.horizontalBarStyle ],
|
|
'aria-hidden': 'true',
|
|
onMousedown: onHorizontalMousedown
|
|
}),
|
|
|
|
vue.withDirectives(
|
|
vue.h('div', {
|
|
ref: scroll.vertical.ref,
|
|
class: scroll.vertical.thumbClass.value,
|
|
style: scroll.vertical.style.value,
|
|
'aria-hidden': 'true'
|
|
}),
|
|
thumbVertDir
|
|
),
|
|
|
|
vue.withDirectives(
|
|
vue.h('div', {
|
|
ref: scroll.horizontal.ref,
|
|
class: scroll.horizontal.thumbClass.value,
|
|
style: scroll.horizontal.style.value,
|
|
'aria-hidden': 'true'
|
|
}),
|
|
thumbHorizDir
|
|
)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
const aggBucketSize = 1000;
|
|
|
|
const scrollToEdges = [
|
|
'start',
|
|
'center',
|
|
'end',
|
|
'start-force',
|
|
'center-force',
|
|
'end-force'
|
|
];
|
|
|
|
const filterProto = Array.prototype.filter;
|
|
|
|
const setOverflowAnchor = window.getComputedStyle(document.body).overflowAnchor === void 0
|
|
? noop
|
|
: function (contentEl, index) {
|
|
if (contentEl === null) {
|
|
return
|
|
}
|
|
|
|
if (contentEl._qOverflowAnimationFrame !== void 0) {
|
|
cancelAnimationFrame(contentEl._qOverflowAnimationFrame);
|
|
}
|
|
|
|
contentEl._qOverflowAnimationFrame = requestAnimationFrame(() => {
|
|
if (contentEl === null) {
|
|
return
|
|
}
|
|
|
|
contentEl._qOverflowAnimationFrame = void 0;
|
|
const children = contentEl.children || [];
|
|
|
|
filterProto
|
|
.call(children, el => el.dataset && el.dataset.qVsAnchor !== void 0)
|
|
.forEach(el => {
|
|
delete el.dataset.qVsAnchor;
|
|
});
|
|
|
|
const el = children[ index ];
|
|
|
|
if (el && el.dataset) {
|
|
el.dataset.qVsAnchor = '';
|
|
}
|
|
});
|
|
};
|
|
|
|
function sumFn (acc, h) {
|
|
return acc + h
|
|
}
|
|
|
|
function getScrollDetails (
|
|
parent,
|
|
child,
|
|
beforeRef,
|
|
afterRef,
|
|
horizontal,
|
|
rtl,
|
|
stickyStart,
|
|
stickyEnd
|
|
) {
|
|
const
|
|
parentCalc = parent === window ? document.scrollingElement || document.documentElement : parent,
|
|
propElSize = horizontal === true ? 'offsetWidth' : 'offsetHeight',
|
|
details = {
|
|
scrollStart: 0,
|
|
scrollViewSize: -stickyStart - stickyEnd,
|
|
scrollMaxSize: 0,
|
|
offsetStart: -stickyStart,
|
|
offsetEnd: -stickyEnd
|
|
};
|
|
|
|
if (horizontal === true) {
|
|
if (parent === window) {
|
|
details.scrollStart = window.pageXOffset || window.scrollX || document.body.scrollLeft || 0;
|
|
details.scrollViewSize += document.documentElement.clientWidth;
|
|
}
|
|
else {
|
|
details.scrollStart = parentCalc.scrollLeft;
|
|
details.scrollViewSize += parentCalc.clientWidth;
|
|
}
|
|
details.scrollMaxSize = parentCalc.scrollWidth;
|
|
|
|
if (rtl === true) {
|
|
details.scrollStart = (rtlHasScrollBug === true ? details.scrollMaxSize - details.scrollViewSize : 0) - details.scrollStart;
|
|
}
|
|
}
|
|
else {
|
|
if (parent === window) {
|
|
details.scrollStart = window.pageYOffset || window.scrollY || document.body.scrollTop || 0;
|
|
details.scrollViewSize += document.documentElement.clientHeight;
|
|
}
|
|
else {
|
|
details.scrollStart = parentCalc.scrollTop;
|
|
details.scrollViewSize += parentCalc.clientHeight;
|
|
}
|
|
details.scrollMaxSize = parentCalc.scrollHeight;
|
|
}
|
|
|
|
if (beforeRef !== null) {
|
|
for (let el = beforeRef.previousElementSibling; el !== null; el = el.previousElementSibling) {
|
|
if (el.classList.contains('q-virtual-scroll--skip') === false) {
|
|
details.offsetStart += el[ propElSize ];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (afterRef !== null) {
|
|
for (let el = afterRef.nextElementSibling; el !== null; el = el.nextElementSibling) {
|
|
if (el.classList.contains('q-virtual-scroll--skip') === false) {
|
|
details.offsetEnd += el[ propElSize ];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (child !== parent) {
|
|
const
|
|
parentRect = parentCalc.getBoundingClientRect(),
|
|
childRect = child.getBoundingClientRect();
|
|
|
|
if (horizontal === true) {
|
|
details.offsetStart += childRect.left - parentRect.left;
|
|
details.offsetEnd -= childRect.width;
|
|
}
|
|
else {
|
|
details.offsetStart += childRect.top - parentRect.top;
|
|
details.offsetEnd -= childRect.height;
|
|
}
|
|
|
|
if (parent !== window) {
|
|
details.offsetStart += details.scrollStart;
|
|
}
|
|
details.offsetEnd += details.scrollMaxSize - details.offsetStart;
|
|
}
|
|
|
|
return details
|
|
}
|
|
|
|
function setScroll (parent, scroll, horizontal, rtl) {
|
|
if (scroll === 'end') {
|
|
scroll = (parent === window ? document.body : parent)[
|
|
horizontal === true ? 'scrollWidth' : 'scrollHeight'
|
|
];
|
|
}
|
|
|
|
if (parent === window) {
|
|
if (horizontal === true) {
|
|
if (rtl === true) {
|
|
scroll = (rtlHasScrollBug === true ? document.body.scrollWidth - document.documentElement.clientWidth : 0) - scroll;
|
|
}
|
|
window.scrollTo(scroll, window.pageYOffset || window.scrollY || document.body.scrollTop || 0);
|
|
}
|
|
else {
|
|
window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, scroll);
|
|
}
|
|
}
|
|
else if (horizontal === true) {
|
|
if (rtl === true) {
|
|
scroll = (rtlHasScrollBug === true ? parent.scrollWidth - parent.offsetWidth : 0) - scroll;
|
|
}
|
|
parent.scrollLeft = scroll;
|
|
}
|
|
else {
|
|
parent.scrollTop = scroll;
|
|
}
|
|
}
|
|
|
|
function sumSize (sizeAgg, size, from, to) {
|
|
if (from >= to) { return 0 }
|
|
|
|
const
|
|
lastTo = size.length,
|
|
fromAgg = Math.floor(from / aggBucketSize),
|
|
toAgg = Math.floor((to - 1) / aggBucketSize) + 1;
|
|
|
|
let total = sizeAgg.slice(fromAgg, toAgg).reduce(sumFn, 0);
|
|
|
|
if (from % aggBucketSize !== 0) {
|
|
total -= size.slice(fromAgg * aggBucketSize, from).reduce(sumFn, 0);
|
|
}
|
|
if (to % aggBucketSize !== 0 && to !== lastTo) {
|
|
total -= size.slice(to, toAgg * aggBucketSize).reduce(sumFn, 0);
|
|
}
|
|
|
|
return total
|
|
}
|
|
|
|
const commonVirtScrollProps = {
|
|
virtualScrollSliceSize: {
|
|
type: [ Number, String ],
|
|
default: null
|
|
},
|
|
|
|
virtualScrollSliceRatioBefore: {
|
|
type: [ Number, String ],
|
|
default: 1
|
|
},
|
|
|
|
virtualScrollSliceRatioAfter: {
|
|
type: [ Number, String ],
|
|
default: 1
|
|
},
|
|
|
|
virtualScrollItemSize: {
|
|
type: [ Number, String ],
|
|
default: 24
|
|
},
|
|
|
|
virtualScrollStickySizeStart: {
|
|
type: [ Number, String ],
|
|
default: 0
|
|
},
|
|
|
|
virtualScrollStickySizeEnd: {
|
|
type: [ Number, String ],
|
|
default: 0
|
|
},
|
|
|
|
tableColspan: [ Number, String ]
|
|
};
|
|
|
|
const commonVirtPropsList = Object.keys(commonVirtScrollProps);
|
|
|
|
const useVirtualScrollProps = {
|
|
virtualScrollHorizontal: Boolean,
|
|
onVirtualScroll: Function,
|
|
...commonVirtScrollProps
|
|
};
|
|
|
|
function useVirtualScroll ({
|
|
virtualScrollLength, getVirtualScrollTarget, getVirtualScrollEl,
|
|
virtualScrollItemSizeComputed // optional
|
|
}) {
|
|
const vm = vue.getCurrentInstance();
|
|
|
|
const { props, emit, proxy } = vm;
|
|
const { $q } = proxy;
|
|
|
|
let prevScrollStart, prevToIndex, localScrollViewSize, virtualScrollSizesAgg = [], virtualScrollSizes;
|
|
|
|
const virtualScrollPaddingBefore = vue.ref(0);
|
|
const virtualScrollPaddingAfter = vue.ref(0);
|
|
const virtualScrollSliceSizeComputed = vue.ref({});
|
|
|
|
const beforeRef = vue.ref(null);
|
|
const afterRef = vue.ref(null);
|
|
const contentRef = vue.ref(null);
|
|
|
|
const virtualScrollSliceRange = vue.ref({ from: 0, to: 0 });
|
|
|
|
const colspanAttr = vue.computed(() => (props.tableColspan !== void 0 ? props.tableColspan : 100));
|
|
|
|
if (virtualScrollItemSizeComputed === void 0) {
|
|
virtualScrollItemSizeComputed = vue.computed(() => props.virtualScrollItemSize);
|
|
}
|
|
|
|
const needsReset = vue.computed(() => virtualScrollItemSizeComputed.value + ';' + props.virtualScrollHorizontal);
|
|
|
|
const needsSliceRecalc = vue.computed(() =>
|
|
needsReset.value + ';' + props.virtualScrollSliceRatioBefore + ';' + props.virtualScrollSliceRatioAfter
|
|
);
|
|
|
|
vue.watch(needsSliceRecalc, () => { setVirtualScrollSize(); });
|
|
vue.watch(needsReset, reset);
|
|
|
|
function reset () {
|
|
localResetVirtualScroll(prevToIndex, true);
|
|
}
|
|
|
|
function refresh (toIndex) {
|
|
localResetVirtualScroll(toIndex === void 0 ? prevToIndex : toIndex);
|
|
}
|
|
|
|
function scrollTo (toIndex, edge) {
|
|
const scrollEl = getVirtualScrollTarget();
|
|
|
|
if (scrollEl === void 0 || scrollEl === null || scrollEl.nodeType === 8) {
|
|
return
|
|
}
|
|
|
|
const scrollDetails = getScrollDetails(
|
|
scrollEl,
|
|
getVirtualScrollEl(),
|
|
beforeRef.value,
|
|
afterRef.value,
|
|
props.virtualScrollHorizontal,
|
|
$q.lang.rtl,
|
|
props.virtualScrollStickySizeStart,
|
|
props.virtualScrollStickySizeEnd
|
|
);
|
|
|
|
localScrollViewSize !== scrollDetails.scrollViewSize && setVirtualScrollSize(scrollDetails.scrollViewSize);
|
|
|
|
setVirtualScrollSliceRange(
|
|
scrollEl,
|
|
scrollDetails,
|
|
Math.min(virtualScrollLength.value - 1, Math.max(0, parseInt(toIndex, 10) || 0)),
|
|
0,
|
|
scrollToEdges.indexOf(edge) > -1 ? edge : (prevToIndex > -1 && toIndex > prevToIndex ? 'end' : 'start')
|
|
);
|
|
}
|
|
|
|
function localOnVirtualScrollEvt () {
|
|
const scrollEl = getVirtualScrollTarget();
|
|
|
|
if (scrollEl === void 0 || scrollEl === null || scrollEl.nodeType === 8) {
|
|
return
|
|
}
|
|
|
|
const
|
|
scrollDetails = getScrollDetails(
|
|
scrollEl,
|
|
getVirtualScrollEl(),
|
|
beforeRef.value,
|
|
afterRef.value,
|
|
props.virtualScrollHorizontal,
|
|
$q.lang.rtl,
|
|
props.virtualScrollStickySizeStart,
|
|
props.virtualScrollStickySizeEnd
|
|
),
|
|
listLastIndex = virtualScrollLength.value - 1,
|
|
listEndOffset = scrollDetails.scrollMaxSize - scrollDetails.offsetStart - scrollDetails.offsetEnd - virtualScrollPaddingAfter.value;
|
|
|
|
if (prevScrollStart === scrollDetails.scrollStart) {
|
|
return
|
|
}
|
|
|
|
if (scrollDetails.scrollMaxSize <= 0) {
|
|
setVirtualScrollSliceRange(scrollEl, scrollDetails, 0, 0);
|
|
return
|
|
}
|
|
|
|
localScrollViewSize !== scrollDetails.scrollViewSize && setVirtualScrollSize(scrollDetails.scrollViewSize);
|
|
|
|
updateVirtualScrollSizes(virtualScrollSliceRange.value.from);
|
|
|
|
const scrollMaxStart = Math.floor(scrollDetails.scrollMaxSize
|
|
- Math.max(scrollDetails.scrollViewSize, scrollDetails.offsetEnd)
|
|
- Math.min(virtualScrollSizes[ listLastIndex ], scrollDetails.scrollViewSize / 2));
|
|
|
|
if (scrollMaxStart > 0 && Math.ceil(scrollDetails.scrollStart) >= scrollMaxStart) {
|
|
setVirtualScrollSliceRange(
|
|
scrollEl,
|
|
scrollDetails,
|
|
listLastIndex,
|
|
scrollDetails.scrollMaxSize - scrollDetails.offsetEnd - virtualScrollSizesAgg.reduce(sumFn, 0)
|
|
);
|
|
|
|
return
|
|
}
|
|
|
|
let
|
|
toIndex = 0,
|
|
listOffset = scrollDetails.scrollStart - scrollDetails.offsetStart,
|
|
offset = listOffset;
|
|
|
|
if (listOffset <= listEndOffset && listOffset + scrollDetails.scrollViewSize >= virtualScrollPaddingBefore.value) {
|
|
listOffset -= virtualScrollPaddingBefore.value;
|
|
toIndex = virtualScrollSliceRange.value.from;
|
|
offset = listOffset;
|
|
}
|
|
else {
|
|
for (let j = 0; listOffset >= virtualScrollSizesAgg[ j ] && toIndex < listLastIndex; j++) {
|
|
listOffset -= virtualScrollSizesAgg[ j ];
|
|
toIndex += aggBucketSize;
|
|
}
|
|
}
|
|
|
|
while (listOffset > 0 && toIndex < listLastIndex) {
|
|
listOffset -= virtualScrollSizes[ toIndex ];
|
|
if (listOffset > -scrollDetails.scrollViewSize) {
|
|
toIndex++;
|
|
offset = listOffset;
|
|
}
|
|
else {
|
|
offset = virtualScrollSizes[ toIndex ] + listOffset;
|
|
}
|
|
}
|
|
|
|
setVirtualScrollSliceRange(
|
|
scrollEl,
|
|
scrollDetails,
|
|
toIndex,
|
|
offset
|
|
);
|
|
}
|
|
|
|
function setVirtualScrollSliceRange (scrollEl, scrollDetails, toIndex, offset, align) {
|
|
const alignForce = typeof align === 'string' && align.indexOf('-force') > -1;
|
|
const alignEnd = alignForce === true ? align.replace('-force', '') : align;
|
|
const alignRange = alignEnd !== void 0 ? alignEnd : 'start';
|
|
|
|
let
|
|
from = Math.max(0, toIndex - virtualScrollSliceSizeComputed.value[ alignRange ]),
|
|
to = from + virtualScrollSliceSizeComputed.value.total;
|
|
|
|
if (to > virtualScrollLength.value) {
|
|
to = virtualScrollLength.value;
|
|
from = Math.max(0, to - virtualScrollSliceSizeComputed.value.total);
|
|
}
|
|
|
|
prevScrollStart = scrollDetails.scrollStart;
|
|
|
|
const rangeChanged = from !== virtualScrollSliceRange.value.from || to !== virtualScrollSliceRange.value.to;
|
|
|
|
if (rangeChanged === false && alignEnd === void 0) {
|
|
emitScroll(toIndex);
|
|
return
|
|
}
|
|
|
|
const { activeElement } = document;
|
|
const contentEl = contentRef.value;
|
|
if (
|
|
rangeChanged === true
|
|
&& contentEl !== null
|
|
&& contentEl !== activeElement
|
|
&& contentEl.contains(activeElement) === true
|
|
) {
|
|
contentEl.addEventListener('focusout', onBlurRefocusFn);
|
|
|
|
setTimeout(() => {
|
|
contentEl !== null && contentEl.removeEventListener('focusout', onBlurRefocusFn);
|
|
});
|
|
}
|
|
|
|
setOverflowAnchor(contentEl, toIndex - from);
|
|
|
|
const sizeBefore = alignEnd !== void 0 ? virtualScrollSizes.slice(from, toIndex).reduce(sumFn, 0) : 0;
|
|
|
|
if (rangeChanged === true) {
|
|
// vue key matching algorithm works only if
|
|
// the array of VNodes changes on only one of the ends
|
|
// so we first change one end and then the other
|
|
|
|
const tempTo = to >= virtualScrollSliceRange.value.from && from <= virtualScrollSliceRange.value.to
|
|
? virtualScrollSliceRange.value.to
|
|
: to;
|
|
|
|
virtualScrollSliceRange.value = { from, to: tempTo };
|
|
virtualScrollPaddingBefore.value = sumSize(virtualScrollSizesAgg, virtualScrollSizes, 0, from);
|
|
virtualScrollPaddingAfter.value = sumSize(virtualScrollSizesAgg, virtualScrollSizes, to, virtualScrollLength.value);
|
|
|
|
requestAnimationFrame(() => {
|
|
if (virtualScrollSliceRange.value.to !== to && prevScrollStart === scrollDetails.scrollStart) {
|
|
virtualScrollSliceRange.value = { from: virtualScrollSliceRange.value.from, to };
|
|
virtualScrollPaddingAfter.value = sumSize(virtualScrollSizesAgg, virtualScrollSizes, to, virtualScrollLength.value);
|
|
}
|
|
});
|
|
}
|
|
|
|
requestAnimationFrame(() => {
|
|
// if the scroll was changed give up
|
|
// (another call to setVirtualScrollSliceRange before animation frame)
|
|
if (prevScrollStart !== scrollDetails.scrollStart) {
|
|
return
|
|
}
|
|
|
|
if (rangeChanged === true) {
|
|
updateVirtualScrollSizes(from);
|
|
}
|
|
|
|
const
|
|
sizeAfter = virtualScrollSizes.slice(from, toIndex).reduce(sumFn, 0),
|
|
posStart = sizeAfter + scrollDetails.offsetStart + virtualScrollPaddingBefore.value,
|
|
posEnd = posStart + virtualScrollSizes[ toIndex ];
|
|
|
|
let scrollPosition = posStart + offset;
|
|
|
|
if (alignEnd !== void 0) {
|
|
const sizeDiff = sizeAfter - sizeBefore;
|
|
const scrollStart = scrollDetails.scrollStart + sizeDiff;
|
|
|
|
scrollPosition = alignForce !== true && scrollStart < posStart && posEnd < scrollStart + scrollDetails.scrollViewSize
|
|
? scrollStart
|
|
: (
|
|
alignEnd === 'end'
|
|
? posEnd - scrollDetails.scrollViewSize
|
|
: posStart - (alignEnd === 'start' ? 0 : Math.round((scrollDetails.scrollViewSize - virtualScrollSizes[ toIndex ]) / 2))
|
|
);
|
|
}
|
|
|
|
prevScrollStart = scrollPosition;
|
|
|
|
setScroll(
|
|
scrollEl,
|
|
scrollPosition,
|
|
props.virtualScrollHorizontal,
|
|
$q.lang.rtl
|
|
);
|
|
|
|
emitScroll(toIndex);
|
|
});
|
|
}
|
|
|
|
function updateVirtualScrollSizes (from) {
|
|
const contentEl = contentRef.value;
|
|
|
|
if (contentEl) {
|
|
const
|
|
children = filterProto.call(
|
|
contentEl.children,
|
|
el => el.classList && el.classList.contains('q-virtual-scroll--skip') === false
|
|
),
|
|
childrenLength = children.length,
|
|
sizeFn = props.virtualScrollHorizontal === true
|
|
? el => el.getBoundingClientRect().width
|
|
: el => el.offsetHeight;
|
|
|
|
let
|
|
index = from,
|
|
size, diff;
|
|
|
|
for (let i = 0; i < childrenLength;) {
|
|
size = sizeFn(children[ i ]);
|
|
i++;
|
|
|
|
while (i < childrenLength && children[ i ].classList.contains('q-virtual-scroll--with-prev') === true) {
|
|
size += sizeFn(children[ i ]);
|
|
i++;
|
|
}
|
|
|
|
diff = size - virtualScrollSizes[ index ];
|
|
|
|
if (diff !== 0) {
|
|
virtualScrollSizes[ index ] += diff;
|
|
virtualScrollSizesAgg[ Math.floor(index / aggBucketSize) ] += diff;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function onBlurRefocusFn () {
|
|
contentRef.value !== null && contentRef.value !== void 0 && contentRef.value.focus();
|
|
}
|
|
|
|
function localResetVirtualScroll (toIndex, fullReset) {
|
|
const defaultSize = 1 * virtualScrollItemSizeComputed.value;
|
|
|
|
if (fullReset === true || Array.isArray(virtualScrollSizes) === false) {
|
|
virtualScrollSizes = [];
|
|
}
|
|
|
|
const oldVirtualScrollSizesLength = virtualScrollSizes.length;
|
|
|
|
virtualScrollSizes.length = virtualScrollLength.value;
|
|
|
|
for (let i = virtualScrollLength.value - 1; i >= oldVirtualScrollSizesLength; i--) {
|
|
virtualScrollSizes[ i ] = defaultSize;
|
|
}
|
|
|
|
const jMax = Math.floor((virtualScrollLength.value - 1) / aggBucketSize);
|
|
virtualScrollSizesAgg = [];
|
|
for (let j = 0; j <= jMax; j++) {
|
|
let size = 0;
|
|
const iMax = Math.min((j + 1) * aggBucketSize, virtualScrollLength.value);
|
|
for (let i = j * aggBucketSize; i < iMax; i++) {
|
|
size += virtualScrollSizes[ i ];
|
|
}
|
|
virtualScrollSizesAgg.push(size);
|
|
}
|
|
|
|
prevToIndex = -1;
|
|
prevScrollStart = void 0;
|
|
|
|
virtualScrollPaddingBefore.value = sumSize(virtualScrollSizesAgg, virtualScrollSizes, 0, virtualScrollSliceRange.value.from);
|
|
virtualScrollPaddingAfter.value = sumSize(virtualScrollSizesAgg, virtualScrollSizes, virtualScrollSliceRange.value.to, virtualScrollLength.value);
|
|
|
|
if (toIndex >= 0) {
|
|
updateVirtualScrollSizes(virtualScrollSliceRange.value.from);
|
|
vue.nextTick(() => { scrollTo(toIndex); });
|
|
}
|
|
else {
|
|
onVirtualScrollEvt();
|
|
}
|
|
}
|
|
|
|
function setVirtualScrollSize (scrollViewSize) {
|
|
if (scrollViewSize === void 0 && typeof window !== 'undefined') {
|
|
const scrollEl = getVirtualScrollTarget();
|
|
|
|
if (scrollEl !== void 0 && scrollEl !== null && scrollEl.nodeType !== 8) {
|
|
scrollViewSize = getScrollDetails(
|
|
scrollEl,
|
|
getVirtualScrollEl(),
|
|
beforeRef.value,
|
|
afterRef.value,
|
|
props.virtualScrollHorizontal,
|
|
$q.lang.rtl,
|
|
props.virtualScrollStickySizeStart,
|
|
props.virtualScrollStickySizeEnd
|
|
).scrollViewSize;
|
|
}
|
|
}
|
|
|
|
localScrollViewSize = scrollViewSize;
|
|
|
|
const virtualScrollSliceRatioBefore = parseFloat(props.virtualScrollSliceRatioBefore) || 0;
|
|
const virtualScrollSliceRatioAfter = parseFloat(props.virtualScrollSliceRatioAfter) || 0;
|
|
const multiplier = 1 + virtualScrollSliceRatioBefore + virtualScrollSliceRatioAfter;
|
|
const view = scrollViewSize === void 0 || scrollViewSize <= 0
|
|
? 1
|
|
: Math.ceil(scrollViewSize / virtualScrollItemSizeComputed.value);
|
|
|
|
const baseSize = Math.max(
|
|
1,
|
|
view,
|
|
Math.ceil((props.virtualScrollSliceSize > 0 ? props.virtualScrollSliceSize : 10) / multiplier)
|
|
);
|
|
|
|
virtualScrollSliceSizeComputed.value = {
|
|
total: Math.ceil(baseSize * multiplier),
|
|
start: Math.ceil(baseSize * virtualScrollSliceRatioBefore),
|
|
center: Math.ceil(baseSize * (0.5 + virtualScrollSliceRatioBefore)),
|
|
end: Math.ceil(baseSize * (1 + virtualScrollSliceRatioBefore)),
|
|
view
|
|
};
|
|
}
|
|
|
|
function padVirtualScroll (tag, content) {
|
|
const paddingSize = props.virtualScrollHorizontal === true ? 'width' : 'height';
|
|
const style = {
|
|
[ '--q-virtual-scroll-item-' + paddingSize ]: virtualScrollItemSizeComputed.value + 'px'
|
|
};
|
|
|
|
return [
|
|
tag === 'tbody'
|
|
? vue.h(tag, {
|
|
class: 'q-virtual-scroll__padding',
|
|
key: 'before',
|
|
ref: beforeRef
|
|
}, [
|
|
vue.h('tr', [
|
|
vue.h('td', {
|
|
style: { [ paddingSize ]: `${ virtualScrollPaddingBefore.value }px`, ...style },
|
|
colspan: colspanAttr.value
|
|
})
|
|
])
|
|
])
|
|
: vue.h(tag, {
|
|
class: 'q-virtual-scroll__padding',
|
|
key: 'before',
|
|
ref: beforeRef,
|
|
style: { [ paddingSize ]: `${ virtualScrollPaddingBefore.value }px`, ...style }
|
|
}),
|
|
|
|
vue.h(tag, {
|
|
class: 'q-virtual-scroll__content',
|
|
key: 'content',
|
|
ref: contentRef,
|
|
tabindex: -1
|
|
}, content.flat()),
|
|
|
|
tag === 'tbody'
|
|
? vue.h(tag, {
|
|
class: 'q-virtual-scroll__padding',
|
|
key: 'after',
|
|
ref: afterRef
|
|
}, [
|
|
vue.h('tr', [
|
|
vue.h('td', {
|
|
style: { [ paddingSize ]: `${ virtualScrollPaddingAfter.value }px`, ...style },
|
|
colspan: colspanAttr.value
|
|
})
|
|
])
|
|
])
|
|
: vue.h(tag, {
|
|
class: 'q-virtual-scroll__padding',
|
|
key: 'after',
|
|
ref: afterRef,
|
|
style: { [ paddingSize ]: `${ virtualScrollPaddingAfter.value }px`, ...style }
|
|
})
|
|
]
|
|
}
|
|
|
|
function emitScroll (index) {
|
|
if (prevToIndex !== index) {
|
|
props.onVirtualScroll !== void 0 && emit('virtualScroll', {
|
|
index,
|
|
from: virtualScrollSliceRange.value.from,
|
|
to: virtualScrollSliceRange.value.to - 1,
|
|
direction: index < prevToIndex ? 'decrease' : 'increase',
|
|
ref: proxy
|
|
});
|
|
|
|
prevToIndex = index;
|
|
}
|
|
}
|
|
|
|
setVirtualScrollSize();
|
|
const onVirtualScrollEvt = debounce(
|
|
localOnVirtualScrollEvt,
|
|
$q.platform.is.ios === true ? 120 : 35
|
|
);
|
|
|
|
vue.onBeforeMount(() => {
|
|
setVirtualScrollSize();
|
|
});
|
|
|
|
let shouldActivate = false;
|
|
|
|
vue.onDeactivated(() => {
|
|
shouldActivate = true;
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
if (shouldActivate !== true) { return }
|
|
|
|
const scrollEl = getVirtualScrollTarget();
|
|
|
|
if (prevScrollStart !== void 0 && scrollEl !== void 0 && scrollEl !== null && scrollEl.nodeType !== 8) {
|
|
setScroll(
|
|
scrollEl,
|
|
prevScrollStart,
|
|
props.virtualScrollHorizontal,
|
|
$q.lang.rtl
|
|
);
|
|
}
|
|
else {
|
|
scrollTo(prevToIndex);
|
|
}
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
onVirtualScrollEvt.cancel();
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { scrollTo, reset, refresh });
|
|
|
|
return {
|
|
virtualScrollSliceRange,
|
|
virtualScrollSliceSizeComputed,
|
|
|
|
setVirtualScrollSize,
|
|
onVirtualScrollEvt,
|
|
localResetVirtualScroll,
|
|
padVirtualScroll,
|
|
|
|
scrollTo,
|
|
reset,
|
|
refresh
|
|
}
|
|
}
|
|
|
|
const validateNewValueMode = v => [ 'add', 'add-unique', 'toggle' ].includes(v);
|
|
const reEscapeList = '.*+?^${}()|[]\\';
|
|
const fieldPropsList = Object.keys(useFieldProps);
|
|
|
|
var QSelect = createComponent({
|
|
name: 'QSelect',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...useVirtualScrollProps,
|
|
...useFormProps,
|
|
...useFieldProps,
|
|
|
|
modelValue: {
|
|
required: true
|
|
},
|
|
|
|
multiple: Boolean,
|
|
|
|
displayValue: [ String, Number ],
|
|
displayValueHtml: Boolean,
|
|
dropdownIcon: String,
|
|
|
|
options: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
|
|
optionValue: [ Function, String ],
|
|
optionLabel: [ Function, String ],
|
|
optionDisable: [ Function, String ],
|
|
|
|
hideSelected: Boolean,
|
|
hideDropdownIcon: Boolean,
|
|
fillInput: Boolean,
|
|
|
|
maxValues: [ Number, String ],
|
|
|
|
optionsDense: Boolean,
|
|
optionsDark: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
optionsSelectedClass: String,
|
|
optionsHtml: Boolean,
|
|
|
|
optionsCover: Boolean,
|
|
|
|
menuShrink: Boolean,
|
|
menuAnchor: String,
|
|
menuSelf: String,
|
|
menuOffset: Array,
|
|
|
|
popupContentClass: String,
|
|
popupContentStyle: [ String, Array, Object ],
|
|
|
|
useInput: Boolean,
|
|
useChips: Boolean,
|
|
|
|
newValueMode: {
|
|
type: String,
|
|
validator: validateNewValueMode
|
|
},
|
|
|
|
mapOptions: Boolean,
|
|
emitValue: Boolean,
|
|
|
|
inputDebounce: {
|
|
type: [ Number, String ],
|
|
default: 500
|
|
},
|
|
|
|
inputClass: [ Array, String, Object ],
|
|
inputStyle: [ Array, String, Object ],
|
|
|
|
tabindex: {
|
|
type: [ String, Number ],
|
|
default: 0
|
|
},
|
|
|
|
autocomplete: String,
|
|
|
|
transitionShow: String,
|
|
transitionHide: String,
|
|
transitionDuration: [ String, Number ],
|
|
|
|
behavior: {
|
|
type: String,
|
|
validator: v => [ 'default', 'menu', 'dialog' ].includes(v),
|
|
default: 'default'
|
|
},
|
|
|
|
virtualScrollItemSize: {
|
|
type: [ Number, String ],
|
|
default: void 0
|
|
},
|
|
|
|
onNewValue: Function,
|
|
onFilter: Function
|
|
},
|
|
|
|
emits: [
|
|
...useFieldEmits,
|
|
'add', 'remove', 'inputValue', 'newValue',
|
|
'keyup', 'keypress', 'keydown',
|
|
'filterAbort'
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const menu = vue.ref(false);
|
|
const dialog = vue.ref(false);
|
|
const optionIndex = vue.ref(-1);
|
|
const inputValue = vue.ref('');
|
|
const dialogFieldFocused = vue.ref(false);
|
|
const innerLoadingIndicator = vue.ref(false);
|
|
|
|
let inputTimer = null, innerValueCache,
|
|
hasDialog, userInputValue, filterId = null, defaultInputValue,
|
|
transitionShowComputed, searchBuffer, searchBufferExp;
|
|
|
|
const inputRef = vue.ref(null);
|
|
const targetRef = vue.ref(null);
|
|
const menuRef = vue.ref(null);
|
|
const dialogRef = vue.ref(null);
|
|
const menuContentRef = vue.ref(null);
|
|
|
|
const nameProp = useFormInputNameAttr(props);
|
|
|
|
const onComposition = useKeyComposition(onInput);
|
|
|
|
const virtualScrollLength = vue.computed(() => (
|
|
Array.isArray(props.options)
|
|
? props.options.length
|
|
: 0
|
|
));
|
|
|
|
const virtualScrollItemSizeComputed = vue.computed(() => (
|
|
props.virtualScrollItemSize === void 0
|
|
? (props.optionsDense === true ? 24 : 48)
|
|
: props.virtualScrollItemSize
|
|
));
|
|
|
|
const {
|
|
virtualScrollSliceRange,
|
|
virtualScrollSliceSizeComputed,
|
|
localResetVirtualScroll,
|
|
padVirtualScroll,
|
|
onVirtualScrollEvt,
|
|
scrollTo,
|
|
setVirtualScrollSize
|
|
} = useVirtualScroll({
|
|
virtualScrollLength, getVirtualScrollTarget, getVirtualScrollEl,
|
|
virtualScrollItemSizeComputed
|
|
});
|
|
|
|
const state = useFieldState();
|
|
|
|
const innerValue = vue.computed(() => {
|
|
const
|
|
mapNull = props.mapOptions === true && props.multiple !== true,
|
|
val = props.modelValue !== void 0 && (props.modelValue !== null || mapNull === true)
|
|
? (props.multiple === true && Array.isArray(props.modelValue) ? props.modelValue : [ props.modelValue ])
|
|
: [];
|
|
|
|
if (props.mapOptions === true && Array.isArray(props.options) === true) {
|
|
const cache = props.mapOptions === true && innerValueCache !== void 0
|
|
? innerValueCache
|
|
: [];
|
|
const values = val.map(v => getOption(v, cache));
|
|
|
|
return props.modelValue === null && mapNull === true
|
|
? values.filter(v => v !== null)
|
|
: values
|
|
}
|
|
|
|
return val
|
|
});
|
|
|
|
const innerFieldProps = vue.computed(() => {
|
|
const acc = {};
|
|
fieldPropsList.forEach(key => {
|
|
const val = props[ key ];
|
|
if (val !== void 0) {
|
|
acc[ key ] = val;
|
|
}
|
|
});
|
|
return acc
|
|
});
|
|
|
|
const isOptionsDark = vue.computed(() => (
|
|
props.optionsDark === null
|
|
? state.isDark.value
|
|
: props.optionsDark
|
|
));
|
|
|
|
const hasValue = vue.computed(() => fieldValueIsFilled(innerValue.value));
|
|
|
|
const computedInputClass = vue.computed(() => {
|
|
let cls = 'q-field__input q-placeholder col';
|
|
|
|
if (props.hideSelected === true || innerValue.value.length === 0) {
|
|
return [ cls, props.inputClass ]
|
|
}
|
|
|
|
cls += ' q-field__input--padding';
|
|
|
|
return props.inputClass === void 0
|
|
? cls
|
|
: [ cls, props.inputClass ]
|
|
});
|
|
|
|
const menuContentClass = vue.computed(() =>
|
|
(props.virtualScrollHorizontal === true ? 'q-virtual-scroll--horizontal' : '')
|
|
+ (props.popupContentClass ? ' ' + props.popupContentClass : '')
|
|
);
|
|
|
|
const noOptions = vue.computed(() => virtualScrollLength.value === 0);
|
|
|
|
const selectedString = vue.computed(() =>
|
|
innerValue.value
|
|
.map(opt => getOptionLabel.value(opt))
|
|
.join(', ')
|
|
);
|
|
|
|
const ariaCurrentValue = vue.computed(() => (props.displayValue !== void 0
|
|
? props.displayValue
|
|
: selectedString.value
|
|
));
|
|
|
|
const needsHtmlFn = vue.computed(() => (
|
|
props.optionsHtml === true
|
|
? () => true
|
|
: opt => opt !== void 0 && opt !== null && opt.html === true
|
|
));
|
|
|
|
const valueAsHtml = vue.computed(() => (
|
|
props.displayValueHtml === true || (
|
|
props.displayValue === void 0 && (
|
|
props.optionsHtml === true
|
|
|| innerValue.value.some(needsHtmlFn.value)
|
|
)
|
|
)
|
|
));
|
|
|
|
const tabindex = vue.computed(() => (state.focused.value === true ? props.tabindex : -1));
|
|
|
|
const comboboxAttrs = vue.computed(() => {
|
|
const attrs = {
|
|
tabindex: props.tabindex,
|
|
role: 'combobox',
|
|
'aria-label': props.label,
|
|
'aria-readonly': props.readonly === true ? 'true' : 'false',
|
|
'aria-autocomplete': props.useInput === true ? 'list' : 'none',
|
|
'aria-expanded': menu.value === true ? 'true' : 'false',
|
|
'aria-controls': `${ state.targetUid.value }_lb`
|
|
};
|
|
|
|
if (optionIndex.value >= 0) {
|
|
attrs[ 'aria-activedescendant' ] = `${ state.targetUid.value }_${ optionIndex.value }`;
|
|
}
|
|
|
|
return attrs
|
|
});
|
|
|
|
const listboxAttrs = vue.computed(() => ({
|
|
id: `${ state.targetUid.value }_lb`,
|
|
role: 'listbox',
|
|
'aria-multiselectable': props.multiple === true ? 'true' : 'false'
|
|
}));
|
|
|
|
const selectedScope = vue.computed(() => {
|
|
return innerValue.value.map((opt, i) => ({
|
|
index: i,
|
|
opt,
|
|
html: needsHtmlFn.value(opt),
|
|
selected: true,
|
|
removeAtIndex: removeAtIndexAndFocus,
|
|
toggleOption,
|
|
tabindex: tabindex.value
|
|
}))
|
|
});
|
|
|
|
const optionScope = vue.computed(() => {
|
|
if (virtualScrollLength.value === 0) {
|
|
return []
|
|
}
|
|
|
|
const { from, to } = virtualScrollSliceRange.value;
|
|
|
|
return props.options.slice(from, to).map((opt, i) => {
|
|
const disable = isOptionDisabled.value(opt) === true;
|
|
const index = from + i;
|
|
|
|
const itemProps = {
|
|
clickable: true,
|
|
active: false,
|
|
activeClass: computedOptionsSelectedClass.value,
|
|
manualFocus: true,
|
|
focused: false,
|
|
disable,
|
|
tabindex: -1,
|
|
dense: props.optionsDense,
|
|
dark: isOptionsDark.value,
|
|
role: 'option',
|
|
id: `${ state.targetUid.value }_${ index }`,
|
|
onClick: () => { toggleOption(opt); }
|
|
};
|
|
|
|
if (disable !== true) {
|
|
isOptionSelected(opt) === true && (itemProps.active = true);
|
|
optionIndex.value === index && (itemProps.focused = true);
|
|
|
|
itemProps[ 'aria-selected' ] = itemProps.active === true ? 'true' : 'false';
|
|
|
|
if ($q.platform.is.desktop === true) {
|
|
itemProps.onMousemove = () => { menu.value === true && setOptionIndex(index); };
|
|
}
|
|
}
|
|
|
|
return {
|
|
index,
|
|
opt,
|
|
html: needsHtmlFn.value(opt),
|
|
label: getOptionLabel.value(opt),
|
|
selected: itemProps.active,
|
|
focused: itemProps.focused,
|
|
toggleOption,
|
|
setOptionIndex,
|
|
itemProps
|
|
}
|
|
})
|
|
});
|
|
|
|
const dropdownArrowIcon = vue.computed(() => (
|
|
props.dropdownIcon !== void 0
|
|
? props.dropdownIcon
|
|
: $q.iconSet.arrow.dropdown
|
|
));
|
|
|
|
const squaredMenu = vue.computed(() =>
|
|
props.optionsCover === false
|
|
&& props.outlined !== true
|
|
&& props.standout !== true
|
|
&& props.borderless !== true
|
|
&& props.rounded !== true
|
|
);
|
|
|
|
const computedOptionsSelectedClass = vue.computed(() => (
|
|
props.optionsSelectedClass !== void 0
|
|
? props.optionsSelectedClass
|
|
: (props.color !== void 0 ? `text-${ props.color }` : '')
|
|
));
|
|
|
|
// returns method to get value of an option;
|
|
// takes into account 'option-value' prop
|
|
const getOptionValue = vue.computed(() => getPropValueFn(props.optionValue, 'value'));
|
|
|
|
// returns method to get label of an option;
|
|
// takes into account 'option-label' prop
|
|
const getOptionLabel = vue.computed(() => getPropValueFn(props.optionLabel, 'label'));
|
|
|
|
// returns method to tell if an option is disabled;
|
|
// takes into account 'option-disable' prop
|
|
const isOptionDisabled = vue.computed(() => getPropValueFn(props.optionDisable, 'disable'));
|
|
|
|
const innerOptionsValue = vue.computed(() => innerValue.value.map(opt => getOptionValue.value(opt)));
|
|
|
|
const inputControlEvents = vue.computed(() => {
|
|
const evt = {
|
|
onInput,
|
|
// Safari < 10.2 & UIWebView doesn't fire compositionend when
|
|
// switching focus before confirming composition choice
|
|
// this also fixes the issue where some browsers e.g. iOS Chrome
|
|
// fires "change" instead of "input" on autocomplete.
|
|
onChange: onComposition,
|
|
onKeydown: onTargetKeydown,
|
|
onKeyup: onTargetAutocomplete,
|
|
onKeypress: onTargetKeypress,
|
|
onFocus: selectInputText,
|
|
onClick (e) { hasDialog === true && stop(e); }
|
|
};
|
|
|
|
evt.onCompositionstart = evt.onCompositionupdate = evt.onCompositionend = onComposition;
|
|
|
|
return evt
|
|
});
|
|
|
|
vue.watch(innerValue, val => {
|
|
innerValueCache = val;
|
|
|
|
if (
|
|
props.useInput === true
|
|
&& props.fillInput === true
|
|
&& props.multiple !== true
|
|
// Prevent re-entering in filter while filtering
|
|
// Also prevent clearing inputValue while filtering
|
|
&& state.innerLoading.value !== true
|
|
&& ((dialog.value !== true && menu.value !== true) || hasValue.value !== true)
|
|
) {
|
|
userInputValue !== true && resetInputValue();
|
|
if (dialog.value === true || menu.value === true) {
|
|
filter('');
|
|
}
|
|
}
|
|
}, { immediate: true });
|
|
|
|
vue.watch(() => props.fillInput, resetInputValue);
|
|
|
|
vue.watch(menu, updateMenu);
|
|
|
|
vue.watch(virtualScrollLength, rerenderMenu);
|
|
|
|
function getEmittingOptionValue (opt) {
|
|
return props.emitValue === true
|
|
? getOptionValue.value(opt)
|
|
: opt
|
|
}
|
|
|
|
function removeAtIndex (index) {
|
|
if (index > -1 && index < innerValue.value.length) {
|
|
if (props.multiple === true) {
|
|
const model = props.modelValue.slice();
|
|
emit('remove', { index, value: model.splice(index, 1)[ 0 ] });
|
|
emit('update:modelValue', model);
|
|
}
|
|
else {
|
|
emit('update:modelValue', null);
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeAtIndexAndFocus (index) {
|
|
removeAtIndex(index);
|
|
state.focus();
|
|
}
|
|
|
|
function add (opt, unique) {
|
|
const val = getEmittingOptionValue(opt);
|
|
|
|
if (props.multiple !== true) {
|
|
props.fillInput === true && updateInputValue(
|
|
getOptionLabel.value(opt),
|
|
true,
|
|
true
|
|
);
|
|
|
|
emit('update:modelValue', val);
|
|
return
|
|
}
|
|
|
|
if (innerValue.value.length === 0) {
|
|
emit('add', { index: 0, value: val });
|
|
emit('update:modelValue', props.multiple === true ? [ val ] : val);
|
|
return
|
|
}
|
|
|
|
if (unique === true && isOptionSelected(opt) === true) {
|
|
return
|
|
}
|
|
|
|
if (props.maxValues !== void 0 && props.modelValue.length >= props.maxValues) {
|
|
return
|
|
}
|
|
|
|
const model = props.modelValue.slice();
|
|
|
|
emit('add', { index: model.length, value: val });
|
|
model.push(val);
|
|
emit('update:modelValue', model);
|
|
}
|
|
|
|
function toggleOption (opt, keepOpen) {
|
|
if (state.editable.value !== true || opt === void 0 || isOptionDisabled.value(opt) === true) {
|
|
return
|
|
}
|
|
|
|
const optValue = getOptionValue.value(opt);
|
|
|
|
if (props.multiple !== true) {
|
|
if (keepOpen !== true) {
|
|
updateInputValue(
|
|
props.fillInput === true ? getOptionLabel.value(opt) : '',
|
|
true,
|
|
true
|
|
);
|
|
|
|
hidePopup();
|
|
}
|
|
|
|
targetRef.value !== null && targetRef.value.focus();
|
|
|
|
if (
|
|
innerValue.value.length === 0
|
|
|| isDeepEqual(getOptionValue.value(innerValue.value[ 0 ]), optValue) !== true
|
|
) {
|
|
emit('update:modelValue', props.emitValue === true ? optValue : opt);
|
|
}
|
|
return
|
|
}
|
|
|
|
(hasDialog !== true || dialogFieldFocused.value === true) && state.focus();
|
|
|
|
selectInputText();
|
|
|
|
if (innerValue.value.length === 0) {
|
|
const val = props.emitValue === true ? optValue : opt;
|
|
emit('add', { index: 0, value: val });
|
|
emit('update:modelValue', props.multiple === true ? [ val ] : val);
|
|
return
|
|
}
|
|
|
|
const
|
|
model = props.modelValue.slice(),
|
|
index = innerOptionsValue.value.findIndex(v => isDeepEqual(v, optValue));
|
|
|
|
if (index > -1) {
|
|
emit('remove', { index, value: model.splice(index, 1)[ 0 ] });
|
|
}
|
|
else {
|
|
if (props.maxValues !== void 0 && model.length >= props.maxValues) {
|
|
return
|
|
}
|
|
|
|
const val = props.emitValue === true ? optValue : opt;
|
|
|
|
emit('add', { index: model.length, value: val });
|
|
model.push(val);
|
|
}
|
|
|
|
emit('update:modelValue', model);
|
|
}
|
|
|
|
function setOptionIndex (index) {
|
|
if ($q.platform.is.desktop !== true) { return }
|
|
|
|
const val = index > -1 && index < virtualScrollLength.value
|
|
? index
|
|
: -1;
|
|
|
|
if (optionIndex.value !== val) {
|
|
optionIndex.value = val;
|
|
}
|
|
}
|
|
|
|
function moveOptionSelection (offset = 1, skipInputValue) {
|
|
if (menu.value === true) {
|
|
let index = optionIndex.value;
|
|
do {
|
|
index = normalizeToInterval(
|
|
index + offset,
|
|
-1,
|
|
virtualScrollLength.value - 1
|
|
);
|
|
}
|
|
while (index !== -1 && index !== optionIndex.value && isOptionDisabled.value(props.options[ index ]) === true)
|
|
|
|
if (optionIndex.value !== index) {
|
|
setOptionIndex(index);
|
|
scrollTo(index);
|
|
|
|
if (skipInputValue !== true && props.useInput === true && props.fillInput === true) {
|
|
setInputValue(index >= 0
|
|
? getOptionLabel.value(props.options[ index ])
|
|
: defaultInputValue
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getOption (value, valueCache) {
|
|
const fn = opt => isDeepEqual(getOptionValue.value(opt), value);
|
|
return props.options.find(fn) || valueCache.find(fn) || value
|
|
}
|
|
|
|
function getPropValueFn (propValue, defaultVal) {
|
|
const val = propValue !== void 0
|
|
? propValue
|
|
: defaultVal;
|
|
|
|
return typeof val === 'function'
|
|
? val
|
|
: opt => (opt !== null && typeof opt === 'object' && val in opt ? opt[ val ] : opt)
|
|
}
|
|
|
|
function isOptionSelected (opt) {
|
|
const val = getOptionValue.value(opt);
|
|
return innerOptionsValue.value.find(v => isDeepEqual(v, val)) !== void 0
|
|
}
|
|
|
|
function selectInputText (e) {
|
|
if (
|
|
props.useInput === true
|
|
&& targetRef.value !== null
|
|
&& (e === void 0 || (targetRef.value === e.target && e.target.value === selectedString.value))
|
|
) {
|
|
targetRef.value.select();
|
|
}
|
|
}
|
|
|
|
function onTargetKeyup (e) {
|
|
// if ESC and we have an opened menu
|
|
// then stop propagation (might be caught by a QDialog
|
|
// and so it will also close the QDialog, which is wrong)
|
|
if (isKeyCode(e, 27) === true && menu.value === true) {
|
|
stop(e);
|
|
// on ESC we need to close the dialog also
|
|
hidePopup();
|
|
resetInputValue();
|
|
}
|
|
|
|
emit('keyup', e);
|
|
}
|
|
|
|
function onTargetAutocomplete (e) {
|
|
const { value } = e.target;
|
|
|
|
if (e.keyCode !== void 0) {
|
|
onTargetKeyup(e);
|
|
return
|
|
}
|
|
|
|
e.target.value = '';
|
|
|
|
if (inputTimer !== null) {
|
|
clearTimeout(inputTimer);
|
|
inputTimer = null;
|
|
}
|
|
|
|
resetInputValue();
|
|
|
|
if (typeof value === 'string' && value.length !== 0) {
|
|
const needle = value.toLocaleLowerCase();
|
|
const findFn = extractFn => {
|
|
const option = props.options.find(opt => extractFn.value(opt).toLocaleLowerCase() === needle);
|
|
|
|
if (option === void 0) {
|
|
return false
|
|
}
|
|
|
|
if (innerValue.value.indexOf(option) === -1) {
|
|
toggleOption(option);
|
|
}
|
|
else {
|
|
hidePopup();
|
|
}
|
|
|
|
return true
|
|
};
|
|
const fillFn = afterFilter => {
|
|
if (findFn(getOptionValue) === true) {
|
|
return
|
|
}
|
|
if (findFn(getOptionLabel) === true || afterFilter === true) {
|
|
return
|
|
}
|
|
|
|
filter(value, true, () => fillFn(true));
|
|
};
|
|
|
|
fillFn();
|
|
}
|
|
else {
|
|
state.clearValue(e);
|
|
}
|
|
}
|
|
|
|
function onTargetKeypress (e) {
|
|
emit('keypress', e);
|
|
}
|
|
|
|
function onTargetKeydown (e) {
|
|
emit('keydown', e);
|
|
|
|
if (shouldIgnoreKey(e) === true) {
|
|
return
|
|
}
|
|
|
|
const newValueModeValid = inputValue.value.length !== 0
|
|
&& (props.newValueMode !== void 0 || props.onNewValue !== void 0);
|
|
|
|
const tabShouldSelect = e.shiftKey !== true
|
|
&& props.multiple !== true
|
|
&& (optionIndex.value > -1 || newValueModeValid === true);
|
|
|
|
// escape
|
|
if (e.keyCode === 27) {
|
|
prevent(e); // prevent clearing the inputValue
|
|
return
|
|
}
|
|
|
|
// tab
|
|
if (e.keyCode === 9 && tabShouldSelect === false) {
|
|
closeMenu();
|
|
return
|
|
}
|
|
|
|
if (
|
|
e.target === void 0
|
|
|| e.target.id !== state.targetUid.value
|
|
|| state.editable.value !== true
|
|
) { return }
|
|
|
|
// down
|
|
if (
|
|
e.keyCode === 40
|
|
&& state.innerLoading.value !== true
|
|
&& menu.value === false
|
|
) {
|
|
stopAndPrevent(e);
|
|
showPopup();
|
|
return
|
|
}
|
|
|
|
// backspace
|
|
if (
|
|
e.keyCode === 8
|
|
&& props.hideSelected !== true
|
|
&& inputValue.value.length === 0
|
|
) {
|
|
if (props.multiple === true && Array.isArray(props.modelValue) === true) {
|
|
removeAtIndex(props.modelValue.length - 1);
|
|
}
|
|
else if (props.multiple !== true && props.modelValue !== null) {
|
|
emit('update:modelValue', null);
|
|
}
|
|
return
|
|
}
|
|
|
|
// home, end - 36, 35
|
|
if (
|
|
(e.keyCode === 35 || e.keyCode === 36)
|
|
&& (typeof inputValue.value !== 'string' || inputValue.value.length === 0)
|
|
) {
|
|
stopAndPrevent(e);
|
|
optionIndex.value = -1;
|
|
moveOptionSelection(e.keyCode === 36 ? 1 : -1, props.multiple);
|
|
}
|
|
|
|
// pg up, pg down - 33, 34
|
|
if (
|
|
(e.keyCode === 33 || e.keyCode === 34)
|
|
&& virtualScrollSliceSizeComputed.value !== void 0
|
|
) {
|
|
stopAndPrevent(e);
|
|
optionIndex.value = Math.max(
|
|
-1,
|
|
Math.min(
|
|
virtualScrollLength.value,
|
|
optionIndex.value + (e.keyCode === 33 ? -1 : 1) * virtualScrollSliceSizeComputed.value.view
|
|
)
|
|
);
|
|
moveOptionSelection(e.keyCode === 33 ? 1 : -1, props.multiple);
|
|
}
|
|
|
|
// up, down
|
|
if (e.keyCode === 38 || e.keyCode === 40) {
|
|
stopAndPrevent(e);
|
|
moveOptionSelection(e.keyCode === 38 ? -1 : 1, props.multiple);
|
|
}
|
|
|
|
const optionsLength = virtualScrollLength.value;
|
|
|
|
// clear search buffer if expired
|
|
if (searchBuffer === void 0 || searchBufferExp < Date.now()) {
|
|
searchBuffer = '';
|
|
}
|
|
|
|
// keyboard search when not having use-input
|
|
if (
|
|
optionsLength > 0
|
|
&& props.useInput !== true
|
|
&& e.key !== void 0
|
|
&& e.key.length === 1 // printable char
|
|
&& e.altKey === false // not kbd shortcut
|
|
&& e.ctrlKey === false // not kbd shortcut
|
|
&& e.metaKey === false // not kbd shortcut, especially on macOS with Command key
|
|
&& (e.keyCode !== 32 || searchBuffer.length !== 0) // space in middle of search
|
|
) {
|
|
menu.value !== true && showPopup(e);
|
|
|
|
const
|
|
char = e.key.toLocaleLowerCase(),
|
|
keyRepeat = searchBuffer.length === 1 && searchBuffer[ 0 ] === char;
|
|
|
|
searchBufferExp = Date.now() + 1500;
|
|
if (keyRepeat === false) {
|
|
stopAndPrevent(e);
|
|
searchBuffer += char;
|
|
}
|
|
|
|
const searchRe = new RegExp('^' + searchBuffer.split('').map(l => (reEscapeList.indexOf(l) > -1 ? '\\' + l : l)).join('.*'), 'i');
|
|
|
|
let index = optionIndex.value;
|
|
|
|
if (keyRepeat === true || index < 0 || searchRe.test(getOptionLabel.value(props.options[ index ])) !== true) {
|
|
do {
|
|
index = normalizeToInterval(index + 1, -1, optionsLength - 1);
|
|
}
|
|
while (index !== optionIndex.value && (
|
|
isOptionDisabled.value(props.options[ index ]) === true
|
|
|| searchRe.test(getOptionLabel.value(props.options[ index ])) !== true
|
|
))
|
|
}
|
|
|
|
if (optionIndex.value !== index) {
|
|
vue.nextTick(() => {
|
|
setOptionIndex(index);
|
|
scrollTo(index);
|
|
|
|
if (index >= 0 && props.useInput === true && props.fillInput === true) {
|
|
setInputValue(getOptionLabel.value(props.options[ index ]));
|
|
}
|
|
});
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// enter, space (when not using use-input and not in search), or tab (when not using multiple and option selected)
|
|
// same target is checked above
|
|
if (
|
|
e.keyCode !== 13
|
|
&& (e.keyCode !== 32 || props.useInput === true || searchBuffer !== '')
|
|
&& (e.keyCode !== 9 || tabShouldSelect === false)
|
|
) { return }
|
|
|
|
e.keyCode !== 9 && stopAndPrevent(e);
|
|
|
|
if (optionIndex.value > -1 && optionIndex.value < optionsLength) {
|
|
toggleOption(props.options[ optionIndex.value ]);
|
|
return
|
|
}
|
|
|
|
if (newValueModeValid === true) {
|
|
const done = (val, mode) => {
|
|
if (mode) {
|
|
if (validateNewValueMode(mode) !== true) {
|
|
return
|
|
}
|
|
}
|
|
else {
|
|
mode = props.newValueMode;
|
|
}
|
|
|
|
updateInputValue('', props.multiple !== true, true);
|
|
|
|
if (val === void 0 || val === null) {
|
|
return
|
|
}
|
|
|
|
const fn = mode === 'toggle' ? toggleOption : add;
|
|
fn(val, mode === 'add-unique');
|
|
|
|
if (props.multiple !== true) {
|
|
targetRef.value !== null && targetRef.value.focus();
|
|
hidePopup();
|
|
}
|
|
};
|
|
|
|
if (props.onNewValue !== void 0) {
|
|
emit('newValue', inputValue.value, done);
|
|
}
|
|
else {
|
|
done(inputValue.value);
|
|
}
|
|
|
|
if (props.multiple !== true) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if (menu.value === true) {
|
|
closeMenu();
|
|
}
|
|
else if (state.innerLoading.value !== true) {
|
|
showPopup();
|
|
}
|
|
}
|
|
|
|
function getVirtualScrollEl () {
|
|
return hasDialog === true
|
|
? menuContentRef.value
|
|
: (
|
|
menuRef.value !== null && menuRef.value.contentEl !== null
|
|
? menuRef.value.contentEl
|
|
: void 0
|
|
)
|
|
}
|
|
|
|
function getVirtualScrollTarget () {
|
|
return getVirtualScrollEl()
|
|
}
|
|
|
|
function getSelection () {
|
|
if (props.hideSelected === true) {
|
|
return []
|
|
}
|
|
|
|
if (slots[ 'selected-item' ] !== void 0) {
|
|
return selectedScope.value.map(scope => slots[ 'selected-item' ](scope)).slice()
|
|
}
|
|
|
|
if (slots.selected !== void 0) {
|
|
return [].concat(slots.selected())
|
|
}
|
|
|
|
if (props.useChips === true) {
|
|
return selectedScope.value.map((scope, i) => vue.h(QChip, {
|
|
key: 'option-' + i,
|
|
removable: state.editable.value === true && isOptionDisabled.value(scope.opt) !== true,
|
|
dense: true,
|
|
textColor: props.color,
|
|
tabindex: tabindex.value,
|
|
onRemove () { scope.removeAtIndex(i); }
|
|
}, () => vue.h('span', {
|
|
class: 'ellipsis',
|
|
[ scope.html === true ? 'innerHTML' : 'textContent' ]: getOptionLabel.value(scope.opt)
|
|
})))
|
|
}
|
|
|
|
return [
|
|
vue.h('span', {
|
|
[ valueAsHtml.value === true ? 'innerHTML' : 'textContent' ]: ariaCurrentValue.value
|
|
})
|
|
]
|
|
}
|
|
|
|
function getAllOptions () {
|
|
if (noOptions.value === true) {
|
|
return slots[ 'no-option' ] !== void 0
|
|
? slots[ 'no-option' ]({ inputValue: inputValue.value })
|
|
: void 0
|
|
}
|
|
|
|
const fn = slots.option !== void 0
|
|
? slots.option
|
|
: scope => {
|
|
return vue.h(QItem, {
|
|
key: scope.index,
|
|
...scope.itemProps
|
|
}, () => {
|
|
return vue.h(
|
|
QItemSection,
|
|
() => vue.h(
|
|
QItemLabel,
|
|
() => vue.h('span', {
|
|
[ scope.html === true ? 'innerHTML' : 'textContent' ]: scope.label
|
|
})
|
|
)
|
|
)
|
|
})
|
|
};
|
|
|
|
let options = padVirtualScroll('div', optionScope.value.map(fn));
|
|
|
|
if (slots[ 'before-options' ] !== void 0) {
|
|
options = slots[ 'before-options' ]().concat(options);
|
|
}
|
|
|
|
return hMergeSlot(slots[ 'after-options' ], options)
|
|
}
|
|
|
|
function getInput (fromDialog, isTarget) {
|
|
const attrs = isTarget === true ? { ...comboboxAttrs.value, ...state.splitAttrs.attributes.value } : void 0;
|
|
|
|
const data = {
|
|
ref: isTarget === true ? targetRef : void 0,
|
|
key: 'i_t',
|
|
class: computedInputClass.value,
|
|
style: props.inputStyle,
|
|
value: inputValue.value !== void 0 ? inputValue.value : '',
|
|
// required for Android in order to show ENTER key when in form
|
|
type: 'search',
|
|
...attrs,
|
|
id: isTarget === true ? state.targetUid.value : void 0,
|
|
maxlength: props.maxlength,
|
|
autocomplete: props.autocomplete,
|
|
'data-autofocus': fromDialog === true || props.autofocus === true || void 0,
|
|
disabled: props.disable === true,
|
|
readonly: props.readonly === true,
|
|
...inputControlEvents.value
|
|
};
|
|
|
|
if (fromDialog !== true && hasDialog === true) {
|
|
if (Array.isArray(data.class) === true) {
|
|
data.class = [ ...data.class, 'no-pointer-events' ];
|
|
}
|
|
else {
|
|
data.class += ' no-pointer-events';
|
|
}
|
|
}
|
|
|
|
return vue.h('input', data)
|
|
}
|
|
|
|
function onInput (e) {
|
|
if (inputTimer !== null) {
|
|
clearTimeout(inputTimer);
|
|
inputTimer = null;
|
|
}
|
|
|
|
if (e && e.target && e.target.qComposing === true) {
|
|
return
|
|
}
|
|
|
|
setInputValue(e.target.value || '');
|
|
// mark it here as user input so that if updateInputValue is called
|
|
// before filter is called the indicator is reset
|
|
userInputValue = true;
|
|
defaultInputValue = inputValue.value;
|
|
|
|
if (
|
|
state.focused.value !== true
|
|
&& (hasDialog !== true || dialogFieldFocused.value === true)
|
|
) {
|
|
state.focus();
|
|
}
|
|
|
|
if (props.onFilter !== void 0) {
|
|
inputTimer = setTimeout(() => {
|
|
inputTimer = null;
|
|
filter(inputValue.value);
|
|
}, props.inputDebounce);
|
|
}
|
|
}
|
|
|
|
function setInputValue (val) {
|
|
if (inputValue.value !== val) {
|
|
inputValue.value = val;
|
|
emit('inputValue', val);
|
|
}
|
|
}
|
|
|
|
function updateInputValue (val, noFiltering, internal) {
|
|
userInputValue = internal !== true;
|
|
|
|
if (props.useInput === true) {
|
|
setInputValue(val);
|
|
|
|
if (noFiltering === true || internal !== true) {
|
|
defaultInputValue = val;
|
|
}
|
|
|
|
noFiltering !== true && filter(val);
|
|
}
|
|
}
|
|
|
|
function filter (val, keepClosed, afterUpdateFn) {
|
|
if (props.onFilter === void 0 || (keepClosed !== true && state.focused.value !== true)) {
|
|
return
|
|
}
|
|
|
|
if (state.innerLoading.value === true) {
|
|
emit('filterAbort');
|
|
}
|
|
else {
|
|
state.innerLoading.value = true;
|
|
innerLoadingIndicator.value = true;
|
|
}
|
|
|
|
if (
|
|
val !== ''
|
|
&& props.multiple !== true
|
|
&& innerValue.value.length !== 0
|
|
&& userInputValue !== true
|
|
&& val === getOptionLabel.value(innerValue.value[ 0 ])
|
|
) {
|
|
val = '';
|
|
}
|
|
|
|
const localFilterId = setTimeout(() => {
|
|
menu.value === true && (menu.value = false);
|
|
}, 10);
|
|
|
|
filterId !== null && clearTimeout(filterId);
|
|
filterId = localFilterId;
|
|
|
|
emit(
|
|
'filter',
|
|
val,
|
|
(fn, afterFn) => {
|
|
if ((keepClosed === true || state.focused.value === true) && filterId === localFilterId) {
|
|
clearTimeout(filterId);
|
|
|
|
typeof fn === 'function' && fn();
|
|
|
|
// hide indicator to allow arrow to animate
|
|
innerLoadingIndicator.value = false;
|
|
|
|
vue.nextTick(() => {
|
|
state.innerLoading.value = false;
|
|
|
|
if (state.editable.value === true) {
|
|
if (keepClosed === true) {
|
|
menu.value === true && hidePopup();
|
|
}
|
|
else if (menu.value === true) {
|
|
updateMenu(true);
|
|
}
|
|
else {
|
|
menu.value = true;
|
|
}
|
|
}
|
|
|
|
typeof afterFn === 'function' && vue.nextTick(() => { afterFn(proxy); });
|
|
typeof afterUpdateFn === 'function' && vue.nextTick(() => { afterUpdateFn(proxy); });
|
|
});
|
|
}
|
|
},
|
|
() => {
|
|
if (state.focused.value === true && filterId === localFilterId) {
|
|
clearTimeout(filterId);
|
|
state.innerLoading.value = false;
|
|
innerLoadingIndicator.value = false;
|
|
}
|
|
menu.value === true && (menu.value = false);
|
|
}
|
|
);
|
|
}
|
|
|
|
function getMenu () {
|
|
return vue.h(QMenu, {
|
|
ref: menuRef,
|
|
class: menuContentClass.value,
|
|
style: props.popupContentStyle,
|
|
modelValue: menu.value,
|
|
fit: props.menuShrink !== true,
|
|
cover: props.optionsCover === true && noOptions.value !== true && props.useInput !== true,
|
|
anchor: props.menuAnchor,
|
|
self: props.menuSelf,
|
|
offset: props.menuOffset,
|
|
dark: isOptionsDark.value,
|
|
noParentEvent: true,
|
|
noRefocus: true,
|
|
noFocus: true,
|
|
square: squaredMenu.value,
|
|
transitionShow: props.transitionShow,
|
|
transitionHide: props.transitionHide,
|
|
transitionDuration: props.transitionDuration,
|
|
separateClosePopup: true,
|
|
...listboxAttrs.value,
|
|
onScrollPassive: onVirtualScrollEvt,
|
|
onBeforeShow: onControlPopupShow,
|
|
onBeforeHide: onMenuBeforeHide,
|
|
onShow: onMenuShow
|
|
}, getAllOptions)
|
|
}
|
|
|
|
function onMenuBeforeHide (e) {
|
|
onControlPopupHide(e);
|
|
closeMenu();
|
|
}
|
|
|
|
function onMenuShow () {
|
|
setVirtualScrollSize();
|
|
}
|
|
|
|
function onDialogFieldFocus (e) {
|
|
stop(e);
|
|
targetRef.value !== null && targetRef.value.focus();
|
|
dialogFieldFocused.value = true;
|
|
window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0);
|
|
}
|
|
|
|
function onDialogFieldBlur (e) {
|
|
stop(e);
|
|
vue.nextTick(() => {
|
|
dialogFieldFocused.value = false;
|
|
});
|
|
}
|
|
|
|
function getDialog () {
|
|
const content = [
|
|
vue.h(QField, {
|
|
class: `col-auto ${ state.fieldClass.value }`,
|
|
...innerFieldProps.value,
|
|
for: state.targetUid.value,
|
|
dark: isOptionsDark.value,
|
|
square: true,
|
|
loading: innerLoadingIndicator.value,
|
|
itemAligned: false,
|
|
filled: true,
|
|
stackLabel: inputValue.value.length !== 0,
|
|
...state.splitAttrs.listeners.value,
|
|
onFocus: onDialogFieldFocus,
|
|
onBlur: onDialogFieldBlur
|
|
}, {
|
|
...slots,
|
|
rawControl: () => state.getControl(true),
|
|
before: void 0,
|
|
after: void 0
|
|
})
|
|
];
|
|
|
|
menu.value === true && content.push(
|
|
vue.h('div', {
|
|
ref: menuContentRef,
|
|
class: menuContentClass.value + ' scroll',
|
|
style: props.popupContentStyle,
|
|
...listboxAttrs.value,
|
|
onClick: prevent,
|
|
onScrollPassive: onVirtualScrollEvt
|
|
}, getAllOptions())
|
|
);
|
|
|
|
return vue.h(QDialog, {
|
|
ref: dialogRef,
|
|
modelValue: dialog.value,
|
|
position: props.useInput === true ? 'top' : void 0,
|
|
transitionShow: transitionShowComputed,
|
|
transitionHide: props.transitionHide,
|
|
transitionDuration: props.transitionDuration,
|
|
onBeforeShow: onControlPopupShow,
|
|
onBeforeHide: onDialogBeforeHide,
|
|
onHide: onDialogHide,
|
|
onShow: onDialogShow
|
|
}, () => vue.h('div', {
|
|
class: 'q-select__dialog'
|
|
+ (isOptionsDark.value === true ? ' q-select__dialog--dark q-dark' : '')
|
|
+ (dialogFieldFocused.value === true ? ' q-select__dialog--focused' : '')
|
|
}, content))
|
|
}
|
|
|
|
function onDialogBeforeHide (e) {
|
|
onControlPopupHide(e);
|
|
|
|
if (dialogRef.value !== null) {
|
|
dialogRef.value.__updateRefocusTarget(
|
|
state.rootRef.value.querySelector('.q-field__native > [tabindex]:last-child')
|
|
);
|
|
}
|
|
|
|
state.focused.value = false;
|
|
}
|
|
|
|
function onDialogHide (e) {
|
|
hidePopup();
|
|
state.focused.value === false && emit('blur', e);
|
|
resetInputValue();
|
|
}
|
|
|
|
function onDialogShow () {
|
|
const el = document.activeElement;
|
|
if (
|
|
(el === null || el.id !== state.targetUid.value)
|
|
&& targetRef.value !== null
|
|
&& targetRef.value !== el
|
|
) {
|
|
targetRef.value.focus();
|
|
}
|
|
|
|
setVirtualScrollSize();
|
|
}
|
|
|
|
function closeMenu () {
|
|
if (dialog.value === true) {
|
|
return
|
|
}
|
|
|
|
optionIndex.value = -1;
|
|
|
|
if (menu.value === true) {
|
|
menu.value = false;
|
|
}
|
|
|
|
if (state.focused.value === false) {
|
|
if (filterId !== null) {
|
|
clearTimeout(filterId);
|
|
filterId = null;
|
|
}
|
|
|
|
if (state.innerLoading.value === true) {
|
|
emit('filterAbort');
|
|
state.innerLoading.value = false;
|
|
innerLoadingIndicator.value = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function showPopup (e) {
|
|
if (state.editable.value !== true) {
|
|
return
|
|
}
|
|
|
|
if (hasDialog === true) {
|
|
state.onControlFocusin(e);
|
|
dialog.value = true;
|
|
vue.nextTick(() => {
|
|
state.focus();
|
|
});
|
|
}
|
|
else {
|
|
state.focus();
|
|
}
|
|
|
|
if (props.onFilter !== void 0) {
|
|
filter(inputValue.value);
|
|
}
|
|
else if (noOptions.value !== true || slots[ 'no-option' ] !== void 0) {
|
|
menu.value = true;
|
|
}
|
|
}
|
|
|
|
function hidePopup () {
|
|
dialog.value = false;
|
|
closeMenu();
|
|
}
|
|
|
|
function resetInputValue () {
|
|
props.useInput === true && updateInputValue(
|
|
props.multiple !== true && props.fillInput === true && innerValue.value.length !== 0
|
|
? getOptionLabel.value(innerValue.value[ 0 ]) || ''
|
|
: '',
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
|
|
function updateMenu (show) {
|
|
let optionIndex = -1;
|
|
|
|
if (show === true) {
|
|
if (innerValue.value.length !== 0) {
|
|
const val = getOptionValue.value(innerValue.value[ 0 ]);
|
|
optionIndex = props.options.findIndex(v => isDeepEqual(getOptionValue.value(v), val));
|
|
}
|
|
|
|
localResetVirtualScroll(optionIndex);
|
|
}
|
|
|
|
setOptionIndex(optionIndex);
|
|
}
|
|
|
|
function rerenderMenu (newLength, oldLength) {
|
|
if (menu.value === true && state.innerLoading.value === false) {
|
|
localResetVirtualScroll(-1, true);
|
|
|
|
vue.nextTick(() => {
|
|
if (menu.value === true && state.innerLoading.value === false) {
|
|
if (newLength > oldLength) {
|
|
localResetVirtualScroll();
|
|
}
|
|
else {
|
|
updateMenu(true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateMenuPosition () {
|
|
if (dialog.value === false && menuRef.value !== null) {
|
|
menuRef.value.updatePosition();
|
|
}
|
|
}
|
|
|
|
function onControlPopupShow (e) {
|
|
e !== void 0 && stop(e);
|
|
emit('popupShow', e);
|
|
state.hasPopupOpen = true;
|
|
state.onControlFocusin(e);
|
|
}
|
|
|
|
function onControlPopupHide (e) {
|
|
e !== void 0 && stop(e);
|
|
emit('popupHide', e);
|
|
state.hasPopupOpen = false;
|
|
state.onControlFocusout(e);
|
|
}
|
|
|
|
function updatePreState () {
|
|
hasDialog = $q.platform.is.mobile !== true && props.behavior !== 'dialog'
|
|
? false
|
|
: props.behavior !== 'menu' && (
|
|
props.useInput === true
|
|
? slots[ 'no-option' ] !== void 0 || props.onFilter !== void 0 || noOptions.value === false
|
|
: true
|
|
);
|
|
|
|
transitionShowComputed = $q.platform.is.ios === true && hasDialog === true && props.useInput === true
|
|
? 'fade'
|
|
: props.transitionShow;
|
|
}
|
|
|
|
vue.onBeforeUpdate(updatePreState);
|
|
vue.onUpdated(updateMenuPosition);
|
|
|
|
updatePreState();
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
inputTimer !== null && clearTimeout(inputTimer);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
showPopup, hidePopup,
|
|
removeAtIndex, add, toggleOption,
|
|
getOptionIndex: () => optionIndex.value,
|
|
setOptionIndex, moveOptionSelection,
|
|
filter, updateMenuPosition, updateInputValue,
|
|
isOptionSelected,
|
|
getEmittingOptionValue,
|
|
isOptionDisabled: (...args) => isOptionDisabled.value.apply(null, args) === true,
|
|
getOptionValue: (...args) => getOptionValue.value.apply(null, args),
|
|
getOptionLabel: (...args) => getOptionLabel.value.apply(null, args)
|
|
});
|
|
|
|
Object.assign(state, {
|
|
innerValue,
|
|
|
|
fieldClass: vue.computed(() =>
|
|
`q-select q-field--auto-height q-select--with${ props.useInput !== true ? 'out' : '' }-input`
|
|
+ ` q-select--with${ props.useChips !== true ? 'out' : '' }-chips`
|
|
+ ` q-select--${ props.multiple === true ? 'multiple' : 'single' }`
|
|
),
|
|
|
|
inputRef,
|
|
targetRef,
|
|
hasValue,
|
|
showPopup,
|
|
|
|
floatingLabel: vue.computed(() =>
|
|
(props.hideSelected !== true && hasValue.value === true)
|
|
|| typeof inputValue.value === 'number'
|
|
|| inputValue.value.length !== 0
|
|
|| fieldValueIsFilled(props.displayValue)
|
|
),
|
|
|
|
getControlChild: () => {
|
|
if (
|
|
state.editable.value !== false && (
|
|
dialog.value === true // dialog always has menu displayed, so need to render it
|
|
|| noOptions.value !== true
|
|
|| slots[ 'no-option' ] !== void 0
|
|
)
|
|
) {
|
|
return hasDialog === true ? getDialog() : getMenu()
|
|
}
|
|
else if (state.hasPopupOpen === true) {
|
|
// explicitly set it otherwise TAB will not blur component
|
|
state.hasPopupOpen = false;
|
|
}
|
|
},
|
|
|
|
controlEvents: {
|
|
onFocusin (e) { state.onControlFocusin(e); },
|
|
onFocusout (e) {
|
|
state.onControlFocusout(e, () => {
|
|
resetInputValue();
|
|
closeMenu();
|
|
});
|
|
},
|
|
onClick (e) {
|
|
// label from QField will propagate click on the input
|
|
prevent(e);
|
|
|
|
if (hasDialog !== true && menu.value === true) {
|
|
closeMenu();
|
|
targetRef.value !== null && targetRef.value.focus();
|
|
return
|
|
}
|
|
|
|
showPopup(e);
|
|
}
|
|
},
|
|
|
|
getControl: fromDialog => {
|
|
const child = getSelection();
|
|
const isTarget = fromDialog === true || dialog.value !== true || hasDialog !== true;
|
|
|
|
if (props.useInput === true) {
|
|
child.push(getInput(fromDialog, isTarget));
|
|
}
|
|
// there can be only one (when dialog is opened the control in dialog should be target)
|
|
else if (state.editable.value === true) {
|
|
const attrs = isTarget === true ? comboboxAttrs.value : void 0;
|
|
|
|
child.push(
|
|
vue.h('input', {
|
|
ref: isTarget === true ? targetRef : void 0,
|
|
key: 'd_t',
|
|
class: 'q-select__focus-target',
|
|
id: isTarget === true ? state.targetUid.value : void 0,
|
|
value: ariaCurrentValue.value,
|
|
readonly: true,
|
|
'data-autofocus': fromDialog === true || props.autofocus === true || void 0,
|
|
...attrs,
|
|
onKeydown: onTargetKeydown,
|
|
onKeyup: onTargetKeyup,
|
|
onKeypress: onTargetKeypress
|
|
})
|
|
);
|
|
|
|
if (isTarget === true && typeof props.autocomplete === 'string' && props.autocomplete.length !== 0) {
|
|
child.push(
|
|
vue.h('input', {
|
|
class: 'q-select__autocomplete-input',
|
|
autocomplete: props.autocomplete,
|
|
tabindex: -1,
|
|
onKeyup: onTargetAutocomplete
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
if (nameProp.value !== void 0 && props.disable !== true && innerOptionsValue.value.length !== 0) {
|
|
const opts = innerOptionsValue.value.map(value => vue.h('option', { value, selected: true }));
|
|
|
|
child.push(
|
|
vue.h('select', {
|
|
class: 'hidden',
|
|
name: nameProp.value,
|
|
multiple: props.multiple
|
|
}, opts)
|
|
);
|
|
}
|
|
|
|
const attrs = props.useInput === true || isTarget !== true ? void 0 : state.splitAttrs.attributes.value;
|
|
|
|
return vue.h('div', {
|
|
class: 'q-field__native row items-center',
|
|
...attrs,
|
|
...state.splitAttrs.listeners.value
|
|
}, child)
|
|
},
|
|
|
|
getInnerAppend: () => (
|
|
props.loading !== true && innerLoadingIndicator.value !== true && props.hideDropdownIcon !== true
|
|
? [
|
|
vue.h(QIcon, {
|
|
class: 'q-select__dropdown-icon' + (menu.value === true ? ' rotate-180' : ''),
|
|
name: dropdownArrowIcon.value
|
|
})
|
|
]
|
|
: null
|
|
)
|
|
});
|
|
|
|
return useField(state)
|
|
}
|
|
});
|
|
|
|
const skeletonTypes = [
|
|
'text', 'rect', 'circle',
|
|
'QBtn', 'QBadge', 'QChip', 'QToolbar',
|
|
'QCheckbox', 'QRadio', 'QToggle',
|
|
'QSlider', 'QRange', 'QInput',
|
|
'QAvatar'
|
|
];
|
|
|
|
const skeletonAnimations = [
|
|
'wave', 'pulse', 'pulse-x', 'pulse-y', 'fade', 'blink', 'none'
|
|
];
|
|
|
|
var QSkeleton = createComponent({
|
|
name: 'QSkeleton',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
tag: {
|
|
type: String,
|
|
default: 'div'
|
|
},
|
|
|
|
type: {
|
|
type: String,
|
|
validator: v => skeletonTypes.includes(v),
|
|
default: 'rect'
|
|
},
|
|
|
|
animation: {
|
|
type: String,
|
|
validator: v => skeletonAnimations.includes(v),
|
|
default: 'wave'
|
|
},
|
|
animationSpeed: {
|
|
type: [ String, Number ],
|
|
default: 1500
|
|
},
|
|
|
|
square: Boolean,
|
|
bordered: Boolean,
|
|
|
|
size: String,
|
|
width: String,
|
|
height: String
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const style = vue.computed(() => {
|
|
const size = props.size !== void 0
|
|
? [ props.size, props.size ]
|
|
: [ props.width, props.height ];
|
|
|
|
return {
|
|
'--q-skeleton-speed': `${ props.animationSpeed }ms`,
|
|
width: size[ 0 ],
|
|
height: size[ 1 ]
|
|
}
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-skeleton q-skeleton--${ isDark.value === true ? 'dark' : 'light' } q-skeleton--type-${ props.type }`
|
|
+ (props.animation !== 'none' ? ` q-skeleton--anim q-skeleton--anim-${ props.animation }` : '')
|
|
+ (props.square === true ? ' q-skeleton--square' : '')
|
|
+ (props.bordered === true ? ' q-skeleton--bordered' : '')
|
|
);
|
|
|
|
return () => vue.h(props.tag, {
|
|
class: classes.value,
|
|
style: style.value
|
|
}, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
const slotsDef = [
|
|
[ 'left', 'center', 'start', 'width' ],
|
|
[ 'right', 'center', 'end', 'width' ],
|
|
[ 'top', 'start', 'center', 'height' ],
|
|
[ 'bottom', 'end', 'center', 'height' ]
|
|
];
|
|
|
|
var QSlideItem = createComponent({
|
|
name: 'QSlideItem',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
leftColor: String,
|
|
rightColor: String,
|
|
topColor: String,
|
|
bottomColor: String,
|
|
|
|
onSlide: Function
|
|
},
|
|
|
|
emits: [ 'action', 'top', 'right', 'bottom', 'left' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { getCacheWithFn } = useCache();
|
|
|
|
const contentRef = vue.ref(null);
|
|
|
|
let timer = null, pan = {}, dirRefs = {}, dirContentRefs = {};
|
|
|
|
const langDir = vue.computed(() => (
|
|
$q.lang.rtl === true
|
|
? { left: 'right', right: 'left' }
|
|
: { left: 'left', right: 'right' }
|
|
));
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-slide-item q-item-type overflow-hidden'
|
|
+ (isDark.value === true ? ' q-slide-item--dark q-dark' : '')
|
|
);
|
|
|
|
function reset () {
|
|
contentRef.value.style.transform = 'translate(0,0)';
|
|
}
|
|
|
|
function emitSlide (side, ratio, isReset) {
|
|
props.onSlide !== void 0 && emit('slide', { side, ratio, isReset });
|
|
}
|
|
|
|
function onPan (evt) {
|
|
const node = contentRef.value;
|
|
|
|
if (evt.isFirst) {
|
|
pan = {
|
|
dir: null,
|
|
size: { left: 0, right: 0, top: 0, bottom: 0 },
|
|
scale: 0
|
|
};
|
|
|
|
node.classList.add('no-transition');
|
|
|
|
slotsDef.forEach(slotName => {
|
|
if (slots[ slotName[ 0 ] ] !== void 0) {
|
|
const node = dirContentRefs[ slotName[ 0 ] ];
|
|
node.style.transform = 'scale(1)';
|
|
pan.size[ slotName[ 0 ] ] = node.getBoundingClientRect()[ slotName[ 3 ] ];
|
|
}
|
|
});
|
|
|
|
pan.axis = (evt.direction === 'up' || evt.direction === 'down')
|
|
? 'Y'
|
|
: 'X';
|
|
}
|
|
else if (evt.isFinal) {
|
|
node.classList.remove('no-transition');
|
|
|
|
if (pan.scale === 1) {
|
|
node.style.transform = `translate${ pan.axis }(${ pan.dir * 100 }%)`;
|
|
|
|
timer !== null && clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
timer = null;
|
|
emit(pan.showing, { reset });
|
|
emit('action', { side: pan.showing, reset });
|
|
}, 230);
|
|
}
|
|
else {
|
|
node.style.transform = 'translate(0,0)';
|
|
emitSlide(pan.showing, 0, true);
|
|
}
|
|
|
|
return
|
|
}
|
|
else {
|
|
evt.direction = pan.axis === 'X'
|
|
? evt.offset.x < 0 ? 'left' : 'right'
|
|
: evt.offset.y < 0 ? 'up' : 'down';
|
|
}
|
|
|
|
if (
|
|
(slots.left === void 0 && evt.direction === langDir.value.right)
|
|
|| (slots.right === void 0 && evt.direction === langDir.value.left)
|
|
|| (slots.top === void 0 && evt.direction === 'down')
|
|
|| (slots.bottom === void 0 && evt.direction === 'up')
|
|
) {
|
|
node.style.transform = 'translate(0,0)';
|
|
return
|
|
}
|
|
|
|
let showing, dir, dist;
|
|
|
|
if (pan.axis === 'X') {
|
|
dir = evt.direction === 'left' ? -1 : 1;
|
|
showing = dir === 1 ? langDir.value.left : langDir.value.right;
|
|
dist = evt.distance.x;
|
|
}
|
|
else {
|
|
dir = evt.direction === 'up' ? -2 : 2;
|
|
showing = dir === 2 ? 'top' : 'bottom';
|
|
dist = evt.distance.y;
|
|
}
|
|
|
|
if (pan.dir !== null && Math.abs(dir) !== Math.abs(pan.dir)) {
|
|
return
|
|
}
|
|
|
|
if (pan.dir !== dir) {
|
|
[ 'left', 'right', 'top', 'bottom' ].forEach(d => {
|
|
if (dirRefs[ d ]) {
|
|
dirRefs[ d ].style.visibility = showing === d
|
|
? 'visible'
|
|
: 'hidden';
|
|
}
|
|
});
|
|
pan.showing = showing;
|
|
pan.dir = dir;
|
|
}
|
|
|
|
pan.scale = Math.max(0, Math.min(1, (dist - 40) / pan.size[ showing ]));
|
|
|
|
node.style.transform = `translate${ pan.axis }(${ dist * dir / Math.abs(dir) }px)`;
|
|
dirContentRefs[ showing ].style.transform = `scale(${ pan.scale })`;
|
|
|
|
emitSlide(showing, pan.scale, false);
|
|
}
|
|
|
|
vue.onBeforeUpdate(() => {
|
|
dirRefs = {};
|
|
dirContentRefs = {};
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
timer !== null && clearTimeout(timer);
|
|
});
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { reset });
|
|
|
|
return () => {
|
|
const
|
|
content = [],
|
|
slotsList = {
|
|
left: slots[ langDir.value.right ] !== void 0,
|
|
right: slots[ langDir.value.left ] !== void 0,
|
|
up: slots.bottom !== void 0,
|
|
down: slots.top !== void 0
|
|
},
|
|
dirs = Object.keys(slotsList).filter(key => slotsList[ key ] === true);
|
|
|
|
slotsDef.forEach(slotName => {
|
|
const dir = slotName[ 0 ];
|
|
|
|
if (slots[ dir ] !== void 0) {
|
|
content.push(
|
|
vue.h('div', {
|
|
ref: el => { dirRefs[ dir ] = el; },
|
|
class: `q-slide-item__${ dir } absolute-full row no-wrap items-${ slotName[ 1 ] } justify-${ slotName[ 2 ] }`
|
|
+ (props[ dir + 'Color' ] !== void 0 ? ` bg-${ props[ dir + 'Color' ] }` : '')
|
|
}, [
|
|
vue.h('div', { ref: el => { dirContentRefs[ dir ] = el; } }, slots[ dir ]())
|
|
])
|
|
);
|
|
}
|
|
});
|
|
|
|
const node = vue.h('div', {
|
|
key: `${ dirs.length === 0 ? 'only-' : '' } content`,
|
|
ref: contentRef,
|
|
class: 'q-slide-item__content'
|
|
}, hSlot(slots.default));
|
|
|
|
if (dirs.length === 0) {
|
|
content.push(node);
|
|
}
|
|
else {
|
|
content.push(
|
|
vue.withDirectives(node, getCacheWithFn('dir#' + dirs.join(''), () => {
|
|
const modifiers = {
|
|
prevent: true,
|
|
stop: true,
|
|
mouse: true
|
|
};
|
|
|
|
dirs.forEach(dir => {
|
|
modifiers[ dir ] = true;
|
|
});
|
|
|
|
return [ [
|
|
TouchPan,
|
|
onPan,
|
|
void 0,
|
|
modifiers
|
|
] ]
|
|
}))
|
|
);
|
|
}
|
|
|
|
return vue.h('div', { class: classes.value }, content)
|
|
}
|
|
}
|
|
});
|
|
|
|
const space = vue.h('div', { class: 'q-space' });
|
|
|
|
var QSpace = createComponent({
|
|
name: 'QSpace',
|
|
|
|
setup () {
|
|
return () => space
|
|
}
|
|
});
|
|
|
|
const svg$l = [
|
|
vue.h('g', {
|
|
transform: 'matrix(1 0 0 -1 0 80)'
|
|
}, [
|
|
vue.h('rect', {
|
|
width: '10',
|
|
height: '20',
|
|
rx: '3'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0s',
|
|
dur: '4.3s',
|
|
values: '20;45;57;80;64;32;66;45;64;23;66;13;64;56;34;34;2;23;76;79;20',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '15',
|
|
width: '10',
|
|
height: '80',
|
|
rx: '3'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0s',
|
|
dur: '2s',
|
|
values: '80;55;33;5;75;23;73;33;12;14;60;80',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '30',
|
|
width: '10',
|
|
height: '50',
|
|
rx: '3'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0s',
|
|
dur: '1.4s',
|
|
values: '50;34;78;23;56;23;34;76;80;54;21;50',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '45',
|
|
width: '10',
|
|
height: '30',
|
|
rx: '3'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0s',
|
|
dur: '2s',
|
|
values: '30;45;13;80;56;72;45;76;34;23;67;30',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerAudio = createComponent({
|
|
name: 'QSpinnerAudio',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
fill: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 55 80',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$l)
|
|
}
|
|
});
|
|
|
|
const svg$k = [
|
|
vue.h('g', {
|
|
transform: 'translate(1 1)',
|
|
'stroke-width': '2',
|
|
fill: 'none',
|
|
'fill-rule': 'evenodd'
|
|
}, [
|
|
vue.h('circle', {
|
|
cx: '5',
|
|
cy: '50',
|
|
r: '5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'cy',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
values: '50;5;50;50',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'cx',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
values: '5;27;49;5',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '27',
|
|
cy: '5',
|
|
r: '5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'cy',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
from: '5',
|
|
to: '5',
|
|
values: '5;50;50;5',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'cx',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
from: '27',
|
|
to: '27',
|
|
values: '27;49;5;27',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '49',
|
|
cy: '50',
|
|
r: '5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'cy',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
values: '50;50;5;50',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'cx',
|
|
from: '49',
|
|
to: '49',
|
|
begin: '0s',
|
|
dur: '2.2s',
|
|
values: '49;5;27;49',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerBall = createComponent({
|
|
name: 'QSpinnerBall',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
stroke: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 57 57',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$k)
|
|
}
|
|
});
|
|
|
|
const svg$j = [
|
|
vue.h('rect', {
|
|
y: '10',
|
|
width: '15',
|
|
height: '120',
|
|
rx: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0.5s',
|
|
dur: '1s',
|
|
values: '120;110;100;90;80;70;60;50;40;140;120',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
begin: '0.5s',
|
|
dur: '1s',
|
|
values: '10;15;20;25;30;35;40;45;50;0;10',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '30',
|
|
y: '10',
|
|
width: '15',
|
|
height: '120',
|
|
rx: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0.25s',
|
|
dur: '1s',
|
|
values: '120;110;100;90;80;70;60;50;40;140;120',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
begin: '0.25s',
|
|
dur: '1s',
|
|
values: '10;15;20;25;30;35;40;45;50;0;10',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '60',
|
|
width: '15',
|
|
height: '140',
|
|
rx: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0s',
|
|
dur: '1s',
|
|
values: '120;110;100;90;80;70;60;50;40;140;120',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
begin: '0s',
|
|
dur: '1s',
|
|
values: '10;15;20;25;30;35;40;45;50;0;10',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '90',
|
|
y: '10',
|
|
width: '15',
|
|
height: '120',
|
|
rx: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0.25s',
|
|
dur: '1s',
|
|
values: '120;110;100;90;80;70;60;50;40;140;120',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
begin: '0.25s',
|
|
dur: '1s',
|
|
values: '10;15;20;25;30;35;40;45;50;0;10',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '120',
|
|
y: '10',
|
|
width: '15',
|
|
height: '120',
|
|
rx: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
begin: '0.5s',
|
|
dur: '1s',
|
|
values: '120;110;100;90;80;70;60;50;40;140;120',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
begin: '0.5s',
|
|
dur: '1s',
|
|
values: '10;15;20;25;30;35;40;45;50;0;10',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerBars = createComponent({
|
|
name: 'QSpinnerBars',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
fill: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 135 140',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$j)
|
|
}
|
|
});
|
|
|
|
const svg$i = [
|
|
vue.h('rect', {
|
|
x: '25',
|
|
y: '25',
|
|
width: '50',
|
|
height: '50',
|
|
fill: 'none',
|
|
'stroke-width': '4',
|
|
stroke: 'currentColor'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
id: 'spinnerBox',
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '180 50 50',
|
|
dur: '0.5s',
|
|
begin: 'rectBox.end'
|
|
})
|
|
]),
|
|
vue.h('rect', {
|
|
x: '27',
|
|
y: '27',
|
|
width: '46',
|
|
height: '50',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animate', {
|
|
id: 'rectBox',
|
|
attributeName: 'height',
|
|
begin: '0s;spinnerBox.end',
|
|
dur: '1.3s',
|
|
from: '50',
|
|
to: '0',
|
|
fill: 'freeze'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerBox = createComponent({
|
|
name: 'QSpinnerBox',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$i)
|
|
}
|
|
});
|
|
|
|
const svg$h = [
|
|
vue.h('circle', {
|
|
cx: '50',
|
|
cy: '50',
|
|
r: '48',
|
|
fill: 'none',
|
|
'stroke-width': '4',
|
|
'stroke-miterlimit': '10',
|
|
stroke: 'currentColor'
|
|
}),
|
|
vue.h('line', {
|
|
'stroke-linecap': 'round',
|
|
'stroke-width': '4',
|
|
'stroke-miterlimit': '10',
|
|
stroke: 'currentColor',
|
|
x1: '50',
|
|
y1: '50',
|
|
x2: '85',
|
|
y2: '50.5'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '2s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
'stroke-linecap': 'round',
|
|
'stroke-width': '4',
|
|
'stroke-miterlimit': '10',
|
|
stroke: 'currentColor',
|
|
x1: '50',
|
|
y1: '50',
|
|
x2: '49.5',
|
|
y2: '74'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '15s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerClock = createComponent({
|
|
name: 'QSpinnerClock',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$h)
|
|
}
|
|
});
|
|
|
|
const svg$g = [
|
|
vue.h('rect', {
|
|
x: '0',
|
|
y: '0',
|
|
width: '100',
|
|
height: '100',
|
|
fill: 'none'
|
|
}),
|
|
vue.h('path', {
|
|
d: 'M78,19H22c-6.6,0-12,5.4-12,12v31c0,6.6,5.4,12,12,12h37.2c0.4,3,1.8,5.6,3.7,7.6c2.4,2.5,5.1,4.1,9.1,4 c-1.4-2.1-2-7.2-2-10.3c0-0.4,0-0.8,0-1.3h8c6.6,0,12-5.4,12-12V31C90,24.4,84.6,19,78,19z',
|
|
fill: 'currentColor'
|
|
}),
|
|
vue.h('circle', {
|
|
cx: '30',
|
|
cy: '47',
|
|
r: '5',
|
|
fill: '#fff'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
values: '0;1;1',
|
|
keyTimes: '0;0.2;1',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '50',
|
|
cy: '47',
|
|
r: '5',
|
|
fill: '#fff'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
values: '0;0;1;1',
|
|
keyTimes: '0;0.2;0.4;1',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '70',
|
|
cy: '47',
|
|
r: '5',
|
|
fill: '#fff'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
values: '0;0;1;1',
|
|
keyTimes: '0;0.4;0.6;1',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerComment = createComponent({
|
|
name: 'QSpinnerComment',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid'
|
|
}, svg$g)
|
|
}
|
|
});
|
|
|
|
const svg$f = [
|
|
vue.h('rect', {
|
|
x: '0',
|
|
y: '0',
|
|
width: '100',
|
|
height: '100',
|
|
fill: 'none'
|
|
}),
|
|
vue.h('g', {
|
|
transform: 'translate(25 25)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-20',
|
|
y: '-20',
|
|
width: '40',
|
|
height: '40',
|
|
fill: 'currentColor',
|
|
opacity: '0.9'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '1.5',
|
|
to: '1',
|
|
repeatCount: 'indefinite',
|
|
begin: '0s',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.2 0.8 0.2 0.8',
|
|
keyTimes: '0;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(75 25)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-20',
|
|
y: '-20',
|
|
width: '40',
|
|
height: '40',
|
|
fill: 'currentColor',
|
|
opacity: '0.8'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '1.5',
|
|
to: '1',
|
|
repeatCount: 'indefinite',
|
|
begin: '0.1s',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.2 0.8 0.2 0.8',
|
|
keyTimes: '0;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(25 75)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-20',
|
|
y: '-20',
|
|
width: '40',
|
|
height: '40',
|
|
fill: 'currentColor',
|
|
opacity: '0.7'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '1.5',
|
|
to: '1',
|
|
repeatCount: 'indefinite',
|
|
begin: '0.3s',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.2 0.8 0.2 0.8',
|
|
keyTimes: '0;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(75 75)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-20',
|
|
y: '-20',
|
|
width: '40',
|
|
height: '40',
|
|
fill: 'currentColor',
|
|
opacity: '0.6'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '1.5',
|
|
to: '1',
|
|
repeatCount: 'indefinite',
|
|
begin: '0.2s',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.2 0.8 0.2 0.8',
|
|
keyTimes: '0;1'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerCube = createComponent({
|
|
name: 'QSpinnerCube',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid'
|
|
}, svg$f)
|
|
}
|
|
});
|
|
|
|
const svg$e = [
|
|
vue.h('circle', {
|
|
cx: '15',
|
|
cy: '15',
|
|
r: '15'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
from: '15',
|
|
to: '15',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '15;9;15',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
from: '1',
|
|
to: '1',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '1;.5;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '60',
|
|
cy: '15',
|
|
r: '9',
|
|
'fill-opacity': '.3'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
from: '9',
|
|
to: '9',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '9;15;9',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
from: '.5',
|
|
to: '.5',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '.5;1;.5',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '105',
|
|
cy: '15',
|
|
r: '15'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
from: '15',
|
|
to: '15',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '15;9;15',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
from: '1',
|
|
to: '1',
|
|
begin: '0s',
|
|
dur: '0.8s',
|
|
values: '1;.5;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerDots = createComponent({
|
|
name: 'QSpinnerDots',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
fill: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 120 30',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$e)
|
|
}
|
|
});
|
|
|
|
const svg$d = [
|
|
vue.h('g', {
|
|
transform: 'translate(20 50)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-10',
|
|
y: '-30',
|
|
width: '20',
|
|
height: '60',
|
|
fill: 'currentColor',
|
|
opacity: '0.6'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '2',
|
|
to: '1',
|
|
begin: '0s',
|
|
repeatCount: 'indefinite',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.1 0.9 0.4 1',
|
|
keyTimes: '0;1',
|
|
values: '2;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(50 50)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-10',
|
|
y: '-30',
|
|
width: '20',
|
|
height: '60',
|
|
fill: 'currentColor',
|
|
opacity: '0.8'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '2',
|
|
to: '1',
|
|
begin: '0.1s',
|
|
repeatCount: 'indefinite',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.1 0.9 0.4 1',
|
|
keyTimes: '0;1',
|
|
values: '2;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(80 50)'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '-10',
|
|
y: '-30',
|
|
width: '20',
|
|
height: '60',
|
|
fill: 'currentColor',
|
|
opacity: '0.9'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'scale',
|
|
from: '2',
|
|
to: '1',
|
|
begin: '0.2s',
|
|
repeatCount: 'indefinite',
|
|
dur: '1s',
|
|
calcMode: 'spline',
|
|
keySplines: '0.1 0.9 0.4 1',
|
|
keyTimes: '0;1',
|
|
values: '2;1'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerFacebook = createComponent({
|
|
name: 'QSpinnerFacebook',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
preserveAspectRatio: 'xMidYMid'
|
|
}, svg$d)
|
|
}
|
|
});
|
|
|
|
const svg$c = [
|
|
vue.h('g', {
|
|
transform: 'translate(-20,-20)'
|
|
}, [
|
|
vue.h('path', {
|
|
d: 'M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '90 50 50',
|
|
to: '0 50 50',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(20,20) rotate(15 50 50)'
|
|
}, [
|
|
vue.h('path', {
|
|
d: 'M79.9,52.6C80,51.8,80,50.9,80,50s0-1.8-0.1-2.6l-5.1-0.4c-0.3-2.4-0.9-4.6-1.8-6.7l4.2-2.9c-0.7-1.6-1.6-3.1-2.6-4.5 L70,35c-1.4-1.9-3.1-3.5-4.9-4.9l2.2-4.6c-1.4-1-2.9-1.9-4.5-2.6L59.8,27c-2.1-0.9-4.4-1.5-6.7-1.8l-0.4-5.1C51.8,20,50.9,20,50,20 s-1.8,0-2.6,0.1l-0.4,5.1c-2.4,0.3-4.6,0.9-6.7,1.8l-2.9-4.1c-1.6,0.7-3.1,1.6-4.5,2.6l2.1,4.6c-1.9,1.4-3.5,3.1-5,4.9l-4.5-2.1 c-1,1.4-1.9,2.9-2.6,4.5l4.1,2.9c-0.9,2.1-1.5,4.4-1.8,6.8l-5,0.4C20,48.2,20,49.1,20,50s0,1.8,0.1,2.6l5,0.4 c0.3,2.4,0.9,4.7,1.8,6.8l-4.1,2.9c0.7,1.6,1.6,3.1,2.6,4.5l4.5-2.1c1.4,1.9,3.1,3.5,5,4.9l-2.1,4.6c1.4,1,2.9,1.9,4.5,2.6l2.9-4.1 c2.1,0.9,4.4,1.5,6.7,1.8l0.4,5.1C48.2,80,49.1,80,50,80s1.8,0,2.6-0.1l0.4-5.1c2.3-0.3,4.6-0.9,6.7-1.8l2.9,4.2 c1.6-0.7,3.1-1.6,4.5-2.6L65,69.9c1.9-1.4,3.5-3,4.9-4.9l4.6,2.2c1-1.4,1.9-2.9,2.6-4.5L73,59.8c0.9-2.1,1.5-4.4,1.8-6.7L79.9,52.6 z M50,65c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15s15,6.7,15,15C65,58.3,58.3,65,50,65z',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '90 50 50',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerGears = createComponent({
|
|
name: 'QSpinnerGears',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$c)
|
|
}
|
|
});
|
|
|
|
const svg$b = [
|
|
vue.h('circle', {
|
|
cx: '12.5',
|
|
cy: '12.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '0s',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '12.5',
|
|
cy: '52.5',
|
|
r: '12.5',
|
|
'fill-opacity': '.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '100ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '52.5',
|
|
cy: '12.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '300ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '52.5',
|
|
cy: '52.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '600ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '92.5',
|
|
cy: '12.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '800ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '92.5',
|
|
cy: '52.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '400ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '12.5',
|
|
cy: '92.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '700ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '52.5',
|
|
cy: '92.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '500ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '92.5',
|
|
cy: '92.5',
|
|
r: '12.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '200ms',
|
|
dur: '1s',
|
|
values: '1;.2;1',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerGrid = createComponent({
|
|
name: 'QSpinnerGrid',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
fill: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 105 105',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$b)
|
|
}
|
|
});
|
|
|
|
const svg$a = [
|
|
vue.h('path', {
|
|
d: 'M30.262 57.02L7.195 40.723c-5.84-3.976-7.56-12.06-3.842-18.063 3.715-6 11.467-7.65 17.306-3.68l4.52 3.76 2.6-5.274c3.716-6.002 11.47-7.65 17.304-3.68 5.84 3.97 7.56 12.054 3.842 18.062L34.49 56.118c-.897 1.512-2.793 1.915-4.228.9z',
|
|
'fill-opacity': '.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '0s',
|
|
dur: '1.4s',
|
|
values: '0.5;1;0.5',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M105.512 56.12l-14.44-24.272c-3.716-6.008-1.996-14.093 3.843-18.062 5.835-3.97 13.588-2.322 17.306 3.68l2.6 5.274 4.52-3.76c5.84-3.97 13.593-2.32 17.308 3.68 3.718 6.003 1.998 14.088-3.842 18.064L109.74 57.02c-1.434 1.014-3.33.61-4.228-.9z',
|
|
'fill-opacity': '.5'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'fill-opacity',
|
|
begin: '0.7s',
|
|
dur: '1.4s',
|
|
values: '0.5;1;0.5',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M67.408 57.834l-23.01-24.98c-5.864-6.15-5.864-16.108 0-22.248 5.86-6.14 15.37-6.14 21.234 0L70 16.168l4.368-5.562c5.863-6.14 15.375-6.14 21.235 0 5.863 6.14 5.863 16.098 0 22.247l-23.007 24.98c-1.43 1.556-3.757 1.556-5.188 0z'
|
|
})
|
|
];
|
|
|
|
var QSpinnerHearts = createComponent({
|
|
name: 'QSpinnerHearts',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
fill: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 140 64',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$a)
|
|
}
|
|
});
|
|
|
|
const svg$9 = [
|
|
vue.h('g', [
|
|
vue.h('path', {
|
|
fill: 'none',
|
|
stroke: 'currentColor',
|
|
'stroke-width': '5',
|
|
'stroke-miterlimit': '10',
|
|
d: 'M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z'
|
|
}),
|
|
vue.h('clipPath', {
|
|
id: 'uil-hourglass-clip1'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '15',
|
|
y: '20',
|
|
width: '70',
|
|
height: '25'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
from: '25',
|
|
to: '0',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite',
|
|
values: '25;0;0',
|
|
keyTimes: '0;0.5;1'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
from: '20',
|
|
to: '45',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite',
|
|
values: '20;45;45',
|
|
keyTimes: '0;0.5;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('clipPath', {
|
|
id: 'uil-hourglass-clip2'
|
|
}, [
|
|
vue.h('rect', {
|
|
x: '15',
|
|
y: '55',
|
|
width: '70',
|
|
height: '25'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'height',
|
|
from: '0',
|
|
to: '25',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite',
|
|
values: '0;25;25',
|
|
keyTimes: '0;0.5;1'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'y',
|
|
from: '80',
|
|
to: '55',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite',
|
|
values: '80;55;55',
|
|
keyTimes: '0;0.5;1'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z',
|
|
'clip-path': 'url(#uil-hourglass-clip1)',
|
|
fill: 'currentColor'
|
|
}),
|
|
vue.h('path', {
|
|
d: 'M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z',
|
|
'clip-path': 'url(#uil-hourglass-clip2)',
|
|
fill: 'currentColor'
|
|
}),
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '180 50 50',
|
|
repeatCount: 'indefinite',
|
|
dur: '1s',
|
|
values: '0 50 50;0 50 50;180 50 50',
|
|
keyTimes: '0;0.7;1'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerHourglass = createComponent({
|
|
name: 'QSpinnerHourglass',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$9)
|
|
}
|
|
});
|
|
|
|
const svg$8 = [
|
|
vue.h('path', {
|
|
d: 'M24.3,30C11.4,30,5,43.3,5,50s6.4,20,19.3,20c19.3,0,32.1-40,51.4-40C88.6,30,95,43.3,95,50s-6.4,20-19.3,20C56.4,70,43.6,30,24.3,30z',
|
|
fill: 'none',
|
|
stroke: 'currentColor',
|
|
'stroke-width': '8',
|
|
'stroke-dasharray': '10.691205342610678 10.691205342610678',
|
|
'stroke-dashoffset': '0'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-dashoffset',
|
|
from: '0',
|
|
to: '21.382410685221355',
|
|
begin: '0',
|
|
dur: '2s',
|
|
repeatCount: 'indefinite',
|
|
fill: 'freeze'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerInfinity = createComponent({
|
|
name: 'QSpinnerInfinity',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid'
|
|
}, svg$8)
|
|
}
|
|
});
|
|
|
|
const svg$7 = [
|
|
vue.h('g', {
|
|
'stroke-width': '4',
|
|
'stroke-linecap': 'round'
|
|
}, [
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(180)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0;1',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(210)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '0;1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(240)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.1;0;1;.85;.7;.65;.55;.45;.35;.25;.15;.1',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(270)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.15;.1;0;1;.85;.7;.65;.55;.45;.35;.25;.15',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(300)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.25;.15;.1;0;1;.85;.7;.65;.55;.45;.35;.25',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(330)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.35;.25;.15;.1;0;1;.85;.7;.65;.55;.45;.35',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(0)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.45;.35;.25;.15;.1;0;1;.85;.7;.65;.55;.45',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(30)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.55;.45;.35;.25;.15;.1;0;1;.85;.7;.65;.55',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(60)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.65;.55;.45;.35;.25;.15;.1;0;1;.85;.7;.65',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(90)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.7;.65;.55;.45;.35;.25;.15;.1;0;1;.85;.7',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(120)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '.85;.7;.65;.55;.45;.35;.25;.15;.1;0;1;.85',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('line', {
|
|
y1: '17',
|
|
y2: '29',
|
|
transform: 'translate(32,32) rotate(150)'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
dur: '750ms',
|
|
values: '1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0;1',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerIos = createComponent({
|
|
name: 'QSpinnerIos',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
stroke: 'currentColor',
|
|
fill: 'currentColor',
|
|
viewBox: '0 0 64 64'
|
|
}, svg$7)
|
|
}
|
|
});
|
|
|
|
const svg$6 = [
|
|
vue.h('circle', {
|
|
cx: '50',
|
|
cy: '50',
|
|
r: '44',
|
|
fill: 'none',
|
|
'stroke-width': '4',
|
|
'stroke-opacity': '.5',
|
|
stroke: 'currentColor'
|
|
}),
|
|
vue.h('circle', {
|
|
cx: '8',
|
|
cy: '54',
|
|
r: '6',
|
|
fill: 'currentColor',
|
|
'stroke-width': '3',
|
|
stroke: 'currentColor'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 48',
|
|
to: '360 50 52',
|
|
dur: '2s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerOrbit = createComponent({
|
|
name: 'QSpinnerOrbit',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$6)
|
|
}
|
|
});
|
|
|
|
const svg$5 = [
|
|
vue.h('g', {
|
|
transform: 'translate(1 1)',
|
|
'stroke-width': '2',
|
|
fill: 'none',
|
|
'fill-rule': 'evenodd'
|
|
}, [
|
|
vue.h('circle', {
|
|
'stroke-opacity': '.5',
|
|
cx: '18',
|
|
cy: '18',
|
|
r: '18'
|
|
}),
|
|
vue.h('path', {
|
|
d: 'M36 18c0-9.94-8.06-18-18-18'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 18 18',
|
|
to: '360 18 18',
|
|
dur: '1s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerOval = createComponent({
|
|
name: 'QSpinnerOval',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
stroke: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 38 38',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$5)
|
|
}
|
|
});
|
|
|
|
const svg$4 = [
|
|
vue.h('path', {
|
|
d: 'M0 50A50 50 0 0 1 50 0L50 50L0 50',
|
|
fill: 'currentColor',
|
|
opacity: '0.5'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '0.8s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M50 0A50 50 0 0 1 100 50L50 50L50 0',
|
|
fill: 'currentColor',
|
|
opacity: '0.5'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '1.6s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M100 50A50 50 0 0 1 50 100L50 50L100 50',
|
|
fill: 'currentColor',
|
|
opacity: '0.5'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '2.4s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M50 100A50 50 0 0 1 0 50L50 50L50 100',
|
|
fill: 'currentColor',
|
|
opacity: '0.5'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 50 50',
|
|
to: '360 50 50',
|
|
dur: '3.2s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
];
|
|
|
|
var QSpinnerPie = createComponent({
|
|
name: 'QSpinnerPie',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$4)
|
|
}
|
|
});
|
|
|
|
const svg$3 = [
|
|
vue.h('g', {
|
|
fill: 'none',
|
|
'fill-rule': 'evenodd',
|
|
'stroke-width': '2'
|
|
}, [
|
|
vue.h('circle', {
|
|
cx: '22',
|
|
cy: '22',
|
|
r: '1'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
begin: '0s',
|
|
dur: '1.8s',
|
|
values: '1; 20',
|
|
calcMode: 'spline',
|
|
keyTimes: '0; 1',
|
|
keySplines: '0.165, 0.84, 0.44, 1',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
begin: '0s',
|
|
dur: '1.8s',
|
|
values: '1; 0',
|
|
calcMode: 'spline',
|
|
keyTimes: '0; 1',
|
|
keySplines: '0.3, 0.61, 0.355, 1',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '22',
|
|
cy: '22',
|
|
r: '1'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
begin: '-0.9s',
|
|
dur: '1.8s',
|
|
values: '1; 20',
|
|
calcMode: 'spline',
|
|
keyTimes: '0; 1',
|
|
keySplines: '0.165, 0.84, 0.44, 1',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
begin: '-0.9s',
|
|
dur: '1.8s',
|
|
values: '1; 0',
|
|
calcMode: 'spline',
|
|
keyTimes: '0; 1',
|
|
keySplines: '0.3, 0.61, 0.355, 1',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerPuff = createComponent({
|
|
name: 'QSpinnerPuff',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
stroke: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 44 44',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$3)
|
|
}
|
|
});
|
|
|
|
const svg$2 = [
|
|
vue.h('g', {
|
|
transform: 'scale(0.55)'
|
|
}, [
|
|
vue.h('circle', {
|
|
cx: '30',
|
|
cy: '150',
|
|
r: '30',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
dur: '1s',
|
|
begin: '0',
|
|
repeatCount: 'indefinite',
|
|
keyTimes: '0;0.5;1',
|
|
values: '0;1;1'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M90,150h30c0-49.7-40.3-90-90-90v30C63.1,90,90,116.9,90,150z',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
dur: '1s',
|
|
begin: '0.1',
|
|
repeatCount: 'indefinite',
|
|
keyTimes: '0;0.5;1',
|
|
values: '0;1;1'
|
|
})
|
|
]),
|
|
vue.h('path', {
|
|
d: 'M150,150h30C180,67.2,112.8,0,30,0v30C96.3,30,150,83.7,150,150z',
|
|
fill: 'currentColor'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'opacity',
|
|
from: '0',
|
|
to: '1',
|
|
dur: '1s',
|
|
begin: '0.2',
|
|
repeatCount: 'indefinite',
|
|
keyTimes: '0;0.5;1',
|
|
values: '0;1;1'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerRadio = createComponent({
|
|
name: 'QSpinnerRadio',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 100 100',
|
|
preserveAspectRatio: 'xMidYMid',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$2)
|
|
}
|
|
});
|
|
|
|
const svg$1 = [
|
|
vue.h('g', {
|
|
fill: 'none',
|
|
'fill-rule': 'evenodd',
|
|
transform: 'translate(1 1)',
|
|
'stroke-width': '2'
|
|
}, [
|
|
vue.h('circle', {
|
|
cx: '22',
|
|
cy: '22',
|
|
r: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
begin: '1.5s',
|
|
dur: '3s',
|
|
values: '6;22',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
begin: '1.5s',
|
|
dur: '3s',
|
|
values: '1;0',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-width',
|
|
begin: '1.5s',
|
|
dur: '3s',
|
|
values: '2;0',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '22',
|
|
cy: '22',
|
|
r: '6'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
begin: '3s',
|
|
dur: '3s',
|
|
values: '6;22',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-opacity',
|
|
begin: '3s',
|
|
dur: '3s',
|
|
values: '1;0',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
}),
|
|
vue.h('animate', {
|
|
attributeName: 'stroke-width',
|
|
begin: '3s',
|
|
dur: '3s',
|
|
values: '2;0',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
cx: '22',
|
|
cy: '22',
|
|
r: '8'
|
|
}, [
|
|
vue.h('animate', {
|
|
attributeName: 'r',
|
|
begin: '0s',
|
|
dur: '1.5s',
|
|
values: '6;1;2;3;4;5;6',
|
|
calcMode: 'linear',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerRings = createComponent({
|
|
name: 'QSpinnerRings',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
stroke: 'currentColor',
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 45 45',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg$1)
|
|
}
|
|
});
|
|
|
|
const svg = [
|
|
vue.h('defs', [
|
|
vue.h('linearGradient', {
|
|
x1: '8.042%',
|
|
y1: '0%',
|
|
x2: '65.682%',
|
|
y2: '23.865%',
|
|
id: 'a'
|
|
}, [
|
|
vue.h('stop', {
|
|
'stop-color': 'currentColor',
|
|
'stop-opacity': '0',
|
|
offset: '0%'
|
|
}),
|
|
vue.h('stop', {
|
|
'stop-color': 'currentColor',
|
|
'stop-opacity': '.631',
|
|
offset: '63.146%'
|
|
}),
|
|
vue.h('stop', {
|
|
'stop-color': 'currentColor',
|
|
offset: '100%'
|
|
})
|
|
])
|
|
]),
|
|
vue.h('g', {
|
|
transform: 'translate(1 1)',
|
|
fill: 'none',
|
|
'fill-rule': 'evenodd'
|
|
}, [
|
|
vue.h('path', {
|
|
d: 'M36 18c0-9.94-8.06-18-18-18',
|
|
stroke: 'url(#a)',
|
|
'stroke-width': '2'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 18 18',
|
|
to: '360 18 18',
|
|
dur: '0.9s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
]),
|
|
vue.h('circle', {
|
|
fill: 'currentColor',
|
|
cx: '36',
|
|
cy: '18',
|
|
r: '1'
|
|
}, [
|
|
vue.h('animateTransform', {
|
|
attributeName: 'transform',
|
|
type: 'rotate',
|
|
from: '0 18 18',
|
|
to: '360 18 18',
|
|
dur: '0.9s',
|
|
repeatCount: 'indefinite'
|
|
})
|
|
])
|
|
])
|
|
];
|
|
|
|
var QSpinnerTail = createComponent({
|
|
name: 'QSpinnerTail',
|
|
|
|
props: useSpinnerProps,
|
|
|
|
setup (props) {
|
|
const { cSize, classes } = useSpinner(props);
|
|
|
|
return () => vue.h('svg', {
|
|
class: classes.value,
|
|
width: cSize.value,
|
|
height: cSize.value,
|
|
viewBox: '0 0 38 38',
|
|
xmlns: 'http://www.w3.org/2000/svg'
|
|
}, svg)
|
|
}
|
|
});
|
|
|
|
var QSplitter = createComponent({
|
|
name: 'QSplitter',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
modelValue: {
|
|
type: Number,
|
|
required: true
|
|
},
|
|
reverse: Boolean,
|
|
unit: {
|
|
type: String,
|
|
default: '%',
|
|
validator: v => [ '%', 'px' ].includes(v)
|
|
},
|
|
|
|
limits: {
|
|
type: Array,
|
|
validator: v => {
|
|
if (v.length !== 2) return false
|
|
if (typeof v[ 0 ] !== 'number' || typeof v[ 1 ] !== 'number') return false
|
|
return v[ 0 ] >= 0 && v[ 0 ] <= v[ 1 ]
|
|
}
|
|
},
|
|
|
|
emitImmediately: Boolean,
|
|
|
|
horizontal: Boolean,
|
|
disable: Boolean,
|
|
|
|
beforeClass: [ Array, String, Object ],
|
|
afterClass: [ Array, String, Object ],
|
|
|
|
separatorClass: [ Array, String, Object ],
|
|
separatorStyle: [ Array, String, Object ]
|
|
},
|
|
|
|
emits: [ 'update:modelValue' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, $q);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const sideRefs = {
|
|
before: vue.ref(null),
|
|
after: vue.ref(null)
|
|
};
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-splitter no-wrap '
|
|
+ `${ props.horizontal === true ? 'q-splitter--horizontal column' : 'q-splitter--vertical row' }`
|
|
+ ` q-splitter--${ props.disable === true ? 'disabled' : 'workable' }`
|
|
+ (isDark.value === true ? ' q-splitter--dark' : '')
|
|
);
|
|
|
|
const propName = vue.computed(() => (props.horizontal === true ? 'height' : 'width'));
|
|
const side = vue.computed(() => (props.reverse !== true ? 'before' : 'after'));
|
|
|
|
const computedLimits = vue.computed(() => (
|
|
props.limits !== void 0
|
|
? props.limits
|
|
: (props.unit === '%' ? [ 10, 90 ] : [ 50, Infinity ])
|
|
));
|
|
|
|
function getCSSValue (value) {
|
|
return (props.unit === '%' ? value : Math.round(value)) + props.unit
|
|
}
|
|
|
|
const styles = vue.computed(() => ({
|
|
[ side.value ]: {
|
|
[ propName.value ]: getCSSValue(props.modelValue)
|
|
}
|
|
}));
|
|
|
|
let __dir, __maxValue, __value, __multiplier, __normalized;
|
|
|
|
function pan (evt) {
|
|
if (evt.isFirst === true) {
|
|
const size = rootRef.value.getBoundingClientRect()[ propName.value ];
|
|
|
|
__dir = props.horizontal === true ? 'up' : 'left';
|
|
__maxValue = props.unit === '%' ? 100 : size;
|
|
__value = Math.min(__maxValue, computedLimits.value[ 1 ], Math.max(computedLimits.value[ 0 ], props.modelValue));
|
|
__multiplier = (props.reverse !== true ? 1 : -1)
|
|
* (props.horizontal === true ? 1 : ($q.lang.rtl === true ? -1 : 1))
|
|
* (props.unit === '%' ? (size === 0 ? 0 : 100 / size) : 1);
|
|
|
|
rootRef.value.classList.add('q-splitter--active');
|
|
return
|
|
}
|
|
|
|
if (evt.isFinal === true) {
|
|
if (__normalized !== props.modelValue) {
|
|
emit('update:modelValue', __normalized);
|
|
}
|
|
|
|
rootRef.value.classList.remove('q-splitter--active');
|
|
return
|
|
}
|
|
|
|
const val = __value
|
|
+ __multiplier
|
|
* (evt.direction === __dir ? -1 : 1)
|
|
* evt.distance[ props.horizontal === true ? 'y' : 'x' ];
|
|
|
|
__normalized = Math.min(__maxValue, computedLimits.value[ 1 ], Math.max(computedLimits.value[ 0 ], val));
|
|
|
|
sideRefs[ side.value ].value.style[ propName.value ] = getCSSValue(__normalized);
|
|
|
|
if (props.emitImmediately === true && props.modelValue !== __normalized) {
|
|
emit('update:modelValue', __normalized);
|
|
}
|
|
}
|
|
|
|
const sepDirective = vue.computed(() => {
|
|
// if props.disable !== true
|
|
return [ [
|
|
TouchPan,
|
|
pan,
|
|
void 0,
|
|
{
|
|
[ props.horizontal === true ? 'vertical' : 'horizontal' ]: true,
|
|
prevent: true,
|
|
stop: true,
|
|
mouse: true,
|
|
mouseAllDir: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
function normalize (val, limits) {
|
|
if (val < limits[ 0 ]) {
|
|
emit('update:modelValue', limits[ 0 ]);
|
|
}
|
|
else if (val > limits[ 1 ]) {
|
|
emit('update:modelValue', limits[ 1 ]);
|
|
}
|
|
}
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
normalize(v, computedLimits.value);
|
|
});
|
|
|
|
vue.watch(() => props.limits, () => {
|
|
vue.nextTick(() => {
|
|
normalize(props.modelValue, computedLimits.value);
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
const child = [
|
|
vue.h('div', {
|
|
ref: sideRefs.before,
|
|
class: [
|
|
'q-splitter__panel q-splitter__before' + (props.reverse === true ? ' col' : ''),
|
|
props.beforeClass
|
|
],
|
|
style: styles.value.before
|
|
}, hSlot(slots.before)),
|
|
|
|
vue.h('div', {
|
|
class: [
|
|
'q-splitter__separator',
|
|
props.separatorClass
|
|
],
|
|
style: props.separatorStyle,
|
|
'aria-disabled': props.disable === true ? 'true' : void 0
|
|
}, [
|
|
hDir(
|
|
'div',
|
|
{ class: 'q-splitter__separator-area absolute-full' },
|
|
hSlot(slots.separator),
|
|
'sep',
|
|
props.disable !== true,
|
|
() => sepDirective.value
|
|
)
|
|
]),
|
|
|
|
vue.h('div', {
|
|
ref: sideRefs.after,
|
|
class: [
|
|
'q-splitter__panel q-splitter__after' + (props.reverse === true ? '' : ' col'),
|
|
props.afterClass
|
|
],
|
|
style: styles.value.after
|
|
}, hSlot(slots.after))
|
|
];
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
ref: rootRef
|
|
}, hMergeSlot(slots.default, child))
|
|
}
|
|
}
|
|
});
|
|
|
|
var StepHeader = createComponent({
|
|
name: 'StepHeader',
|
|
|
|
props: {
|
|
stepper: {},
|
|
step: {},
|
|
goToPanel: Function
|
|
},
|
|
|
|
setup (props, { attrs }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
const blurRef = vue.ref(null);
|
|
|
|
const isActive = vue.computed(() => props.stepper.modelValue === props.step.name);
|
|
|
|
const isDisable = vue.computed(() => {
|
|
const opt = props.step.disable;
|
|
return opt === true || opt === ''
|
|
});
|
|
|
|
const isError = vue.computed(() => {
|
|
const opt = props.step.error;
|
|
return opt === true || opt === ''
|
|
});
|
|
|
|
const isDone = vue.computed(() => {
|
|
const opt = props.step.done;
|
|
return isDisable.value === false && (opt === true || opt === '')
|
|
});
|
|
|
|
const headerNav = vue.computed(() => {
|
|
const
|
|
opt = props.step.headerNav,
|
|
nav = opt === true || opt === '' || opt === void 0;
|
|
|
|
return isDisable.value === false
|
|
&& props.stepper.headerNav
|
|
&& nav
|
|
});
|
|
|
|
const hasPrefix = vue.computed(() => {
|
|
return props.step.prefix
|
|
&& (isActive.value === false || props.stepper.activeIcon === 'none')
|
|
&& (isError.value === false || props.stepper.errorIcon === 'none')
|
|
&& (isDone.value === false || props.stepper.doneIcon === 'none')
|
|
});
|
|
|
|
const icon = vue.computed(() => {
|
|
const defaultIcon = props.step.icon || props.stepper.inactiveIcon;
|
|
|
|
if (isActive.value === true) {
|
|
const icon = props.step.activeIcon || props.stepper.activeIcon;
|
|
return icon === 'none'
|
|
? defaultIcon
|
|
: icon || $q.iconSet.stepper.active
|
|
}
|
|
|
|
if (isError.value === true) {
|
|
const icon = props.step.errorIcon || props.stepper.errorIcon;
|
|
return icon === 'none'
|
|
? defaultIcon
|
|
: icon || $q.iconSet.stepper.error
|
|
}
|
|
|
|
if (isDisable.value === false && isDone.value === true) {
|
|
const icon = props.step.doneIcon || props.stepper.doneIcon;
|
|
return icon === 'none'
|
|
? defaultIcon
|
|
: icon || $q.iconSet.stepper.done
|
|
}
|
|
|
|
return defaultIcon
|
|
});
|
|
|
|
const color = vue.computed(() => {
|
|
const errorColor = isError.value === true
|
|
? props.step.errorColor || props.stepper.errorColor
|
|
: void 0;
|
|
|
|
if (isActive.value === true) {
|
|
const color = props.step.activeColor || props.stepper.activeColor || props.step.color;
|
|
return color !== void 0
|
|
? color
|
|
: errorColor
|
|
}
|
|
if (errorColor !== void 0) {
|
|
return errorColor
|
|
}
|
|
if (isDisable.value === false && isDone.value === true) {
|
|
return props.step.doneColor || props.stepper.doneColor || props.step.color || props.stepper.inactiveColor
|
|
}
|
|
|
|
return props.step.color || props.stepper.inactiveColor
|
|
});
|
|
|
|
const classes = vue.computed(() => {
|
|
return 'q-stepper__tab col-grow flex items-center no-wrap relative-position'
|
|
+ (color.value !== void 0 ? ` text-${ color.value }` : '')
|
|
+ (isError.value === true
|
|
? ' q-stepper__tab--error q-stepper__tab--error-with-' + (hasPrefix.value === true ? 'prefix' : 'icon')
|
|
: '')
|
|
+ (isActive.value === true ? ' q-stepper__tab--active' : '')
|
|
+ (isDone.value === true ? ' q-stepper__tab--done' : '')
|
|
+ (headerNav.value === true ? ' q-stepper__tab--navigation q-focusable q-hoverable' : '')
|
|
+ (isDisable.value === true ? ' q-stepper__tab--disabled' : '')
|
|
});
|
|
|
|
const ripple = vue.computed(() => (
|
|
props.stepper.headerNav !== true
|
|
? false
|
|
: headerNav.value
|
|
));
|
|
|
|
function onActivate () {
|
|
blurRef.value !== null && blurRef.value.focus();
|
|
isActive.value === false && props.goToPanel(props.step.name);
|
|
}
|
|
|
|
function onKeyup (e) {
|
|
if (e.keyCode === 13 && isActive.value === false) {
|
|
props.goToPanel(props.step.name);
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
const data = { class: classes.value };
|
|
|
|
if (headerNav.value === true) {
|
|
data.onClick = onActivate;
|
|
data.onKeyup = onKeyup;
|
|
|
|
Object.assign(data,
|
|
isDisable.value === true
|
|
? { tabindex: -1, 'aria-disabled': 'true' }
|
|
: { tabindex: attrs.tabindex || 0 }
|
|
);
|
|
}
|
|
|
|
const child = [
|
|
vue.h('div', { class: 'q-focus-helper', tabindex: -1, ref: blurRef }),
|
|
|
|
vue.h('div', { class: 'q-stepper__dot row flex-center q-stepper__line relative-position' }, [
|
|
vue.h('span', { class: 'row flex-center' }, [
|
|
hasPrefix.value === true
|
|
? props.step.prefix
|
|
: vue.h(QIcon, { name: icon.value })
|
|
])
|
|
])
|
|
];
|
|
|
|
if (props.step.title !== void 0 && props.step.title !== null) {
|
|
const content = [
|
|
vue.h('div', { class: 'q-stepper__title' }, props.step.title)
|
|
];
|
|
|
|
if (props.step.caption !== void 0 && props.step.caption !== null) {
|
|
content.push(
|
|
vue.h('div', { class: 'q-stepper__caption' }, props.step.caption)
|
|
);
|
|
}
|
|
|
|
child.push(
|
|
vue.h('div', {
|
|
class: 'q-stepper__label q-stepper__line relative-position'
|
|
}, content)
|
|
);
|
|
}
|
|
|
|
return vue.withDirectives(
|
|
vue.h('div', data, child),
|
|
[ [ Ripple, ripple.value ] ]
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
function getStepWrapper (slots) {
|
|
return vue.h('div', {
|
|
class: 'q-stepper__step-content'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-stepper__step-inner'
|
|
}, hSlot(slots.default))
|
|
])
|
|
}
|
|
|
|
const PanelWrapper = {
|
|
setup (_, { slots }) {
|
|
return () => getStepWrapper(slots)
|
|
}
|
|
};
|
|
|
|
var QStep = createComponent({
|
|
name: 'QStep',
|
|
|
|
props: {
|
|
...usePanelChildProps,
|
|
|
|
icon: String,
|
|
color: String,
|
|
title: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
caption: String,
|
|
prefix: [ String, Number ],
|
|
|
|
doneIcon: String,
|
|
doneColor: String,
|
|
activeIcon: String,
|
|
activeColor: String,
|
|
errorIcon: String,
|
|
errorColor: String,
|
|
|
|
headerNav: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
done: Boolean,
|
|
error: Boolean,
|
|
|
|
onScroll: [ Function, Array ]
|
|
},
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy: { $q } } = vue.getCurrentInstance();
|
|
|
|
const $stepper = vue.inject(stepperKey, emptyRenderFn);
|
|
if ($stepper === emptyRenderFn) {
|
|
console.error('QStep needs to be a child of QStepper');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const { getCacheWithFn } = useCache();
|
|
|
|
const rootRef = vue.ref(null);
|
|
|
|
const isActive = vue.computed(() => $stepper.value.modelValue === props.name);
|
|
|
|
const scrollEvent = vue.computed(() => (
|
|
($q.platform.is.ios !== true && $q.platform.is.chrome === true)
|
|
|| isActive.value !== true
|
|
|| $stepper.value.vertical !== true
|
|
? {}
|
|
: {
|
|
onScroll (e) {
|
|
const { target } = e;
|
|
if (target.scrollTop > 0) {
|
|
target.scrollTop = 0;
|
|
}
|
|
props.onScroll !== void 0 && emit('scroll', e);
|
|
}
|
|
}
|
|
));
|
|
|
|
const contentKey = vue.computed(() => (
|
|
typeof props.name === 'string' || typeof props.name === 'number'
|
|
? props.name
|
|
: String(props.name)
|
|
));
|
|
|
|
function getStepContent () {
|
|
const vertical = $stepper.value.vertical;
|
|
|
|
if (vertical === true && $stepper.value.keepAlive === true) {
|
|
return vue.h(
|
|
vue.KeepAlive,
|
|
$stepper.value.keepAliveProps.value,
|
|
isActive.value === true
|
|
? [
|
|
vue.h(
|
|
$stepper.value.needsUniqueKeepAliveWrapper.value === true
|
|
? getCacheWithFn(contentKey.value, () => ({ ...PanelWrapper, name: contentKey.value }))
|
|
: PanelWrapper,
|
|
{ key: contentKey.value },
|
|
slots.default
|
|
)
|
|
]
|
|
: void 0
|
|
)
|
|
}
|
|
|
|
return vertical !== true || isActive.value === true
|
|
? getStepWrapper(slots)
|
|
: void 0
|
|
}
|
|
|
|
return () => vue.h(
|
|
'div',
|
|
{ ref: rootRef, class: 'q-stepper__step', role: 'tabpanel', ...scrollEvent.value },
|
|
$stepper.value.vertical === true
|
|
? [
|
|
vue.h(StepHeader, {
|
|
stepper: $stepper.value,
|
|
step: props,
|
|
goToPanel: $stepper.value.goToPanel
|
|
}),
|
|
|
|
$stepper.value.animated === true
|
|
? vue.h(QSlideTransition, getStepContent)
|
|
: getStepContent()
|
|
]
|
|
: [ getStepContent() ]
|
|
)
|
|
}
|
|
});
|
|
|
|
const camelRE = /(-\w)/g;
|
|
|
|
function camelizeProps (props) {
|
|
const acc = {};
|
|
for (const key in props) {
|
|
const newKey = key.replace(camelRE, m => m[ 1 ].toUpperCase());
|
|
acc[ newKey ] = props[ key ];
|
|
}
|
|
return acc
|
|
}
|
|
|
|
var QStepper = createComponent({
|
|
name: 'QStepper',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...usePanelProps,
|
|
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
alternativeLabels: Boolean,
|
|
headerNav: Boolean,
|
|
contracted: Boolean,
|
|
headerClass: String,
|
|
|
|
inactiveColor: String,
|
|
inactiveIcon: String,
|
|
doneIcon: String,
|
|
doneColor: String,
|
|
activeIcon: String,
|
|
activeColor: String,
|
|
errorIcon: String,
|
|
errorColor: String
|
|
},
|
|
|
|
emits: usePanelEmits,
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
const {
|
|
updatePanelsList, isValidPanelName,
|
|
updatePanelIndex, getPanelContent,
|
|
getPanels, panelDirectives, goToPanel,
|
|
keepAliveProps, needsUniqueKeepAliveWrapper
|
|
} = usePanel();
|
|
|
|
vue.provide(stepperKey, vue.computed(() => ({
|
|
goToPanel,
|
|
keepAliveProps,
|
|
needsUniqueKeepAliveWrapper,
|
|
...props
|
|
})));
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-stepper q-stepper--${ props.vertical === true ? 'vertical' : 'horizontal' }`
|
|
+ (props.flat === true ? ' q-stepper--flat' : '')
|
|
+ (props.bordered === true ? ' q-stepper--bordered' : '')
|
|
+ (isDark.value === true ? ' q-stepper--dark q-dark' : '')
|
|
);
|
|
|
|
const headerClasses = vue.computed(() =>
|
|
'q-stepper__header row items-stretch justify-between'
|
|
+ ` q-stepper__header--${ props.alternativeLabels === true ? 'alternative' : 'standard' }-labels`
|
|
+ (props.flat === false || props.bordered === true ? ' q-stepper__header--border' : '')
|
|
+ (props.contracted === true ? ' q-stepper__header--contracted' : '')
|
|
+ (props.headerClass !== void 0 ? ` ${ props.headerClass }` : '')
|
|
);
|
|
|
|
function getContent () {
|
|
const top = hSlot(slots.message, []);
|
|
|
|
if (props.vertical === true) {
|
|
isValidPanelName(props.modelValue) && updatePanelIndex();
|
|
|
|
const content = vue.h('div', {
|
|
class: 'q-stepper__content'
|
|
}, hSlot(slots.default));
|
|
|
|
return top === void 0
|
|
? [ content ]
|
|
: top.concat(content)
|
|
}
|
|
|
|
return [
|
|
vue.h(
|
|
'div',
|
|
{ class: headerClasses.value },
|
|
getPanels().map(panel => {
|
|
const step = camelizeProps(panel.props);
|
|
|
|
return vue.h(StepHeader, {
|
|
key: step.name,
|
|
stepper: props,
|
|
step,
|
|
goToPanel
|
|
})
|
|
})
|
|
),
|
|
|
|
top,
|
|
|
|
hDir(
|
|
'div',
|
|
{ class: 'q-stepper__content q-panel-parent' },
|
|
getPanelContent(),
|
|
'cont',
|
|
props.swipeable,
|
|
() => panelDirectives.value
|
|
)
|
|
]
|
|
}
|
|
|
|
return () => {
|
|
updatePanelsList(slots);
|
|
|
|
return vue.h('div', {
|
|
class: classes.value
|
|
}, hMergeSlot(slots.navigation, getContent()))
|
|
}
|
|
}
|
|
});
|
|
|
|
var QStepperNavigation = createComponent({
|
|
name: 'QStepperNavigation',
|
|
|
|
setup (_, { slots }) {
|
|
return () => vue.h('div', { class: 'q-stepper__nav' }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QTh = createComponent({
|
|
name: 'QTh',
|
|
|
|
props: {
|
|
props: Object,
|
|
autoWidth: Boolean
|
|
},
|
|
|
|
emits: [ 'click' ],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { proxy: { $q } } = vm;
|
|
|
|
const onClick = evt => { emit('click', evt); };
|
|
|
|
return () => {
|
|
if (props.props === void 0) {
|
|
return vue.h('th', {
|
|
class: props.autoWidth === true ? 'q-table--col-auto-width' : '',
|
|
onClick
|
|
}, hSlot(slots.default))
|
|
}
|
|
|
|
let col, child;
|
|
const name = vm.vnode.key;
|
|
|
|
if (name) {
|
|
col = props.props.colsMap[ name ];
|
|
if (col === void 0) { return }
|
|
}
|
|
else {
|
|
col = props.props.col;
|
|
}
|
|
|
|
if (col.sortable === true) {
|
|
const action = col.align === 'right'
|
|
? 'unshift'
|
|
: 'push';
|
|
|
|
child = hUniqueSlot(slots.default, []);
|
|
child[ action ](
|
|
vue.h(QIcon, {
|
|
class: col.__iconClass,
|
|
name: $q.iconSet.table.arrowUp
|
|
})
|
|
);
|
|
}
|
|
else {
|
|
child = hSlot(slots.default);
|
|
}
|
|
|
|
const data = {
|
|
class: col.__thClass
|
|
+ (props.autoWidth === true ? ' q-table--col-auto-width' : ''),
|
|
style: col.headerStyle,
|
|
onClick: evt => {
|
|
col.sortable === true && props.props.sort(col);
|
|
onClick(evt);
|
|
}
|
|
};
|
|
|
|
return vue.h('th', data, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
function getTableMiddle (props, content) {
|
|
return vue.h('div', props, [
|
|
vue.h('table', { class: 'q-table' }, content)
|
|
])
|
|
}
|
|
|
|
const comps = {
|
|
list: QList,
|
|
table: QMarkupTable
|
|
};
|
|
|
|
const typeOptions = [ 'list', 'table', '__qtable' ];
|
|
|
|
var QVirtualScroll = createComponent({
|
|
name: 'QVirtualScroll',
|
|
|
|
props: {
|
|
...useVirtualScrollProps,
|
|
|
|
type: {
|
|
type: String,
|
|
default: 'list',
|
|
validator: v => typeOptions.includes(v)
|
|
},
|
|
|
|
items: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
|
|
itemsFn: Function,
|
|
itemsSize: Number,
|
|
|
|
scrollTarget: {
|
|
default: void 0
|
|
}
|
|
},
|
|
|
|
setup (props, { slots, attrs }) {
|
|
let localScrollTarget;
|
|
const rootRef = vue.ref(null);
|
|
|
|
const virtualScrollLength = vue.computed(() => (
|
|
props.itemsSize >= 0 && props.itemsFn !== void 0
|
|
? parseInt(props.itemsSize, 10)
|
|
: (Array.isArray(props.items) ? props.items.length : 0)
|
|
));
|
|
|
|
const {
|
|
virtualScrollSliceRange,
|
|
localResetVirtualScroll,
|
|
padVirtualScroll,
|
|
onVirtualScrollEvt
|
|
} = useVirtualScroll({
|
|
virtualScrollLength, getVirtualScrollTarget, getVirtualScrollEl
|
|
});
|
|
|
|
const virtualScrollScope = vue.computed(() => {
|
|
if (virtualScrollLength.value === 0) {
|
|
return []
|
|
}
|
|
|
|
const mapFn = (item, i) => ({
|
|
index: virtualScrollSliceRange.value.from + i,
|
|
item
|
|
});
|
|
|
|
return props.itemsFn === void 0
|
|
? props.items.slice(virtualScrollSliceRange.value.from, virtualScrollSliceRange.value.to).map(mapFn)
|
|
: props.itemsFn(virtualScrollSliceRange.value.from, virtualScrollSliceRange.value.to - virtualScrollSliceRange.value.from).map(mapFn)
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-virtual-scroll q-virtual-scroll' + (props.virtualScrollHorizontal === true ? '--horizontal' : '--vertical')
|
|
+ (props.scrollTarget !== void 0 ? '' : ' scroll')
|
|
);
|
|
|
|
const attributes = vue.computed(() => (
|
|
props.scrollTarget !== void 0 ? {} : { tabindex: 0 }
|
|
));
|
|
|
|
vue.watch(virtualScrollLength, () => {
|
|
localResetVirtualScroll();
|
|
});
|
|
|
|
vue.watch(() => props.scrollTarget, () => {
|
|
unconfigureScrollTarget();
|
|
configureScrollTarget();
|
|
});
|
|
|
|
function getVirtualScrollEl () {
|
|
return rootRef.value.$el || rootRef.value
|
|
}
|
|
|
|
function getVirtualScrollTarget () {
|
|
return localScrollTarget
|
|
}
|
|
|
|
function configureScrollTarget () {
|
|
localScrollTarget = getScrollTarget(getVirtualScrollEl(), props.scrollTarget);
|
|
localScrollTarget.addEventListener('scroll', onVirtualScrollEvt, listenOpts.passive);
|
|
}
|
|
|
|
function unconfigureScrollTarget () {
|
|
if (localScrollTarget !== void 0) {
|
|
localScrollTarget.removeEventListener('scroll', onVirtualScrollEvt, listenOpts.passive);
|
|
localScrollTarget = void 0;
|
|
}
|
|
}
|
|
|
|
function __getVirtualChildren () {
|
|
let child = padVirtualScroll(
|
|
props.type === 'list' ? 'div' : 'tbody',
|
|
virtualScrollScope.value.map(slots.default)
|
|
);
|
|
|
|
if (slots.before !== void 0) {
|
|
child = slots.before().concat(child);
|
|
}
|
|
|
|
return hMergeSlot(slots.after, child)
|
|
}
|
|
|
|
vue.onBeforeMount(() => {
|
|
localResetVirtualScroll();
|
|
});
|
|
|
|
vue.onMounted(() => {
|
|
configureScrollTarget();
|
|
});
|
|
|
|
vue.onActivated(() => {
|
|
configureScrollTarget();
|
|
});
|
|
|
|
vue.onDeactivated(() => {
|
|
unconfigureScrollTarget();
|
|
});
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
unconfigureScrollTarget();
|
|
});
|
|
|
|
return () => {
|
|
if (slots.default === void 0) {
|
|
console.error('QVirtualScroll: default scoped slot is required for rendering');
|
|
return
|
|
}
|
|
|
|
return props.type === '__qtable'
|
|
? getTableMiddle(
|
|
{ ref: rootRef, class: 'q-table__middle ' + classes.value },
|
|
__getVirtualChildren()
|
|
)
|
|
: vue.h(comps[ props.type ], {
|
|
...attrs,
|
|
ref: rootRef,
|
|
class: [ attrs.class, classes.value ],
|
|
...attributes.value
|
|
}, __getVirtualChildren)
|
|
}
|
|
}
|
|
});
|
|
|
|
function sortDate (a, b) {
|
|
return (new Date(a)) - (new Date(b))
|
|
}
|
|
|
|
const useTableSortProps = {
|
|
sortMethod: Function,
|
|
binaryStateSort: Boolean,
|
|
columnSortOrder: {
|
|
type: String,
|
|
validator: v => v === 'ad' || v === 'da',
|
|
default: 'ad'
|
|
}
|
|
};
|
|
|
|
function useTableSort (props, computedPagination, colList, setPagination) {
|
|
const columnToSort = vue.computed(() => {
|
|
const { sortBy } = computedPagination.value;
|
|
|
|
return sortBy
|
|
? colList.value.find(def => def.name === sortBy) || null
|
|
: null
|
|
});
|
|
|
|
const computedSortMethod = vue.computed(() => (
|
|
props.sortMethod !== void 0
|
|
? props.sortMethod
|
|
: (data, sortBy, descending) => {
|
|
const col = colList.value.find(def => def.name === sortBy);
|
|
if (col === void 0 || col.field === void 0) {
|
|
return data
|
|
}
|
|
|
|
const
|
|
dir = descending === true ? -1 : 1,
|
|
val = typeof col.field === 'function'
|
|
? v => col.field(v)
|
|
: v => v[ col.field ];
|
|
|
|
return data.sort((a, b) => {
|
|
let
|
|
A = val(a),
|
|
B = val(b);
|
|
|
|
if (A === null || A === void 0) {
|
|
return -1 * dir
|
|
}
|
|
if (B === null || B === void 0) {
|
|
return 1 * dir
|
|
}
|
|
if (col.sort !== void 0) {
|
|
return col.sort(A, B, a, b) * dir
|
|
}
|
|
if (isNumber(A) === true && isNumber(B) === true) {
|
|
return (A - B) * dir
|
|
}
|
|
if (isDate(A) === true && isDate(B) === true) {
|
|
return sortDate(A, B) * dir
|
|
}
|
|
if (typeof A === 'boolean' && typeof B === 'boolean') {
|
|
return (A - B) * dir
|
|
}
|
|
|
|
[ A, B ] = [ A, B ].map(s => (s + '').toLocaleString().toLowerCase());
|
|
|
|
return A < B
|
|
? -1 * dir
|
|
: (A === B ? 0 : dir)
|
|
})
|
|
}
|
|
));
|
|
|
|
function sort (col /* String(col name) or Object(col definition) */) {
|
|
let sortOrder = props.columnSortOrder;
|
|
|
|
if (isObject(col) === true) {
|
|
if (col.sortOrder) {
|
|
sortOrder = col.sortOrder;
|
|
}
|
|
|
|
col = col.name;
|
|
}
|
|
else {
|
|
const def = colList.value.find(def => def.name === col);
|
|
if (def !== void 0 && def.sortOrder) {
|
|
sortOrder = def.sortOrder;
|
|
}
|
|
}
|
|
|
|
let { sortBy, descending } = computedPagination.value;
|
|
|
|
if (sortBy !== col) {
|
|
sortBy = col;
|
|
descending = sortOrder === 'da';
|
|
}
|
|
else if (props.binaryStateSort === true) {
|
|
descending = !descending;
|
|
}
|
|
else if (descending === true) {
|
|
if (sortOrder === 'ad') {
|
|
sortBy = null;
|
|
}
|
|
else {
|
|
descending = false;
|
|
}
|
|
}
|
|
else { // ascending
|
|
if (sortOrder === 'ad') {
|
|
descending = true;
|
|
}
|
|
else {
|
|
sortBy = null;
|
|
}
|
|
}
|
|
|
|
setPagination({ sortBy, descending, page: 1 });
|
|
}
|
|
|
|
return {
|
|
columnToSort,
|
|
computedSortMethod,
|
|
sort
|
|
}
|
|
}
|
|
|
|
const useTableFilterProps = {
|
|
filter: [ String, Object ],
|
|
filterMethod: Function
|
|
};
|
|
|
|
function useTableFilter (props, setPagination) {
|
|
const computedFilterMethod = vue.computed(() => (
|
|
props.filterMethod !== void 0
|
|
? props.filterMethod
|
|
: (rows, terms, cols, cellValue) => {
|
|
const lowerTerms = terms ? terms.toLowerCase() : '';
|
|
return rows.filter(
|
|
row => cols.some(col => {
|
|
const val = cellValue(col, row) + '';
|
|
const haystack = (val === 'undefined' || val === 'null') ? '' : val.toLowerCase();
|
|
return haystack.indexOf(lowerTerms) !== -1
|
|
})
|
|
)
|
|
}
|
|
));
|
|
|
|
vue.watch(
|
|
() => props.filter,
|
|
() => {
|
|
vue.nextTick(() => {
|
|
setPagination({ page: 1 }, true);
|
|
});
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
return { computedFilterMethod }
|
|
}
|
|
|
|
function samePagination (oldPag, newPag) {
|
|
for (const prop in newPag) {
|
|
if (newPag[ prop ] !== oldPag[ prop ]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function fixPagination (p) {
|
|
if (p.page < 1) {
|
|
p.page = 1;
|
|
}
|
|
if (p.rowsPerPage !== void 0 && p.rowsPerPage < 1) {
|
|
p.rowsPerPage = 0;
|
|
}
|
|
return p
|
|
}
|
|
|
|
const useTablePaginationProps = {
|
|
pagination: Object,
|
|
rowsPerPageOptions: {
|
|
type: Array,
|
|
default: () => [ 5, 7, 10, 15, 20, 25, 50, 0 ]
|
|
},
|
|
|
|
'onUpdate:pagination': [ Function, Array ]
|
|
};
|
|
|
|
function useTablePaginationState (vm, getCellValue) {
|
|
const { props, emit } = vm;
|
|
|
|
const innerPagination = vue.ref(
|
|
Object.assign({
|
|
sortBy: null,
|
|
descending: false,
|
|
page: 1,
|
|
rowsPerPage: props.rowsPerPageOptions.length !== 0
|
|
? props.rowsPerPageOptions[ 0 ]
|
|
: 5
|
|
}, props.pagination)
|
|
);
|
|
|
|
const computedPagination = vue.computed(() => {
|
|
const pag = props[ 'onUpdate:pagination' ] !== void 0
|
|
? { ...innerPagination.value, ...props.pagination }
|
|
: innerPagination.value;
|
|
|
|
return fixPagination(pag)
|
|
});
|
|
|
|
const isServerSide = vue.computed(() => computedPagination.value.rowsNumber !== void 0);
|
|
|
|
function sendServerRequest (pagination) {
|
|
requestServerInteraction({
|
|
pagination,
|
|
filter: props.filter
|
|
});
|
|
}
|
|
|
|
function requestServerInteraction (prop = {}) {
|
|
vue.nextTick(() => {
|
|
emit('request', {
|
|
pagination: prop.pagination || computedPagination.value,
|
|
filter: prop.filter || props.filter,
|
|
getCellValue
|
|
});
|
|
});
|
|
}
|
|
|
|
function setPagination (val, forceServerRequest) {
|
|
const newPagination = fixPagination({
|
|
...computedPagination.value,
|
|
...val
|
|
});
|
|
|
|
if (samePagination(computedPagination.value, newPagination) === true) {
|
|
if (isServerSide.value === true && forceServerRequest === true) {
|
|
sendServerRequest(newPagination);
|
|
}
|
|
return
|
|
}
|
|
|
|
if (isServerSide.value === true) {
|
|
sendServerRequest(newPagination);
|
|
return
|
|
}
|
|
|
|
if (
|
|
props.pagination !== void 0
|
|
&& props[ 'onUpdate:pagination' ] !== void 0
|
|
) {
|
|
emit('update:pagination', newPagination);
|
|
}
|
|
else {
|
|
innerPagination.value = newPagination;
|
|
}
|
|
}
|
|
|
|
return {
|
|
innerPagination,
|
|
computedPagination,
|
|
isServerSide,
|
|
|
|
requestServerInteraction,
|
|
setPagination
|
|
}
|
|
}
|
|
|
|
function useTablePagination (vm, innerPagination, computedPagination, isServerSide, setPagination, filteredSortedRowsNumber) {
|
|
const { props, emit, proxy: { $q } } = vm;
|
|
|
|
const computedRowsNumber = vue.computed(() => (
|
|
isServerSide.value === true
|
|
? computedPagination.value.rowsNumber || 0
|
|
: filteredSortedRowsNumber.value
|
|
));
|
|
|
|
const firstRowIndex = vue.computed(() => {
|
|
const { page, rowsPerPage } = computedPagination.value;
|
|
return (page - 1) * rowsPerPage
|
|
});
|
|
|
|
const lastRowIndex = vue.computed(() => {
|
|
const { page, rowsPerPage } = computedPagination.value;
|
|
return page * rowsPerPage
|
|
});
|
|
|
|
const isFirstPage = vue.computed(() => computedPagination.value.page === 1);
|
|
|
|
const pagesNumber = vue.computed(() => (
|
|
computedPagination.value.rowsPerPage === 0
|
|
? 1
|
|
: Math.max(
|
|
1,
|
|
Math.ceil(computedRowsNumber.value / computedPagination.value.rowsPerPage)
|
|
)
|
|
));
|
|
|
|
const isLastPage = vue.computed(() => (
|
|
lastRowIndex.value === 0
|
|
? true
|
|
: computedPagination.value.page >= pagesNumber.value
|
|
));
|
|
|
|
const computedRowsPerPageOptions = vue.computed(() => {
|
|
const opts = props.rowsPerPageOptions.includes(innerPagination.value.rowsPerPage)
|
|
? props.rowsPerPageOptions
|
|
: [ innerPagination.value.rowsPerPage ].concat(props.rowsPerPageOptions);
|
|
|
|
return opts.map(count => ({
|
|
label: count === 0 ? $q.lang.table.allRows : '' + count,
|
|
value: count
|
|
}))
|
|
});
|
|
|
|
vue.watch(pagesNumber, (lastPage, oldLastPage) => {
|
|
if (lastPage === oldLastPage) {
|
|
return
|
|
}
|
|
|
|
const currentPage = computedPagination.value.page;
|
|
if (lastPage && !currentPage) {
|
|
setPagination({ page: 1 });
|
|
}
|
|
else if (lastPage < currentPage) {
|
|
setPagination({ page: lastPage });
|
|
}
|
|
});
|
|
|
|
function firstPage () {
|
|
setPagination({ page: 1 });
|
|
}
|
|
|
|
function prevPage () {
|
|
const { page } = computedPagination.value;
|
|
if (page > 1) {
|
|
setPagination({ page: page - 1 });
|
|
}
|
|
}
|
|
|
|
function nextPage () {
|
|
const { page, rowsPerPage } = computedPagination.value;
|
|
if (lastRowIndex.value > 0 && page * rowsPerPage < computedRowsNumber.value) {
|
|
setPagination({ page: page + 1 });
|
|
}
|
|
}
|
|
|
|
function lastPage () {
|
|
setPagination({ page: pagesNumber.value });
|
|
}
|
|
|
|
if (props[ 'onUpdate:pagination' ] !== void 0) {
|
|
emit('update:pagination', { ...computedPagination.value });
|
|
}
|
|
|
|
return {
|
|
firstRowIndex,
|
|
lastRowIndex,
|
|
isFirstPage,
|
|
isLastPage,
|
|
pagesNumber,
|
|
computedRowsPerPageOptions,
|
|
computedRowsNumber,
|
|
|
|
firstPage,
|
|
prevPage,
|
|
nextPage,
|
|
lastPage
|
|
}
|
|
}
|
|
|
|
const useTableRowSelectionProps = {
|
|
selection: {
|
|
type: String,
|
|
default: 'none',
|
|
validator: v => [ 'single', 'multiple', 'none' ].includes(v)
|
|
},
|
|
selected: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
};
|
|
|
|
const useTableRowSelectionEmits = [ 'update:selected', 'selection' ];
|
|
|
|
function useTableRowSelection (props, emit, computedRows, getRowKey) {
|
|
const selectedKeys = vue.computed(() => {
|
|
const keys = {};
|
|
props.selected.map(getRowKey.value).forEach(key => {
|
|
keys[ key ] = true;
|
|
});
|
|
return keys
|
|
});
|
|
|
|
const hasSelectionMode = vue.computed(() => {
|
|
return props.selection !== 'none'
|
|
});
|
|
|
|
const singleSelection = vue.computed(() => {
|
|
return props.selection === 'single'
|
|
});
|
|
|
|
const multipleSelection = vue.computed(() => {
|
|
return props.selection === 'multiple'
|
|
});
|
|
|
|
const allRowsSelected = vue.computed(() =>
|
|
computedRows.value.length !== 0 && computedRows.value.every(
|
|
row => selectedKeys.value[ getRowKey.value(row) ] === true
|
|
)
|
|
);
|
|
|
|
const someRowsSelected = vue.computed(() =>
|
|
allRowsSelected.value !== true
|
|
&& computedRows.value.some(row => selectedKeys.value[ getRowKey.value(row) ] === true)
|
|
);
|
|
|
|
const rowsSelectedNumber = vue.computed(() => props.selected.length);
|
|
|
|
function isRowSelected (key) {
|
|
return selectedKeys.value[ key ] === true
|
|
}
|
|
|
|
function clearSelection () {
|
|
emit('update:selected', []);
|
|
}
|
|
|
|
function updateSelection (keys, rows, added, evt) {
|
|
emit('selection', { rows, added, keys, evt });
|
|
|
|
const payload = singleSelection.value === true
|
|
? (added === true ? rows : [])
|
|
: (
|
|
added === true
|
|
? props.selected.concat(rows)
|
|
: props.selected.filter(
|
|
row => keys.includes(getRowKey.value(row)) === false
|
|
)
|
|
);
|
|
|
|
emit('update:selected', payload);
|
|
}
|
|
|
|
return {
|
|
hasSelectionMode,
|
|
singleSelection,
|
|
multipleSelection,
|
|
allRowsSelected,
|
|
someRowsSelected,
|
|
rowsSelectedNumber,
|
|
|
|
isRowSelected,
|
|
clearSelection,
|
|
updateSelection
|
|
}
|
|
}
|
|
|
|
function getVal (val) {
|
|
return Array.isArray(val)
|
|
? val.slice()
|
|
: []
|
|
}
|
|
|
|
const useTableRowExpandProps = {
|
|
expanded: Array // v-model:expanded
|
|
};
|
|
|
|
const useTableRowExpandEmits = [ 'update:expanded' ];
|
|
|
|
function useTableRowExpand (props, emit) {
|
|
const innerExpanded = vue.ref(getVal(props.expanded));
|
|
|
|
vue.watch(() => props.expanded, val => {
|
|
innerExpanded.value = getVal(val);
|
|
});
|
|
|
|
function isRowExpanded (key) {
|
|
return innerExpanded.value.includes(key)
|
|
}
|
|
|
|
function setExpanded (val) {
|
|
if (props.expanded !== void 0) {
|
|
emit('update:expanded', val);
|
|
}
|
|
else {
|
|
innerExpanded.value = val;
|
|
}
|
|
}
|
|
|
|
function updateExpanded (key, add) {
|
|
const target = innerExpanded.value.slice();
|
|
const index = target.indexOf(key);
|
|
|
|
if (add === true) {
|
|
if (index === -1) {
|
|
target.push(key);
|
|
setExpanded(target);
|
|
}
|
|
}
|
|
else if (index !== -1) {
|
|
target.splice(index, 1);
|
|
setExpanded(target);
|
|
}
|
|
}
|
|
|
|
return {
|
|
isRowExpanded,
|
|
setExpanded,
|
|
updateExpanded
|
|
}
|
|
}
|
|
|
|
const useTableColumnSelectionProps = {
|
|
visibleColumns: Array
|
|
};
|
|
|
|
function useTableColumnSelection (props, computedPagination, hasSelectionMode) {
|
|
const colList = vue.computed(() => {
|
|
if (props.columns !== void 0) {
|
|
return props.columns
|
|
}
|
|
|
|
// we infer columns from first row
|
|
const row = props.rows[ 0 ];
|
|
|
|
return row !== void 0
|
|
? Object.keys(row).map(name => ({
|
|
name,
|
|
label: name.toUpperCase(),
|
|
field: name,
|
|
align: isNumber(row[ name ]) ? 'right' : 'left',
|
|
sortable: true
|
|
}))
|
|
: []
|
|
});
|
|
|
|
const computedCols = vue.computed(() => {
|
|
const { sortBy, descending } = computedPagination.value;
|
|
|
|
const cols = props.visibleColumns !== void 0
|
|
? colList.value.filter(col => col.required === true || props.visibleColumns.includes(col.name) === true)
|
|
: colList.value;
|
|
|
|
return cols.map(col => {
|
|
const align = col.align || 'right';
|
|
const alignClass = `text-${ align }`;
|
|
|
|
return {
|
|
...col,
|
|
align,
|
|
__iconClass: `q-table__sort-icon q-table__sort-icon--${ align }`,
|
|
__thClass: alignClass
|
|
+ (col.headerClasses !== void 0 ? ' ' + col.headerClasses : '')
|
|
+ (col.sortable === true ? ' sortable' : '')
|
|
+ (col.name === sortBy ? ` sorted ${ descending === true ? 'sort-desc' : '' }` : ''),
|
|
|
|
__tdStyle: col.style !== void 0
|
|
? (
|
|
typeof col.style !== 'function'
|
|
? () => col.style
|
|
: col.style
|
|
)
|
|
: () => null,
|
|
|
|
__tdClass: col.classes !== void 0
|
|
? (
|
|
typeof col.classes !== 'function'
|
|
? () => alignClass + ' ' + col.classes
|
|
: row => alignClass + ' ' + col.classes(row)
|
|
)
|
|
: () => alignClass
|
|
}
|
|
})
|
|
});
|
|
|
|
const computedColsMap = vue.computed(() => {
|
|
const names = {};
|
|
computedCols.value.forEach(col => {
|
|
names[ col.name ] = col;
|
|
});
|
|
return names
|
|
});
|
|
|
|
const computedColspan = vue.computed(() => {
|
|
return props.tableColspan !== void 0
|
|
? props.tableColspan
|
|
: computedCols.value.length + (hasSelectionMode.value === true ? 1 : 0)
|
|
});
|
|
|
|
return {
|
|
colList,
|
|
computedCols,
|
|
computedColsMap,
|
|
computedColspan
|
|
}
|
|
}
|
|
|
|
const bottomClass = 'q-table__bottom row items-center';
|
|
|
|
const commonVirtPropsObj = {};
|
|
commonVirtPropsList.forEach(p => { commonVirtPropsObj[ p ] = {}; });
|
|
|
|
var QTable = createComponent({
|
|
name: 'QTable',
|
|
|
|
props: {
|
|
rows: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
rowKey: {
|
|
type: [ String, Function ],
|
|
default: 'id'
|
|
},
|
|
|
|
columns: Array,
|
|
loading: Boolean,
|
|
|
|
iconFirstPage: String,
|
|
iconPrevPage: String,
|
|
iconNextPage: String,
|
|
iconLastPage: String,
|
|
|
|
title: String,
|
|
|
|
hideHeader: Boolean,
|
|
|
|
grid: Boolean,
|
|
gridHeader: Boolean,
|
|
|
|
dense: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
square: Boolean,
|
|
separator: {
|
|
type: String,
|
|
default: 'horizontal',
|
|
validator: v => [ 'horizontal', 'vertical', 'cell', 'none' ].includes(v)
|
|
},
|
|
wrapCells: Boolean,
|
|
|
|
virtualScroll: Boolean,
|
|
virtualScrollTarget: {
|
|
default: void 0
|
|
},
|
|
...commonVirtPropsObj,
|
|
|
|
noDataLabel: String,
|
|
noResultsLabel: String,
|
|
loadingLabel: String,
|
|
selectedRowsLabel: Function,
|
|
rowsPerPageLabel: String,
|
|
paginationLabel: Function,
|
|
|
|
color: {
|
|
type: String,
|
|
default: 'grey-8'
|
|
},
|
|
|
|
titleClass: [ String, Array, Object ],
|
|
tableStyle: [ String, Array, Object ],
|
|
tableClass: [ String, Array, Object ],
|
|
tableHeaderStyle: [ String, Array, Object ],
|
|
tableHeaderClass: [ String, Array, Object ],
|
|
cardContainerClass: [ String, Array, Object ],
|
|
cardContainerStyle: [ String, Array, Object ],
|
|
cardStyle: [ String, Array, Object ],
|
|
cardClass: [ String, Array, Object ],
|
|
|
|
hideBottom: Boolean,
|
|
hideSelectedBanner: Boolean,
|
|
hideNoData: Boolean,
|
|
hidePagination: Boolean,
|
|
|
|
onRowClick: Function,
|
|
onRowDblclick: Function,
|
|
onRowContextmenu: Function,
|
|
|
|
...useDarkProps,
|
|
...useFullscreenProps,
|
|
|
|
...useTableColumnSelectionProps,
|
|
...useTableFilterProps,
|
|
...useTablePaginationProps,
|
|
...useTableRowExpandProps,
|
|
...useTableRowSelectionProps,
|
|
...useTableSortProps
|
|
},
|
|
|
|
emits: [
|
|
'request', 'virtualScroll',
|
|
...useFullscreenEmits,
|
|
...useTableRowExpandEmits,
|
|
...useTableRowSelectionEmits
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { proxy: { $q } } = vm;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { inFullscreen, toggleFullscreen } = useFullscreen();
|
|
|
|
const getRowKey = vue.computed(() => (
|
|
typeof props.rowKey === 'function'
|
|
? props.rowKey
|
|
: row => row[ props.rowKey ]
|
|
));
|
|
|
|
const rootRef = vue.ref(null);
|
|
const virtScrollRef = vue.ref(null);
|
|
const hasVirtScroll = vue.computed(() => props.grid !== true && props.virtualScroll === true);
|
|
|
|
const cardDefaultClass = vue.computed(() =>
|
|
' q-table__card'
|
|
+ (isDark.value === true ? ' q-table__card--dark q-dark' : '')
|
|
+ (props.square === true ? ' q-table--square' : '')
|
|
+ (props.flat === true ? ' q-table--flat' : '')
|
|
+ (props.bordered === true ? ' q-table--bordered' : '')
|
|
);
|
|
|
|
const __containerClass = vue.computed(() =>
|
|
`q-table__container q-table--${ props.separator }-separator column no-wrap`
|
|
+ (props.grid === true ? ' q-table--grid' : cardDefaultClass.value)
|
|
+ (isDark.value === true ? ' q-table--dark' : '')
|
|
+ (props.dense === true ? ' q-table--dense' : '')
|
|
+ (props.wrapCells === false ? ' q-table--no-wrap' : '')
|
|
+ (inFullscreen.value === true ? ' fullscreen scroll' : '')
|
|
);
|
|
|
|
const containerClass = vue.computed(() =>
|
|
__containerClass.value + (props.loading === true ? ' q-table--loading' : '')
|
|
);
|
|
|
|
vue.watch(
|
|
() => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + __containerClass.value,
|
|
() => { hasVirtScroll.value === true && virtScrollRef.value !== null && virtScrollRef.value.reset(); }
|
|
);
|
|
|
|
const {
|
|
innerPagination,
|
|
computedPagination,
|
|
isServerSide,
|
|
|
|
requestServerInteraction,
|
|
setPagination
|
|
} = useTablePaginationState(vm, getCellValue);
|
|
|
|
const { computedFilterMethod } = useTableFilter(props, setPagination);
|
|
const { isRowExpanded, setExpanded, updateExpanded } = useTableRowExpand(props, emit);
|
|
|
|
const filteredSortedRows = vue.computed(() => {
|
|
let rows = props.rows;
|
|
|
|
if (isServerSide.value === true || rows.length === 0) {
|
|
return rows
|
|
}
|
|
|
|
const { sortBy, descending } = computedPagination.value;
|
|
|
|
if (props.filter) {
|
|
rows = computedFilterMethod.value(rows, props.filter, computedCols.value, getCellValue);
|
|
}
|
|
|
|
if (columnToSort.value !== null) {
|
|
rows = computedSortMethod.value(
|
|
props.rows === rows ? rows.slice() : rows,
|
|
sortBy,
|
|
descending
|
|
);
|
|
}
|
|
|
|
return rows
|
|
});
|
|
|
|
const filteredSortedRowsNumber = vue.computed(() => filteredSortedRows.value.length);
|
|
|
|
const computedRows = vue.computed(() => {
|
|
let rows = filteredSortedRows.value;
|
|
|
|
if (isServerSide.value === true) {
|
|
return rows
|
|
}
|
|
|
|
const { rowsPerPage } = computedPagination.value;
|
|
|
|
if (rowsPerPage !== 0) {
|
|
if (firstRowIndex.value === 0 && props.rows !== rows) {
|
|
if (rows.length > lastRowIndex.value) {
|
|
rows = rows.slice(0, lastRowIndex.value);
|
|
}
|
|
}
|
|
else {
|
|
rows = rows.slice(firstRowIndex.value, lastRowIndex.value);
|
|
}
|
|
}
|
|
|
|
return rows
|
|
});
|
|
|
|
const {
|
|
hasSelectionMode,
|
|
singleSelection,
|
|
multipleSelection,
|
|
allRowsSelected,
|
|
someRowsSelected,
|
|
rowsSelectedNumber,
|
|
|
|
isRowSelected,
|
|
clearSelection,
|
|
updateSelection
|
|
} = useTableRowSelection(props, emit, computedRows, getRowKey);
|
|
|
|
const { colList, computedCols, computedColsMap, computedColspan } = useTableColumnSelection(props, computedPagination, hasSelectionMode);
|
|
|
|
const { columnToSort, computedSortMethod, sort } = useTableSort(props, computedPagination, colList, setPagination);
|
|
|
|
const {
|
|
firstRowIndex,
|
|
lastRowIndex,
|
|
isFirstPage,
|
|
isLastPage,
|
|
pagesNumber,
|
|
computedRowsPerPageOptions,
|
|
computedRowsNumber,
|
|
|
|
firstPage,
|
|
prevPage,
|
|
nextPage,
|
|
lastPage
|
|
} = useTablePagination(vm, innerPagination, computedPagination, isServerSide, setPagination, filteredSortedRowsNumber);
|
|
|
|
const nothingToDisplay = vue.computed(() => computedRows.value.length === 0);
|
|
|
|
const virtProps = vue.computed(() => {
|
|
const acc = {};
|
|
|
|
commonVirtPropsList
|
|
.forEach(p => { acc[ p ] = props[ p ]; });
|
|
|
|
if (acc.virtualScrollItemSize === void 0) {
|
|
acc.virtualScrollItemSize = props.dense === true ? 28 : 48;
|
|
}
|
|
|
|
return acc
|
|
});
|
|
|
|
function resetVirtualScroll () {
|
|
hasVirtScroll.value === true && virtScrollRef.value.reset();
|
|
}
|
|
|
|
function getBody () {
|
|
if (props.grid === true) {
|
|
return getGridBody()
|
|
}
|
|
|
|
const header = props.hideHeader !== true ? getTHead : null;
|
|
|
|
if (hasVirtScroll.value === true) {
|
|
const topRow = slots[ 'top-row' ];
|
|
const bottomRow = slots[ 'bottom-row' ];
|
|
|
|
const virtSlots = {
|
|
default: props => getTBodyTR(props.item, slots.body, props.index)
|
|
};
|
|
|
|
if (topRow !== void 0) {
|
|
const topContent = vue.h('tbody', topRow({ cols: computedCols.value }));
|
|
|
|
virtSlots.before = header === null
|
|
? () => topContent
|
|
: () => [ header() ].concat(topContent);
|
|
}
|
|
else if (header !== null) {
|
|
virtSlots.before = header;
|
|
}
|
|
|
|
if (bottomRow !== void 0) {
|
|
virtSlots.after = () => vue.h('tbody', bottomRow({ cols: computedCols.value }));
|
|
}
|
|
|
|
return vue.h(QVirtualScroll, {
|
|
ref: virtScrollRef,
|
|
class: props.tableClass,
|
|
style: props.tableStyle,
|
|
...virtProps.value,
|
|
scrollTarget: props.virtualScrollTarget,
|
|
items: computedRows.value,
|
|
type: '__qtable',
|
|
tableColspan: computedColspan.value,
|
|
onVirtualScroll: onVScroll
|
|
}, virtSlots)
|
|
}
|
|
|
|
const child = [
|
|
getTBody()
|
|
];
|
|
|
|
if (header !== null) {
|
|
child.unshift(header());
|
|
}
|
|
|
|
return getTableMiddle({
|
|
class: [ 'q-table__middle scroll', props.tableClass ],
|
|
style: props.tableStyle
|
|
}, child)
|
|
}
|
|
|
|
function scrollTo (toIndex, edge) {
|
|
if (virtScrollRef.value !== null) {
|
|
virtScrollRef.value.scrollTo(toIndex, edge);
|
|
return
|
|
}
|
|
|
|
toIndex = parseInt(toIndex, 10);
|
|
const rowEl = rootRef.value.querySelector(`tbody tr:nth-of-type(${ toIndex + 1 })`);
|
|
|
|
if (rowEl !== null) {
|
|
const scrollTarget = rootRef.value.querySelector('.q-table__middle.scroll');
|
|
const offsetTop = rowEl.offsetTop - props.virtualScrollStickySizeStart;
|
|
const direction = offsetTop < scrollTarget.scrollTop ? 'decrease' : 'increase';
|
|
|
|
scrollTarget.scrollTop = offsetTop;
|
|
|
|
emit('virtualScroll', {
|
|
index: toIndex,
|
|
from: 0,
|
|
to: innerPagination.value.rowsPerPage - 1,
|
|
direction
|
|
});
|
|
}
|
|
}
|
|
|
|
function onVScroll (info) {
|
|
emit('virtualScroll', info);
|
|
}
|
|
|
|
function getProgress () {
|
|
return [
|
|
vue.h(QLinearProgress, {
|
|
class: 'q-table__linear-progress',
|
|
color: props.color,
|
|
dark: isDark.value,
|
|
indeterminate: true,
|
|
trackColor: 'transparent'
|
|
})
|
|
]
|
|
}
|
|
|
|
function getTBodyTR (row, bodySlot, pageIndex) {
|
|
const
|
|
key = getRowKey.value(row),
|
|
selected = isRowSelected(key);
|
|
|
|
if (bodySlot !== void 0) {
|
|
return bodySlot(
|
|
getBodyScope({
|
|
key,
|
|
row,
|
|
pageIndex,
|
|
__trClass: selected ? 'selected' : ''
|
|
})
|
|
)
|
|
}
|
|
|
|
const
|
|
bodyCell = slots[ 'body-cell' ],
|
|
child = computedCols.value.map(col => {
|
|
const
|
|
bodyCellCol = slots[ `body-cell-${ col.name }` ],
|
|
slot = bodyCellCol !== void 0 ? bodyCellCol : bodyCell;
|
|
|
|
return slot !== void 0
|
|
? slot(getBodyCellScope({ key, row, pageIndex, col }))
|
|
: vue.h('td', {
|
|
class: col.__tdClass(row),
|
|
style: col.__tdStyle(row)
|
|
}, getCellValue(col, row))
|
|
});
|
|
|
|
if (hasSelectionMode.value === true) {
|
|
const slot = slots[ 'body-selection' ];
|
|
const content = slot !== void 0
|
|
? slot(getBodySelectionScope({ key, row, pageIndex }))
|
|
: [
|
|
vue.h(QCheckbox, {
|
|
modelValue: selected,
|
|
color: props.color,
|
|
dark: isDark.value,
|
|
dense: props.dense,
|
|
'onUpdate:modelValue': (adding, evt) => {
|
|
updateSelection([ key ], [ row ], adding, evt);
|
|
}
|
|
})
|
|
];
|
|
|
|
child.unshift(
|
|
vue.h('td', { class: 'q-table--col-auto-width' }, content)
|
|
);
|
|
}
|
|
|
|
const data = { key, class: { selected } };
|
|
|
|
if (props.onRowClick !== void 0) {
|
|
data.class[ 'cursor-pointer' ] = true;
|
|
data.onClick = evt => {
|
|
emit('RowClick', evt, row, pageIndex);
|
|
};
|
|
}
|
|
|
|
if (props.onRowDblclick !== void 0) {
|
|
data.class[ 'cursor-pointer' ] = true;
|
|
data.onDblclick = evt => {
|
|
emit('RowDblclick', evt, row, pageIndex);
|
|
};
|
|
}
|
|
|
|
if (props.onRowContextmenu !== void 0) {
|
|
data.class[ 'cursor-pointer' ] = true;
|
|
data.onContextmenu = evt => {
|
|
emit('RowContextmenu', evt, row, pageIndex);
|
|
};
|
|
}
|
|
|
|
return vue.h('tr', data, child)
|
|
}
|
|
|
|
function getTBody () {
|
|
const
|
|
body = slots.body,
|
|
topRow = slots[ 'top-row' ],
|
|
bottomRow = slots[ 'bottom-row' ];
|
|
|
|
let child = computedRows.value.map(
|
|
(row, pageIndex) => getTBodyTR(row, body, pageIndex)
|
|
);
|
|
|
|
if (topRow !== void 0) {
|
|
child = topRow({ cols: computedCols.value }).concat(child);
|
|
}
|
|
if (bottomRow !== void 0) {
|
|
child = child.concat(bottomRow({ cols: computedCols.value }));
|
|
}
|
|
|
|
return vue.h('tbody', child)
|
|
}
|
|
|
|
function getBodyScope (data) {
|
|
injectBodyCommonScope(data);
|
|
|
|
data.cols = data.cols.map(
|
|
col => injectProp({ ...col }, 'value', () => getCellValue(col, data.row))
|
|
);
|
|
|
|
return data
|
|
}
|
|
|
|
function getBodyCellScope (data) {
|
|
injectBodyCommonScope(data);
|
|
injectProp(data, 'value', () => getCellValue(data.col, data.row));
|
|
return data
|
|
}
|
|
|
|
function getBodySelectionScope (data) {
|
|
injectBodyCommonScope(data);
|
|
return data
|
|
}
|
|
|
|
function injectBodyCommonScope (data) {
|
|
Object.assign(data, {
|
|
cols: computedCols.value,
|
|
colsMap: computedColsMap.value,
|
|
sort,
|
|
rowIndex: firstRowIndex.value + data.pageIndex,
|
|
color: props.color,
|
|
dark: isDark.value,
|
|
dense: props.dense
|
|
});
|
|
|
|
hasSelectionMode.value === true && injectProp(
|
|
data,
|
|
'selected',
|
|
() => isRowSelected(data.key),
|
|
(adding, evt) => {
|
|
updateSelection([ data.key ], [ data.row ], adding, evt);
|
|
}
|
|
);
|
|
|
|
injectProp(
|
|
data,
|
|
'expand',
|
|
() => isRowExpanded(data.key),
|
|
adding => { updateExpanded(data.key, adding); }
|
|
);
|
|
}
|
|
|
|
function getCellValue (col, row) {
|
|
const val = typeof col.field === 'function' ? col.field(row) : row[ col.field ];
|
|
return col.format !== void 0 ? col.format(val, row) : val
|
|
}
|
|
|
|
const marginalsScope = vue.computed(() => ({
|
|
pagination: computedPagination.value,
|
|
pagesNumber: pagesNumber.value,
|
|
isFirstPage: isFirstPage.value,
|
|
isLastPage: isLastPage.value,
|
|
firstPage,
|
|
prevPage,
|
|
nextPage,
|
|
lastPage,
|
|
|
|
inFullscreen: inFullscreen.value,
|
|
toggleFullscreen
|
|
}));
|
|
|
|
function getTopDiv () {
|
|
const
|
|
top = slots.top,
|
|
topLeft = slots[ 'top-left' ],
|
|
topRight = slots[ 'top-right' ],
|
|
topSelection = slots[ 'top-selection' ],
|
|
hasSelection = hasSelectionMode.value === true
|
|
&& topSelection !== void 0
|
|
&& rowsSelectedNumber.value > 0,
|
|
topClass = 'q-table__top relative-position row items-center';
|
|
|
|
if (top !== void 0) {
|
|
return vue.h('div', { class: topClass }, [ top(marginalsScope.value) ])
|
|
}
|
|
|
|
let child;
|
|
|
|
if (hasSelection === true) {
|
|
child = topSelection(marginalsScope.value).slice();
|
|
}
|
|
else {
|
|
child = [];
|
|
|
|
if (topLeft !== void 0) {
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__control' }, [
|
|
topLeft(marginalsScope.value)
|
|
])
|
|
);
|
|
}
|
|
else if (props.title) {
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__control' }, [
|
|
vue.h('div', {
|
|
class: [ 'q-table__title', props.titleClass ]
|
|
}, props.title)
|
|
])
|
|
);
|
|
}
|
|
}
|
|
|
|
if (topRight !== void 0) {
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__separator col' })
|
|
);
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__control' }, [
|
|
topRight(marginalsScope.value)
|
|
])
|
|
);
|
|
}
|
|
|
|
if (child.length === 0) {
|
|
return
|
|
}
|
|
|
|
return vue.h('div', { class: topClass }, child)
|
|
}
|
|
|
|
const headerSelectedValue = vue.computed(() => (
|
|
someRowsSelected.value === true
|
|
? null
|
|
: allRowsSelected.value
|
|
));
|
|
|
|
function getTHead () {
|
|
const child = getTHeadTR();
|
|
|
|
if (props.loading === true && slots.loading === void 0) {
|
|
child.push(
|
|
vue.h('tr', { class: 'q-table__progress' }, [
|
|
vue.h('th', {
|
|
class: 'relative-position',
|
|
colspan: computedColspan.value
|
|
}, getProgress())
|
|
])
|
|
);
|
|
}
|
|
|
|
return vue.h('thead', child)
|
|
}
|
|
|
|
function getTHeadTR () {
|
|
const
|
|
header = slots.header,
|
|
headerCell = slots[ 'header-cell' ];
|
|
|
|
if (header !== void 0) {
|
|
return header(
|
|
getHeaderScope({ header: true })
|
|
).slice()
|
|
}
|
|
|
|
const child = computedCols.value.map(col => {
|
|
const
|
|
headerCellCol = slots[ `header-cell-${ col.name }` ],
|
|
slot = headerCellCol !== void 0 ? headerCellCol : headerCell,
|
|
props = getHeaderScope({ col });
|
|
|
|
return slot !== void 0
|
|
? slot(props)
|
|
: vue.h(QTh, {
|
|
key: col.name,
|
|
props
|
|
}, () => col.label)
|
|
});
|
|
|
|
if (singleSelection.value === true && props.grid !== true) {
|
|
child.unshift(
|
|
vue.h('th', { class: 'q-table--col-auto-width' }, ' ')
|
|
);
|
|
}
|
|
else if (multipleSelection.value === true) {
|
|
const slot = slots[ 'header-selection' ];
|
|
const content = slot !== void 0
|
|
? slot(getHeaderScope({}))
|
|
: [
|
|
vue.h(QCheckbox, {
|
|
color: props.color,
|
|
modelValue: headerSelectedValue.value,
|
|
dark: isDark.value,
|
|
dense: props.dense,
|
|
'onUpdate:modelValue': onMultipleSelectionSet
|
|
})
|
|
];
|
|
|
|
child.unshift(
|
|
vue.h('th', { class: 'q-table--col-auto-width' }, content)
|
|
);
|
|
}
|
|
|
|
return [
|
|
vue.h('tr', {
|
|
class: props.tableHeaderClass,
|
|
style: props.tableHeaderStyle
|
|
}, child)
|
|
]
|
|
}
|
|
|
|
function getHeaderScope (data) {
|
|
Object.assign(data, {
|
|
cols: computedCols.value,
|
|
sort,
|
|
colsMap: computedColsMap.value,
|
|
color: props.color,
|
|
dark: isDark.value,
|
|
dense: props.dense
|
|
});
|
|
|
|
if (multipleSelection.value === true) {
|
|
injectProp(
|
|
data,
|
|
'selected',
|
|
() => headerSelectedValue.value,
|
|
onMultipleSelectionSet
|
|
);
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
function onMultipleSelectionSet (val) {
|
|
if (someRowsSelected.value === true) {
|
|
val = false;
|
|
}
|
|
|
|
updateSelection(
|
|
computedRows.value.map(getRowKey.value),
|
|
computedRows.value,
|
|
val
|
|
);
|
|
}
|
|
|
|
const navIcon = vue.computed(() => {
|
|
const ico = [
|
|
props.iconFirstPage || $q.iconSet.table.firstPage,
|
|
props.iconPrevPage || $q.iconSet.table.prevPage,
|
|
props.iconNextPage || $q.iconSet.table.nextPage,
|
|
props.iconLastPage || $q.iconSet.table.lastPage
|
|
];
|
|
return $q.lang.rtl === true ? ico.reverse() : ico
|
|
});
|
|
|
|
function getBottomDiv () {
|
|
if (props.hideBottom === true) {
|
|
return
|
|
}
|
|
|
|
if (nothingToDisplay.value === true) {
|
|
if (props.hideNoData === true) {
|
|
return
|
|
}
|
|
|
|
const message = props.loading === true
|
|
? props.loadingLabel || $q.lang.table.loading
|
|
: (props.filter ? props.noResultsLabel || $q.lang.table.noResults : props.noDataLabel || $q.lang.table.noData);
|
|
|
|
const noData = slots[ 'no-data' ];
|
|
const children = noData !== void 0
|
|
? [ noData({ message, icon: $q.iconSet.table.warning, filter: props.filter }) ]
|
|
: [
|
|
vue.h(QIcon, {
|
|
class: 'q-table__bottom-nodata-icon',
|
|
name: $q.iconSet.table.warning
|
|
}),
|
|
message
|
|
];
|
|
|
|
return vue.h('div', { class: bottomClass + ' q-table__bottom--nodata' }, children)
|
|
}
|
|
|
|
const bottom = slots.bottom;
|
|
|
|
if (bottom !== void 0) {
|
|
return vue.h('div', { class: bottomClass }, [ bottom(marginalsScope.value) ])
|
|
}
|
|
|
|
const child = props.hideSelectedBanner !== true && hasSelectionMode.value === true && rowsSelectedNumber.value > 0
|
|
? [
|
|
vue.h('div', { class: 'q-table__control' }, [
|
|
vue.h('div', [
|
|
(props.selectedRowsLabel || $q.lang.table.selectedRecords)(rowsSelectedNumber.value)
|
|
])
|
|
])
|
|
]
|
|
: [];
|
|
|
|
if (props.hidePagination !== true) {
|
|
return vue.h('div', {
|
|
class: bottomClass + ' justify-end'
|
|
}, getPaginationDiv(child))
|
|
}
|
|
|
|
if (child.length !== 0) {
|
|
return vue.h('div', { class: bottomClass }, child)
|
|
}
|
|
}
|
|
|
|
function onPagSelection (pag) {
|
|
setPagination({
|
|
page: 1,
|
|
rowsPerPage: pag.value
|
|
});
|
|
}
|
|
|
|
function getPaginationDiv (child) {
|
|
let control;
|
|
const
|
|
{ rowsPerPage } = computedPagination.value,
|
|
paginationLabel = props.paginationLabel || $q.lang.table.pagination,
|
|
paginationSlot = slots.pagination,
|
|
hasOpts = props.rowsPerPageOptions.length > 1;
|
|
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__separator col' })
|
|
);
|
|
|
|
if (hasOpts === true) {
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__control' }, [
|
|
vue.h('span', { class: 'q-table__bottom-item' }, [
|
|
props.rowsPerPageLabel || $q.lang.table.recordsPerPage
|
|
]),
|
|
vue.h(QSelect, {
|
|
class: 'q-table__select inline q-table__bottom-item',
|
|
color: props.color,
|
|
modelValue: rowsPerPage,
|
|
options: computedRowsPerPageOptions.value,
|
|
displayValue: rowsPerPage === 0
|
|
? $q.lang.table.allRows
|
|
: rowsPerPage,
|
|
dark: isDark.value,
|
|
borderless: true,
|
|
dense: true,
|
|
optionsDense: true,
|
|
optionsCover: true,
|
|
'onUpdate:modelValue': onPagSelection
|
|
})
|
|
])
|
|
);
|
|
}
|
|
|
|
if (paginationSlot !== void 0) {
|
|
control = paginationSlot(marginalsScope.value);
|
|
}
|
|
else {
|
|
control = [
|
|
vue.h('span', rowsPerPage !== 0 ? { class: 'q-table__bottom-item' } : {}, [
|
|
rowsPerPage
|
|
? paginationLabel(firstRowIndex.value + 1, Math.min(lastRowIndex.value, computedRowsNumber.value), computedRowsNumber.value)
|
|
: paginationLabel(1, filteredSortedRowsNumber.value, computedRowsNumber.value)
|
|
])
|
|
];
|
|
|
|
if (rowsPerPage !== 0 && pagesNumber.value > 1) {
|
|
const btnProps = {
|
|
color: props.color,
|
|
round: true,
|
|
dense: true,
|
|
flat: true
|
|
};
|
|
|
|
if (props.dense === true) {
|
|
btnProps.size = 'sm';
|
|
}
|
|
|
|
pagesNumber.value > 2 && control.push(
|
|
vue.h(QBtn, {
|
|
key: 'pgFirst',
|
|
...btnProps,
|
|
icon: navIcon.value[ 0 ],
|
|
disable: isFirstPage.value,
|
|
onClick: firstPage
|
|
})
|
|
);
|
|
|
|
control.push(
|
|
vue.h(QBtn, {
|
|
key: 'pgPrev',
|
|
...btnProps,
|
|
icon: navIcon.value[ 1 ],
|
|
disable: isFirstPage.value,
|
|
onClick: prevPage
|
|
}),
|
|
|
|
vue.h(QBtn, {
|
|
key: 'pgNext',
|
|
...btnProps,
|
|
icon: navIcon.value[ 2 ],
|
|
disable: isLastPage.value,
|
|
onClick: nextPage
|
|
})
|
|
);
|
|
|
|
pagesNumber.value > 2 && control.push(
|
|
vue.h(QBtn, {
|
|
key: 'pgLast',
|
|
...btnProps,
|
|
icon: navIcon.value[ 3 ],
|
|
disable: isLastPage.value,
|
|
onClick: lastPage
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
child.push(
|
|
vue.h('div', { class: 'q-table__control' }, control)
|
|
);
|
|
|
|
return child
|
|
}
|
|
|
|
function getGridHeader () {
|
|
const child = props.gridHeader === true
|
|
? [
|
|
vue.h('table', { class: 'q-table' }, [
|
|
getTHead()
|
|
])
|
|
]
|
|
: (
|
|
props.loading === true && slots.loading === void 0
|
|
? getProgress()
|
|
: void 0
|
|
);
|
|
|
|
return vue.h('div', { class: 'q-table__middle' }, child)
|
|
}
|
|
|
|
function getGridBody () {
|
|
const item = slots.item !== void 0
|
|
? slots.item
|
|
: scope => {
|
|
const child = scope.cols.map(
|
|
col => vue.h('div', { class: 'q-table__grid-item-row' }, [
|
|
vue.h('div', { class: 'q-table__grid-item-title' }, [ col.label ]),
|
|
vue.h('div', { class: 'q-table__grid-item-value' }, [ col.value ])
|
|
])
|
|
);
|
|
|
|
if (hasSelectionMode.value === true) {
|
|
const slot = slots[ 'body-selection' ];
|
|
const content = slot !== void 0
|
|
? slot(scope)
|
|
: [
|
|
vue.h(QCheckbox, {
|
|
modelValue: scope.selected,
|
|
color: props.color,
|
|
dark: isDark.value,
|
|
dense: props.dense,
|
|
'onUpdate:modelValue': (adding, evt) => {
|
|
updateSelection([ scope.key ], [ scope.row ], adding, evt);
|
|
}
|
|
})
|
|
];
|
|
|
|
child.unshift(
|
|
vue.h('div', { class: 'q-table__grid-item-row' }, content),
|
|
vue.h(QSeparator, { dark: isDark.value })
|
|
);
|
|
}
|
|
|
|
const data = {
|
|
class: [
|
|
'q-table__grid-item-card' + cardDefaultClass.value,
|
|
props.cardClass
|
|
],
|
|
style: props.cardStyle
|
|
};
|
|
|
|
if (
|
|
props.onRowClick !== void 0
|
|
|| props.onRowDblclick !== void 0
|
|
) {
|
|
data.class[ 0 ] += ' cursor-pointer';
|
|
|
|
if (props.onRowClick !== void 0) {
|
|
data.onClick = evt => {
|
|
emit('RowClick', evt, scope.row, scope.pageIndex);
|
|
};
|
|
}
|
|
|
|
if (props.onRowDblclick !== void 0) {
|
|
data.onDblclick = evt => {
|
|
emit('RowDblclick', evt, scope.row, scope.pageIndex);
|
|
};
|
|
}
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: 'q-table__grid-item col-xs-12 col-sm-6 col-md-4 col-lg-3'
|
|
+ (scope.selected === true ? ' q-table__grid-item--selected' : '')
|
|
}, [
|
|
vue.h('div', data, child)
|
|
])
|
|
};
|
|
|
|
return vue.h('div', {
|
|
class: [
|
|
'q-table__grid-content row',
|
|
props.cardContainerClass
|
|
],
|
|
style: props.cardContainerStyle
|
|
}, computedRows.value.map((row, pageIndex) => {
|
|
return item(getBodyScope({
|
|
key: getRowKey.value(row),
|
|
row,
|
|
pageIndex
|
|
}))
|
|
}))
|
|
}
|
|
|
|
// expose public methods and needed computed props
|
|
Object.assign(vm.proxy, {
|
|
requestServerInteraction,
|
|
setPagination,
|
|
firstPage,
|
|
prevPage,
|
|
nextPage,
|
|
lastPage,
|
|
isRowSelected,
|
|
clearSelection,
|
|
isRowExpanded,
|
|
setExpanded,
|
|
sort,
|
|
resetVirtualScroll,
|
|
scrollTo,
|
|
getCellValue
|
|
});
|
|
|
|
injectMultipleProps(vm.proxy, {
|
|
filteredSortedRows: () => filteredSortedRows.value,
|
|
computedRows: () => computedRows.value,
|
|
computedRowsNumber: () => computedRowsNumber.value
|
|
});
|
|
|
|
return () => {
|
|
const child = [ getTopDiv() ];
|
|
const data = { ref: rootRef, class: containerClass.value };
|
|
|
|
if (props.grid === true) {
|
|
child.push(getGridHeader());
|
|
}
|
|
else {
|
|
Object.assign(data, {
|
|
class: [ data.class, props.cardClass ],
|
|
style: props.cardStyle
|
|
});
|
|
}
|
|
|
|
child.push(
|
|
getBody(),
|
|
getBottomDiv()
|
|
);
|
|
|
|
if (props.loading === true && slots.loading !== void 0) {
|
|
child.push(
|
|
slots.loading()
|
|
);
|
|
}
|
|
|
|
return vue.h('div', data, child)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QTr = createComponent({
|
|
name: 'QTr',
|
|
|
|
props: {
|
|
props: Object,
|
|
noHover: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() =>
|
|
'q-tr'
|
|
+ (props.props === void 0 || props.props.header === true ? '' : ' ' + props.props.__trClass)
|
|
+ (props.noHover === true ? ' q-tr--no-hover' : '')
|
|
);
|
|
|
|
return () => vue.h('tr', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QTd = createComponent({
|
|
name: 'QTd',
|
|
|
|
props: {
|
|
props: Object,
|
|
autoWidth: Boolean,
|
|
noHover: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const classes = vue.computed(() =>
|
|
'q-td' + (props.autoWidth === true ? ' q-table--col-auto-width' : '')
|
|
+ (props.noHover === true ? ' q-td--no-hover' : '')
|
|
+ ' '
|
|
);
|
|
|
|
return () => {
|
|
if (props.props === void 0) {
|
|
return vue.h('td', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
|
|
const name = vm.vnode.key;
|
|
const col = (
|
|
(props.props.colsMap !== void 0 ? props.props.colsMap[ name ] : null)
|
|
|| props.props.col
|
|
);
|
|
|
|
if (col === void 0) { return }
|
|
|
|
const { row } = props.props;
|
|
|
|
return vue.h('td', {
|
|
class: classes.value + col.__tdClass(row),
|
|
style: col.__tdStyle(row)
|
|
}, hSlot(slots.default))
|
|
}
|
|
}
|
|
});
|
|
|
|
var QRouteTab = createComponent({
|
|
name: 'QRouteTab',
|
|
|
|
props: {
|
|
...useRouterLinkProps,
|
|
...useTabProps
|
|
},
|
|
|
|
emits: useTabEmits,
|
|
|
|
setup (props, { slots, emit }) {
|
|
const routeData = useRouterLink({
|
|
useDisableForRouterLinkProps: false
|
|
});
|
|
|
|
const { renderTab, $tabs } = useTab(
|
|
props,
|
|
slots,
|
|
emit,
|
|
{
|
|
exact: vue.computed(() => props.exact),
|
|
...routeData
|
|
}
|
|
);
|
|
|
|
vue.watch(() => `${ props.name } | ${ props.exact } | ${ (routeData.resolvedLink.value || {}).href }`, () => {
|
|
$tabs.verifyRouteModel();
|
|
});
|
|
|
|
return () => renderTab(routeData.linkTag.value, routeData.linkAttrs.value)
|
|
}
|
|
});
|
|
|
|
function getViewByModel (model, withSeconds) {
|
|
if (model.hour !== null) {
|
|
if (model.minute === null) {
|
|
return 'minute'
|
|
}
|
|
else if (withSeconds === true && model.second === null) {
|
|
return 'second'
|
|
}
|
|
}
|
|
|
|
return 'hour'
|
|
}
|
|
|
|
function getCurrentTime () {
|
|
const d = new Date();
|
|
|
|
return {
|
|
hour: d.getHours(),
|
|
minute: d.getMinutes(),
|
|
second: d.getSeconds(),
|
|
millisecond: d.getMilliseconds()
|
|
}
|
|
}
|
|
|
|
var QTime = createComponent({
|
|
name: 'QTime',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
...useFormProps,
|
|
...useDatetimeProps,
|
|
|
|
mask: {
|
|
default: null
|
|
},
|
|
|
|
format24h: {
|
|
type: Boolean,
|
|
default: null
|
|
},
|
|
|
|
defaultDate: {
|
|
type: String,
|
|
validator: v => /^-?[\d]+\/[0-1]\d\/[0-3]\d$/.test(v)
|
|
},
|
|
|
|
options: Function,
|
|
hourOptions: Array,
|
|
minuteOptions: Array,
|
|
secondOptions: Array,
|
|
|
|
withSeconds: Boolean,
|
|
nowBtn: Boolean
|
|
},
|
|
|
|
emits: useDatetimeEmits,
|
|
|
|
setup (props, { slots, emit }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { $q } = vm.proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
const { tabindex, headerClass, getLocale, getCurrentDate } = useDatetime(props, $q);
|
|
|
|
const formAttrs = useFormAttrs(props);
|
|
const injectFormInput = useFormInject(formAttrs);
|
|
|
|
let draggingClockRect, dragCache;
|
|
|
|
const clockRef = vue.ref(null);
|
|
|
|
const mask = vue.computed(() => getMask());
|
|
const locale = vue.computed(() => getLocale());
|
|
|
|
const defaultDateModel = vue.computed(() => getDefaultDateModel());
|
|
|
|
const model = __splitDate(
|
|
props.modelValue,
|
|
mask.value, // initial mask
|
|
locale.value, // initial locale
|
|
props.calendar,
|
|
defaultDateModel.value
|
|
);
|
|
|
|
const view = vue.ref(getViewByModel(model));
|
|
const innerModel = vue.ref(model);
|
|
const isAM = vue.ref(model.hour === null || model.hour < 12);
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-time q-time--${ props.landscape === true ? 'landscape' : 'portrait' }`
|
|
+ (isDark.value === true ? ' q-time--dark q-dark' : '')
|
|
+ (props.disable === true ? ' disabled' : (props.readonly === true ? ' q-time--readonly' : ''))
|
|
+ (props.bordered === true ? ' q-time--bordered' : '')
|
|
+ (props.square === true ? ' q-time--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-time--flat no-shadow' : '')
|
|
);
|
|
|
|
const stringModel = vue.computed(() => {
|
|
const time = innerModel.value;
|
|
|
|
return {
|
|
hour: time.hour === null
|
|
? '--'
|
|
: (
|
|
computedFormat24h.value === true
|
|
? pad(time.hour)
|
|
: String(
|
|
isAM.value === true
|
|
? (time.hour === 0 ? 12 : time.hour)
|
|
: (time.hour > 12 ? time.hour - 12 : time.hour)
|
|
)
|
|
),
|
|
minute: time.minute === null
|
|
? '--'
|
|
: pad(time.minute),
|
|
second: time.second === null
|
|
? '--'
|
|
: pad(time.second)
|
|
}
|
|
});
|
|
|
|
const computedFormat24h = vue.computed(() => (
|
|
props.format24h !== null
|
|
? props.format24h
|
|
: $q.lang.date.format24h
|
|
));
|
|
|
|
const pointerStyle = vue.computed(() => {
|
|
const
|
|
forHour = view.value === 'hour',
|
|
divider = forHour === true ? 12 : 60,
|
|
amount = innerModel.value[ view.value ],
|
|
degrees = Math.round(amount * (360 / divider)) - 180;
|
|
|
|
let transform = `rotate(${ degrees }deg) translateX(-50%)`;
|
|
|
|
if (
|
|
forHour === true
|
|
&& computedFormat24h.value === true
|
|
&& innerModel.value.hour >= 12
|
|
) {
|
|
transform += ' scale(.7)';
|
|
}
|
|
|
|
return { transform }
|
|
});
|
|
|
|
const minLink = vue.computed(() => innerModel.value.hour !== null);
|
|
const secLink = vue.computed(() => minLink.value === true && innerModel.value.minute !== null);
|
|
|
|
const hourInSelection = vue.computed(() => (
|
|
props.hourOptions !== void 0
|
|
? val => props.hourOptions.includes(val)
|
|
: (
|
|
props.options !== void 0
|
|
? val => props.options(val, null, null)
|
|
: null
|
|
)
|
|
));
|
|
|
|
const minuteInSelection = vue.computed(() => (
|
|
props.minuteOptions !== void 0
|
|
? val => props.minuteOptions.includes(val)
|
|
: (
|
|
props.options !== void 0
|
|
? val => props.options(innerModel.value.hour, val, null)
|
|
: null
|
|
)
|
|
));
|
|
|
|
const secondInSelection = vue.computed(() => (
|
|
props.secondOptions !== void 0
|
|
? val => props.secondOptions.includes(val)
|
|
: (
|
|
props.options !== void 0
|
|
? val => props.options(innerModel.value.hour, innerModel.value.minute, val)
|
|
: null
|
|
)
|
|
));
|
|
|
|
const validHours = vue.computed(() => {
|
|
if (hourInSelection.value === null) {
|
|
return null
|
|
}
|
|
|
|
const am = getValidValues(0, 11, hourInSelection.value);
|
|
const pm = getValidValues(12, 11, hourInSelection.value);
|
|
return { am, pm, values: am.values.concat(pm.values) }
|
|
});
|
|
|
|
const validMinutes = vue.computed(() => (
|
|
minuteInSelection.value !== null
|
|
? getValidValues(0, 59, minuteInSelection.value)
|
|
: null
|
|
));
|
|
|
|
const validSeconds = vue.computed(() => (
|
|
secondInSelection.value !== null
|
|
? getValidValues(0, 59, secondInSelection.value)
|
|
: null
|
|
));
|
|
|
|
const viewValidOptions = vue.computed(() => {
|
|
switch (view.value) {
|
|
case 'hour':
|
|
return validHours.value
|
|
case 'minute':
|
|
return validMinutes.value
|
|
case 'second':
|
|
return validSeconds.value
|
|
}
|
|
});
|
|
|
|
const positions = vue.computed(() => {
|
|
let start, end, offset = 0, step = 1;
|
|
const values = viewValidOptions.value !== null
|
|
? viewValidOptions.value.values
|
|
: void 0;
|
|
|
|
if (view.value === 'hour') {
|
|
if (computedFormat24h.value === true) {
|
|
start = 0;
|
|
end = 23;
|
|
}
|
|
else {
|
|
start = 0;
|
|
end = 11;
|
|
|
|
if (isAM.value === false) {
|
|
offset = 12;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
start = 0;
|
|
end = 55;
|
|
step = 5;
|
|
}
|
|
|
|
const pos = [];
|
|
|
|
for (let val = start, index = start; val <= end; val += step, index++) {
|
|
const
|
|
actualVal = val + offset,
|
|
disable = values !== void 0 && values.includes(actualVal) === false,
|
|
label = view.value === 'hour' && val === 0
|
|
? (computedFormat24h.value === true ? '00' : '12')
|
|
: val;
|
|
|
|
pos.push({ val: actualVal, index, disable, label });
|
|
}
|
|
|
|
return pos
|
|
});
|
|
|
|
const clockDirectives = vue.computed(() => {
|
|
return [ [
|
|
TouchPan,
|
|
onPan,
|
|
void 0,
|
|
{
|
|
stop: true,
|
|
prevent: true,
|
|
mouse: true
|
|
}
|
|
] ]
|
|
});
|
|
|
|
vue.watch(() => props.modelValue, v => {
|
|
const model = __splitDate(
|
|
v,
|
|
mask.value,
|
|
locale.value,
|
|
props.calendar,
|
|
defaultDateModel.value
|
|
);
|
|
|
|
if (
|
|
model.dateHash !== innerModel.value.dateHash
|
|
|| model.timeHash !== innerModel.value.timeHash
|
|
) {
|
|
innerModel.value = model;
|
|
|
|
if (model.hour === null) {
|
|
view.value = 'hour';
|
|
}
|
|
else {
|
|
isAM.value = model.hour < 12;
|
|
}
|
|
}
|
|
});
|
|
|
|
vue.watch([ mask, locale ], () => {
|
|
vue.nextTick(() => {
|
|
updateValue();
|
|
});
|
|
});
|
|
|
|
function setNow () {
|
|
const date = {
|
|
...getCurrentDate(),
|
|
...getCurrentTime()
|
|
};
|
|
|
|
updateValue(date);
|
|
Object.assign(innerModel.value, date); // reset any pending changes to innerModel
|
|
|
|
view.value = 'hour';
|
|
}
|
|
|
|
function getValidValues (start, count, testFn) {
|
|
const values = Array.apply(null, { length: count + 1 })
|
|
.map((_, index) => {
|
|
const i = index + start;
|
|
return {
|
|
index: i,
|
|
val: testFn(i) === true // force boolean
|
|
}
|
|
})
|
|
.filter(v => v.val === true)
|
|
.map(v => v.index);
|
|
|
|
return {
|
|
min: values[ 0 ],
|
|
max: values[ values.length - 1 ],
|
|
values,
|
|
threshold: count + 1
|
|
}
|
|
}
|
|
|
|
function getWheelDist (a, b, threshold) {
|
|
const diff = Math.abs(a - b);
|
|
return Math.min(diff, threshold - diff)
|
|
}
|
|
|
|
function getNormalizedClockValue (val, { min, max, values, threshold }) {
|
|
if (val === min) {
|
|
return min
|
|
}
|
|
|
|
if (val < min || val > max) {
|
|
return getWheelDist(val, min, threshold) <= getWheelDist(val, max, threshold)
|
|
? min
|
|
: max
|
|
}
|
|
|
|
const
|
|
index = values.findIndex(v => val <= v),
|
|
before = values[ index - 1 ],
|
|
after = values[ index ];
|
|
|
|
return val - before <= after - val
|
|
? before
|
|
: after
|
|
}
|
|
|
|
function getMask () {
|
|
return props.calendar !== 'persian' && props.mask !== null
|
|
? props.mask
|
|
: `HH:mm${ props.withSeconds === true ? ':ss' : '' }`
|
|
}
|
|
|
|
function getDefaultDateModel () {
|
|
if (typeof props.defaultDate !== 'string') {
|
|
const date = getCurrentDate(true);
|
|
date.dateHash = getDayHash(date);
|
|
return date
|
|
}
|
|
|
|
return __splitDate(props.defaultDate, 'YYYY/MM/DD', void 0, props.calendar)
|
|
}
|
|
|
|
function shouldAbortInteraction () {
|
|
return vmIsDestroyed(vm) === true
|
|
// if we have limited options, can we actually set any?
|
|
|| (
|
|
viewValidOptions.value !== null
|
|
&& (
|
|
viewValidOptions.value.values.length === 0
|
|
|| (
|
|
view.value === 'hour' && computedFormat24h.value !== true
|
|
&& validHours.value[ isAM.value === true ? 'am' : 'pm' ].values.length === 0
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
function getClockRect () {
|
|
const
|
|
clock = clockRef.value,
|
|
{ top, left, width } = clock.getBoundingClientRect(),
|
|
dist = width / 2;
|
|
|
|
return {
|
|
top: top + dist,
|
|
left: left + dist,
|
|
dist: dist * 0.7
|
|
}
|
|
}
|
|
|
|
function onPan (event) {
|
|
if (shouldAbortInteraction() === true) {
|
|
return
|
|
}
|
|
|
|
if (event.isFirst === true) {
|
|
draggingClockRect = getClockRect();
|
|
dragCache = updateClock(event.evt, draggingClockRect);
|
|
return
|
|
}
|
|
|
|
dragCache = updateClock(event.evt, draggingClockRect, dragCache);
|
|
|
|
if (event.isFinal === true) {
|
|
draggingClockRect = false;
|
|
dragCache = null;
|
|
goToNextView();
|
|
}
|
|
}
|
|
|
|
function goToNextView () {
|
|
if (view.value === 'hour') {
|
|
view.value = 'minute';
|
|
}
|
|
else if (props.withSeconds && view.value === 'minute') {
|
|
view.value = 'second';
|
|
}
|
|
}
|
|
|
|
function updateClock (evt, clockRect, cacheVal) {
|
|
const
|
|
pos = position(evt),
|
|
height = Math.abs(pos.top - clockRect.top),
|
|
distance = Math.sqrt(
|
|
Math.pow(Math.abs(pos.top - clockRect.top), 2)
|
|
+ Math.pow(Math.abs(pos.left - clockRect.left), 2)
|
|
);
|
|
|
|
let
|
|
val,
|
|
angle = Math.asin(height / distance) * (180 / Math.PI);
|
|
|
|
if (pos.top < clockRect.top) {
|
|
angle = clockRect.left < pos.left ? 90 - angle : 270 + angle;
|
|
}
|
|
else {
|
|
angle = clockRect.left < pos.left ? angle + 90 : 270 - angle;
|
|
}
|
|
|
|
if (view.value === 'hour') {
|
|
val = angle / 30;
|
|
|
|
if (validHours.value !== null) {
|
|
const am = computedFormat24h.value !== true
|
|
? isAM.value === true
|
|
: (
|
|
validHours.value.am.values.length !== 0 && validHours.value.pm.values.length !== 0
|
|
? distance >= clockRect.dist
|
|
: validHours.value.am.values.length !== 0
|
|
);
|
|
|
|
val = getNormalizedClockValue(
|
|
val + (am === true ? 0 : 12),
|
|
validHours.value[ am === true ? 'am' : 'pm' ]
|
|
);
|
|
}
|
|
else {
|
|
val = Math.round(val);
|
|
|
|
if (computedFormat24h.value === true) {
|
|
if (distance < clockRect.dist) {
|
|
if (val < 12) {
|
|
val += 12;
|
|
}
|
|
}
|
|
else if (val === 12) {
|
|
val = 0;
|
|
}
|
|
}
|
|
else if (isAM.value === true && val === 12) {
|
|
val = 0;
|
|
}
|
|
else if (isAM.value === false && val !== 12) {
|
|
val += 12;
|
|
}
|
|
}
|
|
|
|
if (computedFormat24h.value === true) {
|
|
isAM.value = val < 12;
|
|
}
|
|
}
|
|
else {
|
|
val = Math.round(angle / 6) % 60;
|
|
|
|
if (view.value === 'minute' && validMinutes.value !== null) {
|
|
val = getNormalizedClockValue(val, validMinutes.value);
|
|
}
|
|
else if (view.value === 'second' && validSeconds.value !== null) {
|
|
val = getNormalizedClockValue(val, validSeconds.value);
|
|
}
|
|
}
|
|
|
|
if (cacheVal !== val) {
|
|
setModel[ view.value ](val);
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
const setView = {
|
|
hour () { view.value = 'hour'; },
|
|
minute () { view.value = 'minute'; },
|
|
second () { view.value = 'second'; }
|
|
};
|
|
|
|
function setAmOnKey (e) {
|
|
e.keyCode === 13 && setAm();
|
|
}
|
|
|
|
function setPmOnKey (e) {
|
|
e.keyCode === 13 && setPm();
|
|
}
|
|
|
|
function onClick (evt) {
|
|
if (shouldAbortInteraction() !== true) {
|
|
// onMousedown() has already updated the offset
|
|
// (on desktop only, through mousedown event)
|
|
if ($q.platform.is.desktop !== true) {
|
|
updateClock(evt, getClockRect());
|
|
}
|
|
|
|
goToNextView();
|
|
}
|
|
}
|
|
|
|
function onMousedown (evt) {
|
|
if (shouldAbortInteraction() !== true) {
|
|
updateClock(evt, getClockRect());
|
|
}
|
|
}
|
|
|
|
function onKeyupHour (e) {
|
|
if (e.keyCode === 13) { // ENTER
|
|
view.value = 'hour';
|
|
}
|
|
else if ([ 37, 39 ].includes(e.keyCode)) {
|
|
const payload = e.keyCode === 37 ? -1 : 1;
|
|
|
|
if (validHours.value !== null) {
|
|
const values = computedFormat24h.value === true
|
|
? validHours.value.values
|
|
: validHours.value[ isAM.value === true ? 'am' : 'pm' ].values;
|
|
|
|
if (values.length === 0) { return }
|
|
|
|
if (innerModel.value.hour === null) {
|
|
setHour(values[ 0 ]);
|
|
}
|
|
else {
|
|
const index = (
|
|
values.length
|
|
+ values.indexOf(innerModel.value.hour)
|
|
+ payload
|
|
) % values.length;
|
|
|
|
setHour(values[ index ]);
|
|
}
|
|
}
|
|
else {
|
|
const
|
|
wrap = computedFormat24h.value === true ? 24 : 12,
|
|
offset = computedFormat24h.value !== true && isAM.value === false ? 12 : 0,
|
|
val = innerModel.value.hour === null ? -payload : innerModel.value.hour;
|
|
|
|
setHour(offset + (24 + val + payload) % wrap);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onKeyupMinute (e) {
|
|
if (e.keyCode === 13) { // ENTER
|
|
view.value = 'minute';
|
|
}
|
|
else if ([ 37, 39 ].includes(e.keyCode)) {
|
|
const payload = e.keyCode === 37 ? -1 : 1;
|
|
|
|
if (validMinutes.value !== null) {
|
|
const values = validMinutes.value.values;
|
|
|
|
if (values.length === 0) { return }
|
|
|
|
if (innerModel.value.minute === null) {
|
|
setMinute(values[ 0 ]);
|
|
}
|
|
else {
|
|
const index = (
|
|
values.length
|
|
+ values.indexOf(innerModel.value.minute)
|
|
+ payload
|
|
) % values.length;
|
|
|
|
setMinute(values[ index ]);
|
|
}
|
|
}
|
|
else {
|
|
const val = innerModel.value.minute === null ? -payload : innerModel.value.minute;
|
|
setMinute((60 + val + payload) % 60);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onKeyupSecond (e) {
|
|
if (e.keyCode === 13) { // ENTER
|
|
view.value = 'second';
|
|
}
|
|
else if ([ 37, 39 ].includes(e.keyCode)) {
|
|
const payload = e.keyCode === 37 ? -1 : 1;
|
|
|
|
if (validSeconds.value !== null) {
|
|
const values = validSeconds.value.values;
|
|
|
|
if (values.length === 0) { return }
|
|
|
|
if (innerModel.value.seconds === null) {
|
|
setSecond(values[ 0 ]);
|
|
}
|
|
else {
|
|
const index = (
|
|
values.length
|
|
+ values.indexOf(innerModel.value.second)
|
|
+ payload
|
|
) % values.length;
|
|
|
|
setSecond(values[ index ]);
|
|
}
|
|
}
|
|
else {
|
|
const val = innerModel.value.second === null ? -payload : innerModel.value.second;
|
|
setSecond((60 + val + payload) % 60);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setHour (hour) {
|
|
if (innerModel.value.hour !== hour) {
|
|
innerModel.value.hour = hour;
|
|
verifyAndUpdate();
|
|
}
|
|
}
|
|
|
|
function setMinute (minute) {
|
|
if (innerModel.value.minute !== minute) {
|
|
innerModel.value.minute = minute;
|
|
verifyAndUpdate();
|
|
}
|
|
}
|
|
|
|
function setSecond (second) {
|
|
if (innerModel.value.second !== second) {
|
|
innerModel.value.second = second;
|
|
verifyAndUpdate();
|
|
}
|
|
}
|
|
|
|
const setModel = {
|
|
hour: setHour,
|
|
minute: setMinute,
|
|
second: setSecond
|
|
};
|
|
|
|
function setAm () {
|
|
if (isAM.value === false) {
|
|
isAM.value = true;
|
|
|
|
if (innerModel.value.hour !== null) {
|
|
innerModel.value.hour -= 12;
|
|
verifyAndUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
function setPm () {
|
|
if (isAM.value === true) {
|
|
isAM.value = false;
|
|
|
|
if (innerModel.value.hour !== null) {
|
|
innerModel.value.hour += 12;
|
|
verifyAndUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyAndUpdate () {
|
|
if (hourInSelection.value !== null && hourInSelection.value(innerModel.value.hour) !== true) {
|
|
innerModel.value = __splitDate();
|
|
view.value = 'hour';
|
|
return
|
|
}
|
|
|
|
if (minuteInSelection.value !== null && minuteInSelection.value(innerModel.value.minute) !== true) {
|
|
innerModel.value.minute = null;
|
|
innerModel.value.second = null;
|
|
view.value = 'minute';
|
|
return
|
|
}
|
|
|
|
if (props.withSeconds === true && secondInSelection.value !== null && secondInSelection.value(innerModel.value.second) !== true) {
|
|
innerModel.value.second = null;
|
|
view.value = 'second';
|
|
return
|
|
}
|
|
|
|
if (innerModel.value.hour === null || innerModel.value.minute === null || (props.withSeconds === true && innerModel.value.second === null)) {
|
|
return
|
|
}
|
|
|
|
updateValue();
|
|
}
|
|
|
|
function updateValue (obj) {
|
|
const date = Object.assign({ ...innerModel.value }, obj);
|
|
|
|
const val = props.calendar === 'persian'
|
|
? pad(date.hour) + ':'
|
|
+ pad(date.minute)
|
|
+ (props.withSeconds === true ? ':' + pad(date.second) : '')
|
|
: formatDate(
|
|
new Date(
|
|
date.year,
|
|
date.month === null ? null : date.month - 1,
|
|
date.day,
|
|
date.hour,
|
|
date.minute,
|
|
date.second,
|
|
date.millisecond
|
|
),
|
|
mask.value,
|
|
locale.value,
|
|
date.year,
|
|
date.timezoneOffset
|
|
);
|
|
|
|
date.changed = val !== props.modelValue;
|
|
emit('update:modelValue', val, date);
|
|
}
|
|
|
|
function getHeader () {
|
|
const label = [
|
|
vue.h('div', {
|
|
class: 'q-time__link '
|
|
+ (view.value === 'hour' ? 'q-time__link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
onClick: setView.hour,
|
|
onKeyup: onKeyupHour
|
|
}, stringModel.value.hour),
|
|
|
|
vue.h('div', ':'),
|
|
|
|
vue.h(
|
|
'div',
|
|
minLink.value === true
|
|
? {
|
|
class: 'q-time__link '
|
|
+ (view.value === 'minute' ? 'q-time__link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
onKeyup: onKeyupMinute,
|
|
onClick: setView.minute
|
|
}
|
|
: { class: 'q-time__link' },
|
|
stringModel.value.minute
|
|
)
|
|
];
|
|
|
|
if (props.withSeconds === true) {
|
|
label.push(
|
|
vue.h('div', ':'),
|
|
|
|
vue.h(
|
|
'div',
|
|
secLink.value === true
|
|
? {
|
|
class: 'q-time__link '
|
|
+ (view.value === 'second' ? 'q-time__link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
onKeyup: onKeyupSecond,
|
|
onClick: setView.second
|
|
}
|
|
: { class: 'q-time__link' },
|
|
stringModel.value.second
|
|
)
|
|
);
|
|
}
|
|
|
|
const child = [
|
|
vue.h('div', {
|
|
class: 'q-time__header-label row items-center no-wrap',
|
|
dir: 'ltr'
|
|
}, label)
|
|
];
|
|
|
|
computedFormat24h.value === false && child.push(
|
|
vue.h('div', {
|
|
class: 'q-time__header-ampm column items-between no-wrap'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-time__link '
|
|
+ (isAM.value === true ? 'q-time__link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
onClick: setAm,
|
|
onKeyup: setAmOnKey
|
|
}, 'AM'),
|
|
|
|
vue.h('div', {
|
|
class: 'q-time__link '
|
|
+ (isAM.value !== true ? 'q-time__link--active' : 'cursor-pointer'),
|
|
tabindex: tabindex.value,
|
|
onClick: setPm,
|
|
onKeyup: setPmOnKey
|
|
}, 'PM')
|
|
])
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: 'q-time__header flex flex-center no-wrap ' + headerClass.value
|
|
}, child)
|
|
}
|
|
|
|
function getClock () {
|
|
const current = innerModel.value[ view.value ];
|
|
|
|
return vue.h('div', {
|
|
class: 'q-time__content col relative-position'
|
|
}, [
|
|
vue.h(vue.Transition, {
|
|
name: 'q-transition--scale'
|
|
}, () => vue.h('div', {
|
|
key: 'clock' + view.value,
|
|
class: 'q-time__container-parent absolute-full'
|
|
}, [
|
|
vue.h('div', {
|
|
ref: clockRef,
|
|
class: 'q-time__container-child fit overflow-hidden'
|
|
}, [
|
|
vue.withDirectives(
|
|
vue.h('div', {
|
|
class: 'q-time__clock cursor-pointer non-selectable',
|
|
onClick,
|
|
onMousedown
|
|
}, [
|
|
vue.h('div', { class: 'q-time__clock-circle fit' }, [
|
|
vue.h('div', {
|
|
class: 'q-time__clock-pointer'
|
|
+ (innerModel.value[ view.value ] === null ? ' hidden' : (props.color !== void 0 ? ` text-${ props.color }` : '')),
|
|
style: pointerStyle.value
|
|
}),
|
|
|
|
positions.value.map(pos => vue.h('div', {
|
|
class: `q-time__clock-position row flex-center q-time__clock-pos-${ pos.index }`
|
|
+ (pos.val === current
|
|
? ' q-time__clock-position--active ' + headerClass.value
|
|
: (pos.disable === true ? ' q-time__clock-position--disable' : ''))
|
|
}, [ vue.h('span', pos.label) ]))
|
|
])
|
|
]),
|
|
clockDirectives.value
|
|
)
|
|
])
|
|
])),
|
|
|
|
props.nowBtn === true ? vue.h(QBtn, {
|
|
class: 'q-time__now-button absolute',
|
|
icon: $q.iconSet.datetime.now,
|
|
unelevated: true,
|
|
size: 'sm',
|
|
round: true,
|
|
color: props.color,
|
|
textColor: props.textColor,
|
|
tabindex: tabindex.value,
|
|
onClick: setNow
|
|
}) : null
|
|
])
|
|
}
|
|
|
|
// expose public method
|
|
vm.proxy.setNow = setNow;
|
|
|
|
return () => {
|
|
const child = [ getClock() ];
|
|
|
|
const def = hSlot(slots.default);
|
|
def !== void 0 && child.push(
|
|
vue.h('div', { class: 'q-time__actions' }, def)
|
|
);
|
|
|
|
if (props.name !== void 0 && props.disable !== true) {
|
|
injectFormInput(child, 'push');
|
|
}
|
|
|
|
return vue.h('div', {
|
|
class: classes.value,
|
|
tabindex: -1
|
|
}, [
|
|
getHeader(),
|
|
vue.h('div', { class: 'q-time__main col overflow-auto' }, child)
|
|
])
|
|
}
|
|
}
|
|
});
|
|
|
|
var QTimeline = createComponent({
|
|
name: 'QTimeline',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
color: {
|
|
type: String,
|
|
default: 'primary'
|
|
},
|
|
side: {
|
|
type: String,
|
|
default: 'right',
|
|
validator: v => [ 'left', 'right' ].includes(v)
|
|
},
|
|
layout: {
|
|
type: String,
|
|
default: 'dense',
|
|
validator: v => [ 'dense', 'comfortable', 'loose' ].includes(v)
|
|
}
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const vm = vue.getCurrentInstance();
|
|
const isDark = useDark(props, vm.proxy.$q);
|
|
|
|
vue.provide(timelineKey, props);
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-timeline q-timeline--${ props.layout } q-timeline--${ props.layout }--${ props.side }`
|
|
+ (isDark.value === true ? ' q-timeline--dark' : '')
|
|
);
|
|
|
|
return () => vue.h('ul', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QTimelineEntry = createComponent({
|
|
name: 'QTimelineEntry',
|
|
|
|
props: {
|
|
heading: Boolean,
|
|
tag: {
|
|
type: String,
|
|
default: 'h3'
|
|
},
|
|
side: {
|
|
type: String,
|
|
default: 'right',
|
|
validator: v => [ 'left', 'right' ].includes(v)
|
|
},
|
|
|
|
icon: String,
|
|
avatar: String,
|
|
|
|
color: String,
|
|
|
|
title: String,
|
|
subtitle: String,
|
|
body: String
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const $timeline = vue.inject(timelineKey, emptyRenderFn);
|
|
if ($timeline === emptyRenderFn) {
|
|
console.error('QTimelineEntry needs to be child of QTimeline');
|
|
return emptyRenderFn
|
|
}
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-timeline__entry q-timeline__entry--${ props.side }`
|
|
+ (props.icon !== void 0 || props.avatar !== void 0 ? ' q-timeline__entry--icon' : '')
|
|
);
|
|
|
|
const dotClass = vue.computed(() =>
|
|
`q-timeline__dot text-${ props.color || $timeline.color }`
|
|
);
|
|
|
|
const reverse = vue.computed(() =>
|
|
$timeline.layout === 'comfortable' && $timeline.side === 'left'
|
|
);
|
|
|
|
return () => {
|
|
const child = hUniqueSlot(slots.default, []);
|
|
|
|
if (props.body !== void 0) {
|
|
child.unshift(props.body);
|
|
}
|
|
|
|
if (props.heading === true) {
|
|
const content = [
|
|
vue.h('div'),
|
|
vue.h('div'),
|
|
vue.h(
|
|
props.tag,
|
|
{ class: 'q-timeline__heading-title' },
|
|
child
|
|
)
|
|
];
|
|
|
|
return vue.h('div', {
|
|
class: 'q-timeline__heading'
|
|
}, reverse.value === true ? content.reverse() : content)
|
|
}
|
|
|
|
let dot;
|
|
|
|
if (props.icon !== void 0) {
|
|
dot = [
|
|
vue.h(QIcon, {
|
|
class: 'row items-center justify-center',
|
|
name: props.icon
|
|
})
|
|
];
|
|
}
|
|
else if (props.avatar !== void 0) {
|
|
dot = [
|
|
vue.h('img', {
|
|
class: 'q-timeline__dot-img',
|
|
src: props.avatar
|
|
})
|
|
];
|
|
}
|
|
|
|
const content = [
|
|
vue.h('div', { class: 'q-timeline__subtitle' }, [
|
|
vue.h('span', {}, hSlot(slots.subtitle, [ props.subtitle ]))
|
|
]),
|
|
|
|
vue.h('div', { class: dotClass.value }, dot),
|
|
|
|
vue.h('div', { class: 'q-timeline__content' }, [
|
|
vue.h('h6', { class: 'q-timeline__title' }, hSlot(slots.title, [ props.title ]))
|
|
].concat(child))
|
|
];
|
|
|
|
return vue.h('li', {
|
|
class: classes.value
|
|
}, reverse.value === true ? content.reverse() : content)
|
|
}
|
|
}
|
|
});
|
|
|
|
var QToolbar = createComponent({
|
|
name: 'QToolbar',
|
|
|
|
props: {
|
|
inset: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() =>
|
|
'q-toolbar row no-wrap items-center'
|
|
+ (props.inset === true ? ' q-toolbar--inset' : '')
|
|
);
|
|
|
|
return () => vue.h('div', { class: classes.value, role: 'toolbar' }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
var QToolbarTitle = createComponent({
|
|
name: 'QToolbarTitle',
|
|
|
|
props: {
|
|
shrink: Boolean
|
|
},
|
|
|
|
setup (props, { slots }) {
|
|
const classes = vue.computed(() =>
|
|
'q-toolbar__title ellipsis'
|
|
+ (props.shrink === true ? ' col-shrink' : '')
|
|
);
|
|
|
|
return () => vue.h('div', { class: classes.value }, hSlot(slots.default))
|
|
}
|
|
});
|
|
|
|
const tickStrategyOptions = [ 'none', 'strict', 'leaf', 'leaf-filtered' ];
|
|
|
|
var QTree = createComponent({
|
|
name: 'QTree',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
nodes: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
nodeKey: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
labelKey: {
|
|
type: String,
|
|
default: 'label'
|
|
},
|
|
childrenKey: {
|
|
type: String,
|
|
default: 'children'
|
|
},
|
|
|
|
dense: Boolean,
|
|
|
|
color: String,
|
|
controlColor: String,
|
|
textColor: String,
|
|
selectedColor: String,
|
|
|
|
icon: String,
|
|
|
|
tickStrategy: {
|
|
type: String,
|
|
default: 'none',
|
|
validator: v => tickStrategyOptions.includes(v)
|
|
},
|
|
ticked: Array, // v-model:ticked
|
|
expanded: Array, // v-model:expanded
|
|
selected: {}, // v-model:selected
|
|
|
|
noSelectionUnset: Boolean,
|
|
|
|
defaultExpandAll: Boolean,
|
|
accordion: Boolean,
|
|
|
|
filter: String,
|
|
filterMethod: Function,
|
|
|
|
duration: Number,
|
|
noConnectors: Boolean,
|
|
noTransition: Boolean,
|
|
|
|
noNodesLabel: String,
|
|
noResultsLabel: String
|
|
},
|
|
|
|
emits: [
|
|
'update:expanded',
|
|
'update:ticked',
|
|
'update:selected',
|
|
'lazyLoad',
|
|
'afterShow',
|
|
'afterHide'
|
|
],
|
|
|
|
setup (props, { slots, emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
const lazy = vue.ref({});
|
|
const innerTicked = vue.ref(props.ticked || []);
|
|
const innerExpanded = vue.ref(props.expanded || []);
|
|
|
|
let blurTargets = {};
|
|
|
|
vue.onBeforeUpdate(() => {
|
|
blurTargets = {};
|
|
});
|
|
|
|
const classes = vue.computed(() =>
|
|
`q-tree q-tree--${ props.dense === true ? 'dense' : 'standard' }`
|
|
+ (props.noConnectors === true ? ' q-tree--no-connectors' : '')
|
|
+ (isDark.value === true ? ' q-tree--dark' : '')
|
|
+ (props.color !== void 0 ? ` text-${ props.color }` : '')
|
|
);
|
|
|
|
const hasSelection = vue.computed(() => props.selected !== void 0);
|
|
|
|
const computedIcon = vue.computed(() => props.icon || $q.iconSet.tree.icon);
|
|
|
|
const computedControlColor = vue.computed(() => props.controlColor || props.color);
|
|
|
|
const textColorClass = vue.computed(() => (
|
|
props.textColor !== void 0
|
|
? ` text-${ props.textColor }`
|
|
: ''
|
|
));
|
|
|
|
const selectedColorClass = vue.computed(() => {
|
|
const color = props.selectedColor || props.color;
|
|
return color ? ` text-${ color }` : ''
|
|
});
|
|
|
|
const computedFilterMethod = vue.computed(() => (
|
|
props.filterMethod !== void 0
|
|
? props.filterMethod
|
|
: (node, filter) => {
|
|
const filt = filter.toLowerCase();
|
|
return node[ props.labelKey ]
|
|
&& node[ props.labelKey ].toLowerCase().indexOf(filt) > -1
|
|
}
|
|
));
|
|
|
|
const meta = vue.computed(() => {
|
|
const meta = {};
|
|
|
|
const travel = (node, parent) => {
|
|
const tickStrategy = node.tickStrategy || (parent ? parent.tickStrategy : props.tickStrategy);
|
|
const
|
|
key = node[ props.nodeKey ],
|
|
isParent = node[ props.childrenKey ] && Array.isArray(node[ props.childrenKey ]) && node[ props.childrenKey ].length !== 0,
|
|
selectable = node.disabled !== true && hasSelection.value === true && node.selectable !== false,
|
|
expandable = node.disabled !== true && node.expandable !== false,
|
|
hasTicking = tickStrategy !== 'none',
|
|
strictTicking = tickStrategy === 'strict',
|
|
leafFilteredTicking = tickStrategy === 'leaf-filtered',
|
|
leafTicking = tickStrategy === 'leaf' || tickStrategy === 'leaf-filtered';
|
|
|
|
let tickable = node.disabled !== true && node.tickable !== false;
|
|
if (leafTicking === true && tickable === true && parent && parent.tickable !== true) {
|
|
tickable = false;
|
|
}
|
|
|
|
let localLazy = node.lazy;
|
|
if (
|
|
localLazy === true
|
|
&& lazy.value[ key ] !== void 0
|
|
&& Array.isArray(node[ props.childrenKey ]) === true
|
|
) {
|
|
localLazy = lazy.value[ key ];
|
|
}
|
|
|
|
const m = {
|
|
key,
|
|
parent,
|
|
isParent,
|
|
lazy: localLazy,
|
|
disabled: node.disabled,
|
|
link: node.disabled !== true && (selectable === true || (expandable === true && (isParent === true || localLazy === true))),
|
|
children: [],
|
|
matchesFilter: props.filter ? computedFilterMethod.value(node, props.filter) : true,
|
|
|
|
selected: key === props.selected && selectable === true,
|
|
selectable,
|
|
expanded: isParent === true ? innerExpanded.value.includes(key) : false,
|
|
expandable,
|
|
noTick: node.noTick === true || (strictTicking !== true && localLazy && localLazy !== 'loaded'),
|
|
tickable,
|
|
tickStrategy,
|
|
hasTicking,
|
|
strictTicking,
|
|
leafFilteredTicking,
|
|
leafTicking,
|
|
ticked: strictTicking === true
|
|
? innerTicked.value.includes(key)
|
|
: (isParent === true ? false : innerTicked.value.includes(key))
|
|
};
|
|
|
|
meta[ key ] = m;
|
|
|
|
if (isParent === true) {
|
|
m.children = node[ props.childrenKey ].map(n => travel(n, m));
|
|
|
|
if (props.filter) {
|
|
if (m.matchesFilter !== true) {
|
|
m.matchesFilter = m.children.some(n => n.matchesFilter);
|
|
}
|
|
else if (
|
|
m.noTick !== true
|
|
&& m.disabled !== true
|
|
&& m.tickable === true
|
|
&& leafFilteredTicking === true
|
|
&& m.children.every(n => n.matchesFilter !== true || n.noTick === true || n.tickable !== true) === true
|
|
) {
|
|
m.tickable = false;
|
|
}
|
|
}
|
|
|
|
if (m.matchesFilter === true) {
|
|
if (m.noTick !== true && strictTicking !== true && m.children.every(n => n.noTick) === true) {
|
|
m.noTick = true;
|
|
}
|
|
|
|
if (leafTicking) {
|
|
m.ticked = false;
|
|
m.indeterminate = m.children.some(node => node.indeterminate === true);
|
|
m.tickable = m.tickable === true && m.children.some(node => node.tickable);
|
|
|
|
if (m.indeterminate !== true) {
|
|
const sel = m.children
|
|
.reduce((acc, meta) => (meta.ticked === true ? acc + 1 : acc), 0);
|
|
|
|
if (sel === m.children.length) {
|
|
m.ticked = true;
|
|
}
|
|
else if (sel > 0) {
|
|
m.indeterminate = true;
|
|
}
|
|
}
|
|
|
|
if (m.indeterminate === true) {
|
|
m.indeterminateNextState = m.children
|
|
.every(meta => meta.tickable !== true || meta.ticked !== true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return m
|
|
};
|
|
|
|
props.nodes.forEach(node => travel(node, null));
|
|
return meta
|
|
});
|
|
|
|
vue.watch(() => props.ticked, val => {
|
|
innerTicked.value = val;
|
|
});
|
|
|
|
vue.watch(() => props.expanded, val => {
|
|
innerExpanded.value = val;
|
|
});
|
|
|
|
function getNodeByKey (key) {
|
|
const reduce = [].reduce;
|
|
|
|
const find = (result, node) => {
|
|
if (result || !node) {
|
|
return result
|
|
}
|
|
if (Array.isArray(node) === true) {
|
|
return reduce.call(Object(node), find, result)
|
|
}
|
|
if (node[ props.nodeKey ] === key) {
|
|
return node
|
|
}
|
|
if (node[ props.childrenKey ]) {
|
|
return find(null, node[ props.childrenKey ])
|
|
}
|
|
};
|
|
|
|
return find(null, props.nodes)
|
|
}
|
|
|
|
function getTickedNodes () {
|
|
return innerTicked.value.map(key => getNodeByKey(key))
|
|
}
|
|
|
|
function getExpandedNodes () {
|
|
return innerExpanded.value.map(key => getNodeByKey(key))
|
|
}
|
|
|
|
function isExpanded (key) {
|
|
return key && meta.value[ key ]
|
|
? meta.value[ key ].expanded
|
|
: false
|
|
}
|
|
|
|
function collapseAll () {
|
|
if (props.expanded !== void 0) {
|
|
emit('update:expanded', []);
|
|
}
|
|
else {
|
|
innerExpanded.value = [];
|
|
}
|
|
}
|
|
|
|
function expandAll () {
|
|
const expanded = [];
|
|
const travel = node => {
|
|
if (node[ props.childrenKey ] && node[ props.childrenKey ].length !== 0) {
|
|
if (node.expandable !== false && node.disabled !== true) {
|
|
expanded.push(node[ props.nodeKey ]);
|
|
node[ props.childrenKey ].forEach(travel);
|
|
}
|
|
}
|
|
};
|
|
|
|
props.nodes.forEach(travel);
|
|
|
|
if (props.expanded !== void 0) {
|
|
emit('update:expanded', expanded);
|
|
}
|
|
else {
|
|
innerExpanded.value = expanded;
|
|
}
|
|
}
|
|
|
|
function setExpanded (key, state, node = getNodeByKey(key), m = meta.value[ key ]) {
|
|
if (m.lazy && m.lazy !== 'loaded') {
|
|
if (m.lazy === 'loading') {
|
|
return
|
|
}
|
|
|
|
lazy.value[ key ] = 'loading';
|
|
if (Array.isArray(node[ props.childrenKey ]) !== true) {
|
|
node[ props.childrenKey ] = [];
|
|
}
|
|
emit('lazyLoad', {
|
|
node,
|
|
key,
|
|
done: children => {
|
|
lazy.value[ key ] = 'loaded';
|
|
node[ props.childrenKey ] = Array.isArray(children) === true ? children : [];
|
|
vue.nextTick(() => {
|
|
const localMeta = meta.value[ key ];
|
|
if (localMeta && localMeta.isParent === true) {
|
|
localSetExpanded(key, true);
|
|
}
|
|
});
|
|
},
|
|
fail: () => {
|
|
delete lazy.value[ key ];
|
|
if (node[ props.childrenKey ].length === 0) {
|
|
delete node[ props.childrenKey ];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
else if (m.isParent === true && m.expandable === true) {
|
|
localSetExpanded(key, state);
|
|
}
|
|
}
|
|
|
|
function localSetExpanded (key, state) {
|
|
let target = innerExpanded.value;
|
|
const shouldEmit = props.expanded !== void 0;
|
|
|
|
if (shouldEmit === true) {
|
|
target = target.slice();
|
|
}
|
|
|
|
if (state) {
|
|
if (props.accordion) {
|
|
if (meta.value[ key ]) {
|
|
const collapse = [];
|
|
if (meta.value[ key ].parent) {
|
|
meta.value[ key ].parent.children.forEach(m => {
|
|
if (m.key !== key && m.expandable === true) {
|
|
collapse.push(m.key);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
props.nodes.forEach(node => {
|
|
const k = node[ props.nodeKey ];
|
|
if (k !== key) {
|
|
collapse.push(k);
|
|
}
|
|
});
|
|
}
|
|
if (collapse.length !== 0) {
|
|
target = target.filter(k => collapse.includes(k) === false);
|
|
}
|
|
}
|
|
}
|
|
|
|
target = target.concat([ key ])
|
|
.filter((key, index, self) => self.indexOf(key) === index);
|
|
}
|
|
else {
|
|
target = target.filter(k => k !== key);
|
|
}
|
|
|
|
if (shouldEmit === true) {
|
|
emit('update:expanded', target);
|
|
}
|
|
else {
|
|
innerExpanded.value = target;
|
|
}
|
|
}
|
|
|
|
function isTicked (key) {
|
|
return key && meta.value[ key ]
|
|
? meta.value[ key ].ticked
|
|
: false
|
|
}
|
|
|
|
function setTicked (keys, state) {
|
|
let target = innerTicked.value;
|
|
const shouldEmit = props.ticked !== void 0;
|
|
|
|
if (shouldEmit === true) {
|
|
target = target.slice();
|
|
}
|
|
|
|
if (state) {
|
|
target = target.concat(keys)
|
|
.filter((key, index, self) => self.indexOf(key) === index);
|
|
}
|
|
else {
|
|
target = target.filter(k => keys.includes(k) === false);
|
|
}
|
|
|
|
if (shouldEmit === true) {
|
|
emit('update:ticked', target);
|
|
}
|
|
}
|
|
|
|
function getSlotScope (node, meta, key) {
|
|
const scope = { tree: proxy, node, key, color: props.color, dark: isDark.value };
|
|
|
|
injectProp(
|
|
scope,
|
|
'expanded',
|
|
() => { return meta.expanded },
|
|
val => { val !== meta.expanded && setExpanded(key, val); }
|
|
);
|
|
|
|
injectProp(
|
|
scope,
|
|
'ticked',
|
|
() => { return meta.ticked },
|
|
val => { val !== meta.ticked && setTicked([ key ], val); }
|
|
);
|
|
|
|
return scope
|
|
}
|
|
|
|
function getChildren (nodes) {
|
|
return (
|
|
props.filter
|
|
? nodes.filter(n => meta.value[ n[ props.nodeKey ] ].matchesFilter)
|
|
: nodes
|
|
).map(child => getNode(child))
|
|
}
|
|
|
|
function getNodeMedia (node) {
|
|
if (node.icon !== void 0) {
|
|
return vue.h(QIcon, {
|
|
class: 'q-tree__icon q-mr-sm',
|
|
name: node.icon,
|
|
color: node.iconColor
|
|
})
|
|
}
|
|
const src = node.img || node.avatar;
|
|
if (src) {
|
|
return vue.h('img', {
|
|
class: `q-tree__${ node.img ? 'img' : 'avatar' } q-mr-sm`,
|
|
src
|
|
})
|
|
}
|
|
}
|
|
|
|
function onShow () {
|
|
emit('afterShow');
|
|
}
|
|
|
|
function onHide () {
|
|
emit('afterHide');
|
|
}
|
|
|
|
function getNode (node) {
|
|
const
|
|
key = node[ props.nodeKey ],
|
|
m = meta.value[ key ],
|
|
header = node.header
|
|
? slots[ `header-${ node.header }` ] || slots[ 'default-header' ]
|
|
: slots[ 'default-header' ];
|
|
|
|
const children = m.isParent === true
|
|
? getChildren(node[ props.childrenKey ])
|
|
: [];
|
|
|
|
const isParent = children.length !== 0 || (m.lazy && m.lazy !== 'loaded');
|
|
|
|
let body = node.body
|
|
? slots[ `body-${ node.body }` ] || slots[ 'default-body' ]
|
|
: slots[ 'default-body' ];
|
|
const slotScope = header !== void 0 || body !== void 0
|
|
? getSlotScope(node, m, key)
|
|
: null;
|
|
|
|
if (body !== void 0) {
|
|
body = vue.h('div', { class: 'q-tree__node-body relative-position' }, [
|
|
vue.h('div', { class: textColorClass.value }, [
|
|
body(slotScope)
|
|
])
|
|
]);
|
|
}
|
|
|
|
return vue.h('div', {
|
|
key,
|
|
class: 'q-tree__node relative-position'
|
|
+ ` q-tree__node--${ isParent === true ? 'parent' : 'child' }`
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-tree__node-header relative-position row no-wrap items-center'
|
|
+ (m.link === true ? ' q-tree__node--link q-hoverable q-focusable' : '')
|
|
+ (m.selected === true ? ' q-tree__node--selected' : '')
|
|
+ (m.disabled === true ? ' q-tree__node--disabled' : ''),
|
|
tabindex: m.link === true ? 0 : -1,
|
|
onClick: (e) => {
|
|
onClick(node, m, e);
|
|
},
|
|
onKeypress (e) {
|
|
if (shouldIgnoreKey(e) !== true) {
|
|
if (e.keyCode === 13) { onClick(node, m, e, true); }
|
|
else if (e.keyCode === 32) { onExpandClick(node, m, e, true); }
|
|
}
|
|
}
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-focus-helper',
|
|
tabindex: -1,
|
|
ref: el => { blurTargets[ m.key ] = el; }
|
|
}),
|
|
|
|
m.lazy === 'loading'
|
|
? vue.h(QSpinner, {
|
|
class: 'q-tree__spinner',
|
|
color: computedControlColor.value
|
|
})
|
|
: (
|
|
isParent === true
|
|
? vue.h(QIcon, {
|
|
class: 'q-tree__arrow'
|
|
+ (m.expanded === true ? ' q-tree__arrow--rotate' : ''),
|
|
name: computedIcon.value,
|
|
onClick (e) { onExpandClick(node, m, e); }
|
|
})
|
|
: null
|
|
),
|
|
|
|
m.hasTicking === true && m.noTick !== true
|
|
? vue.h(QCheckbox, {
|
|
class: 'q-tree__tickbox',
|
|
modelValue: m.indeterminate === true ? null : m.ticked,
|
|
color: computedControlColor.value,
|
|
dark: isDark.value,
|
|
dense: true,
|
|
keepColor: true,
|
|
disable: m.tickable !== true,
|
|
onKeydown: stopAndPrevent,
|
|
'onUpdate:modelValue': v => {
|
|
onTickedClick(m, v);
|
|
}
|
|
})
|
|
: null,
|
|
|
|
vue.h('div', {
|
|
class: 'q-tree__node-header-content col row no-wrap items-center'
|
|
+ (m.selected === true ? selectedColorClass.value : textColorClass.value)
|
|
}, [
|
|
header
|
|
? header(slotScope)
|
|
: [
|
|
getNodeMedia(node),
|
|
vue.h('div', node[ props.labelKey ])
|
|
]
|
|
])
|
|
]),
|
|
|
|
isParent === true
|
|
? (
|
|
props.noTransition === true
|
|
? vue.h('div', {
|
|
class: 'q-tree__node-collapsible' + textColorClass.value,
|
|
key: `${ key }__q`
|
|
}, [
|
|
body,
|
|
vue.h('div', {
|
|
class: 'q-tree__children'
|
|
+ (m.disabled === true ? ' q-tree__node--disabled' : '')
|
|
}, m.expanded ? children : null)
|
|
])
|
|
|
|
: vue.h(QSlideTransition, {
|
|
duration: props.duration,
|
|
onShow,
|
|
onHide
|
|
}, () => vue.withDirectives(
|
|
vue.h('div', {
|
|
class: 'q-tree__node-collapsible' + textColorClass.value,
|
|
key: `${ key }__q`
|
|
}, [
|
|
body,
|
|
vue.h('div', {
|
|
class: 'q-tree__children'
|
|
+ (m.disabled === true ? ' q-tree__node--disabled' : '')
|
|
}, children)
|
|
]),
|
|
[ [ vue.vShow, m.expanded ] ]
|
|
))
|
|
)
|
|
: body
|
|
])
|
|
}
|
|
|
|
function blur (key) {
|
|
const blurTarget = blurTargets[ key ];
|
|
blurTarget && blurTarget.focus();
|
|
}
|
|
|
|
function onClick (node, meta, e, keyboard) {
|
|
keyboard !== true && meta.selectable !== false && blur(meta.key);
|
|
|
|
if (hasSelection.value && meta.selectable) {
|
|
if (props.noSelectionUnset === false) {
|
|
emit('update:selected', meta.key !== props.selected ? meta.key : null);
|
|
}
|
|
else if (meta.key !== props.selected) {
|
|
emit('update:selected', meta.key === void 0 ? null : meta.key);
|
|
}
|
|
}
|
|
else {
|
|
onExpandClick(node, meta, e, keyboard);
|
|
}
|
|
|
|
if (typeof node.handler === 'function') {
|
|
node.handler(node);
|
|
}
|
|
}
|
|
|
|
function onExpandClick (node, meta, e, keyboard) {
|
|
if (e !== void 0) {
|
|
stopAndPrevent(e);
|
|
}
|
|
keyboard !== true && meta.selectable !== false && blur(meta.key);
|
|
setExpanded(meta.key, !meta.expanded, node, meta);
|
|
}
|
|
|
|
function onTickedClick (meta, state) {
|
|
if (meta.indeterminate === true) {
|
|
state = meta.indeterminateNextState;
|
|
}
|
|
if (meta.strictTicking) {
|
|
setTicked([ meta.key ], state);
|
|
}
|
|
else if (meta.leafTicking) {
|
|
const keys = [];
|
|
const travel = meta => {
|
|
if (meta.isParent) {
|
|
if (state !== true && meta.noTick !== true && meta.tickable === true) {
|
|
keys.push(meta.key);
|
|
}
|
|
if (meta.leafTicking === true) {
|
|
meta.children.forEach(travel);
|
|
}
|
|
}
|
|
else if (
|
|
meta.noTick !== true
|
|
&& meta.tickable === true
|
|
&& (meta.leafFilteredTicking !== true || meta.matchesFilter === true)
|
|
) {
|
|
keys.push(meta.key);
|
|
}
|
|
};
|
|
travel(meta);
|
|
setTicked(keys, state);
|
|
}
|
|
}
|
|
|
|
props.defaultExpandAll === true && expandAll();
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, {
|
|
getNodeByKey,
|
|
getTickedNodes,
|
|
getExpandedNodes,
|
|
isExpanded,
|
|
collapseAll,
|
|
expandAll,
|
|
setExpanded,
|
|
isTicked,
|
|
setTicked
|
|
});
|
|
|
|
return () => {
|
|
const children = getChildren(props.nodes);
|
|
|
|
return vue.h(
|
|
'div', {
|
|
class: classes.value
|
|
},
|
|
children.length === 0
|
|
? (
|
|
props.filter
|
|
? props.noResultsLabel || $q.lang.tree.noResults
|
|
: props.noNodesLabel || $q.lang.tree.noNodes
|
|
)
|
|
: children
|
|
)
|
|
}
|
|
}
|
|
});
|
|
|
|
function getProgressLabel (p) {
|
|
return (p * 100).toFixed(2) + '%'
|
|
}
|
|
|
|
const coreProps = {
|
|
...useDarkProps,
|
|
...useFileProps,
|
|
|
|
label: String,
|
|
|
|
color: String,
|
|
textColor: String,
|
|
|
|
square: Boolean,
|
|
flat: Boolean,
|
|
bordered: Boolean,
|
|
|
|
noThumbnails: Boolean,
|
|
autoUpload: Boolean,
|
|
hideUploadBtn: Boolean,
|
|
|
|
disable: Boolean,
|
|
readonly: Boolean
|
|
};
|
|
|
|
const coreEmits = [
|
|
...useFileEmits,
|
|
'start', 'finish', 'added', 'removed'
|
|
];
|
|
|
|
function getRenderer (getPlugin, expose) {
|
|
const vm = vue.getCurrentInstance();
|
|
const { props, slots, emit, proxy } = vm;
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
function updateFileStatus (file, status, uploadedSize) {
|
|
file.__status = status;
|
|
|
|
if (status === 'idle') {
|
|
file.__uploaded = 0;
|
|
file.__progress = 0;
|
|
file.__sizeLabel = humanStorageSize(file.size);
|
|
file.__progressLabel = '0.00%';
|
|
return
|
|
}
|
|
if (status === 'failed') {
|
|
proxy.$forceUpdate();
|
|
return
|
|
}
|
|
|
|
file.__uploaded = status === 'uploaded'
|
|
? file.size
|
|
: uploadedSize;
|
|
|
|
file.__progress = status === 'uploaded'
|
|
? 1
|
|
: Math.min(0.9999, file.__uploaded / file.size);
|
|
|
|
file.__progressLabel = getProgressLabel(file.__progress);
|
|
proxy.$forceUpdate();
|
|
}
|
|
|
|
const editable = vue.computed(() => props.disable !== true && props.readonly !== true);
|
|
const dnd = vue.ref(false);
|
|
|
|
const rootRef = vue.ref(null);
|
|
const inputRef = vue.ref(null);
|
|
|
|
const state = {
|
|
files: vue.ref([]),
|
|
queuedFiles: vue.ref([]),
|
|
uploadedFiles: vue.ref([]),
|
|
uploadedSize: vue.ref(0),
|
|
|
|
updateFileStatus,
|
|
isAlive: () => vmIsDestroyed(vm) === false
|
|
};
|
|
|
|
const {
|
|
pickFiles,
|
|
addFiles,
|
|
onDragover,
|
|
onDragleave,
|
|
processFiles,
|
|
getDndNode,
|
|
maxFilesNumber,
|
|
maxTotalSizeNumber
|
|
} = useFile({ editable, dnd, getFileInput, addFilesToQueue });
|
|
|
|
Object.assign(state, getPlugin({
|
|
props,
|
|
slots,
|
|
emit,
|
|
helpers: state,
|
|
exposeApi: obj => { Object.assign(state, obj); }
|
|
}));
|
|
|
|
if (state.isBusy === void 0) {
|
|
state.isBusy = vue.ref(false);
|
|
}
|
|
|
|
const uploadSize = vue.ref(0);
|
|
const uploadProgress = vue.computed(() => (
|
|
uploadSize.value === 0
|
|
? 0
|
|
: state.uploadedSize.value / uploadSize.value
|
|
));
|
|
const uploadProgressLabel = vue.computed(() => getProgressLabel(uploadProgress.value));
|
|
const uploadSizeLabel = vue.computed(() => humanStorageSize(uploadSize.value));
|
|
|
|
const canAddFiles = vue.computed(() =>
|
|
editable.value === true
|
|
&& state.isUploading.value !== true
|
|
// if single selection and no files are queued:
|
|
&& (props.multiple === true || state.queuedFiles.value.length === 0)
|
|
// if max-files is set and current number of files does not exceeds it:
|
|
&& (props.maxFiles === void 0 || state.files.value.length < maxFilesNumber.value)
|
|
// if max-total-size is set and current upload size does not exceeds it:
|
|
&& (props.maxTotalSize === void 0 || uploadSize.value < maxTotalSizeNumber.value)
|
|
);
|
|
|
|
const canUpload = vue.computed(() =>
|
|
editable.value === true
|
|
&& state.isBusy.value !== true
|
|
&& state.isUploading.value !== true
|
|
&& state.queuedFiles.value.length !== 0
|
|
);
|
|
|
|
vue.provide(uploaderKey, renderInput);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-uploader column no-wrap'
|
|
+ (isDark.value === true ? ' q-uploader--dark q-dark' : '')
|
|
+ (props.bordered === true ? ' q-uploader--bordered' : '')
|
|
+ (props.square === true ? ' q-uploader--square no-border-radius' : '')
|
|
+ (props.flat === true ? ' q-uploader--flat no-shadow' : '')
|
|
+ (props.disable === true ? ' disabled q-uploader--disable' : '')
|
|
+ (dnd.value === true ? ' q-uploader--dnd' : '')
|
|
);
|
|
|
|
const colorClass = vue.computed(() =>
|
|
'q-uploader__header'
|
|
+ (props.color !== void 0 ? ` bg-${ props.color }` : '')
|
|
+ (props.textColor !== void 0 ? ` text-${ props.textColor }` : '')
|
|
);
|
|
|
|
vue.watch(state.isUploading, (newVal, oldVal) => {
|
|
if (oldVal === false && newVal === true) {
|
|
emit('start');
|
|
}
|
|
else if (oldVal === true && newVal === false) {
|
|
emit('finish');
|
|
}
|
|
});
|
|
|
|
function reset () {
|
|
if (props.disable === false) {
|
|
state.abort();
|
|
state.uploadedSize.value = 0;
|
|
uploadSize.value = 0;
|
|
revokeImgURLs();
|
|
state.files.value = [];
|
|
state.queuedFiles.value = [];
|
|
state.uploadedFiles.value = [];
|
|
}
|
|
}
|
|
|
|
function removeUploadedFiles () {
|
|
if (props.disable === false) {
|
|
batchRemoveFiles([ 'uploaded' ], () => {
|
|
state.uploadedFiles.value = [];
|
|
});
|
|
}
|
|
}
|
|
|
|
function removeQueuedFiles () {
|
|
batchRemoveFiles([ 'idle', 'failed' ], ({ size }) => {
|
|
uploadSize.value -= size;
|
|
state.queuedFiles.value = [];
|
|
});
|
|
}
|
|
|
|
function batchRemoveFiles (statusList, cb) {
|
|
if (props.disable === true) {
|
|
return
|
|
}
|
|
|
|
const removed = {
|
|
files: [],
|
|
size: 0
|
|
};
|
|
|
|
const localFiles = state.files.value.filter(f => {
|
|
if (statusList.indexOf(f.__status) === -1) {
|
|
return true
|
|
}
|
|
|
|
removed.size += f.size;
|
|
removed.files.push(f);
|
|
|
|
f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src);
|
|
|
|
return false
|
|
});
|
|
|
|
if (removed.files.length !== 0) {
|
|
state.files.value = localFiles;
|
|
cb(removed);
|
|
emit('removed', removed.files);
|
|
}
|
|
}
|
|
|
|
function removeFile (file) {
|
|
if (props.disable) { return }
|
|
|
|
if (file.__status === 'uploaded') {
|
|
state.uploadedFiles.value = state.uploadedFiles.value.filter(f => f.__key !== file.__key);
|
|
}
|
|
else if (file.__status === 'uploading') {
|
|
file.__abort();
|
|
}
|
|
else {
|
|
uploadSize.value -= file.size;
|
|
}
|
|
|
|
state.files.value = state.files.value.filter(f => {
|
|
if (f.__key !== file.__key) {
|
|
return true
|
|
}
|
|
|
|
f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src);
|
|
|
|
return false
|
|
});
|
|
|
|
state.queuedFiles.value = state.queuedFiles.value.filter(f => f.__key !== file.__key);
|
|
emit('removed', [ file ]);
|
|
}
|
|
|
|
function revokeImgURLs () {
|
|
state.files.value.forEach(f => {
|
|
f.__img !== void 0 && window.URL.revokeObjectURL(f.__img.src);
|
|
});
|
|
}
|
|
|
|
function getFileInput () {
|
|
return inputRef.value
|
|
|| rootRef.value.getElementsByClassName('q-uploader__input')[ 0 ]
|
|
}
|
|
|
|
function addFilesToQueue (e, fileList) {
|
|
const localFiles = processFiles(e, fileList, state.files.value, true);
|
|
const fileInput = getFileInput();
|
|
|
|
if (fileInput !== void 0 && fileInput !== null) {
|
|
fileInput.value = '';
|
|
}
|
|
|
|
if (localFiles === void 0) { return }
|
|
|
|
localFiles.forEach(file => {
|
|
state.updateFileStatus(file, 'idle');
|
|
uploadSize.value += file.size;
|
|
|
|
if (props.noThumbnails !== true && file.type.toUpperCase().startsWith('IMAGE')) {
|
|
const img = new Image();
|
|
img.src = window.URL.createObjectURL(file);
|
|
file.__img = img;
|
|
}
|
|
});
|
|
|
|
state.files.value = state.files.value.concat(localFiles);
|
|
state.queuedFiles.value = state.queuedFiles.value.concat(localFiles);
|
|
emit('added', localFiles);
|
|
props.autoUpload === true && state.upload();
|
|
}
|
|
|
|
function upload () {
|
|
canUpload.value === true && state.upload();
|
|
}
|
|
|
|
function getBtn (show, icon, fn) {
|
|
if (show === true) {
|
|
const data = {
|
|
type: 'a',
|
|
key: icon,
|
|
icon: $q.iconSet.uploader[ icon ],
|
|
flat: true,
|
|
dense: true
|
|
};
|
|
|
|
let child = void 0;
|
|
|
|
if (icon === 'add') {
|
|
data.onClick = pickFiles;
|
|
child = renderInput;
|
|
}
|
|
else {
|
|
data.onClick = fn;
|
|
}
|
|
|
|
return vue.h(QBtn, data, child)
|
|
}
|
|
}
|
|
|
|
function renderInput () {
|
|
return vue.h('input', {
|
|
ref: inputRef,
|
|
class: 'q-uploader__input overflow-hidden absolute-full',
|
|
tabindex: -1,
|
|
type: 'file',
|
|
title: '', // try to remove default tooltip
|
|
accept: props.accept,
|
|
multiple: props.multiple === true ? 'multiple' : void 0,
|
|
capture: props.capture,
|
|
onMousedown: stop, // need to stop refocus from QBtn
|
|
onClick: pickFiles,
|
|
onChange: addFilesToQueue
|
|
})
|
|
}
|
|
|
|
function getHeader () {
|
|
if (slots.header !== void 0) {
|
|
return slots.header(publicApi)
|
|
}
|
|
|
|
return [
|
|
vue.h('div', {
|
|
class: 'q-uploader__header-content column'
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'flex flex-center no-wrap q-gutter-xs'
|
|
}, [
|
|
getBtn(state.queuedFiles.value.length !== 0, 'removeQueue', removeQueuedFiles),
|
|
getBtn(state.uploadedFiles.value.length !== 0, 'removeUploaded', removeUploadedFiles),
|
|
|
|
state.isUploading.value === true
|
|
? vue.h(QSpinner, { class: 'q-uploader__spinner' })
|
|
: null,
|
|
|
|
vue.h('div', { class: 'col column justify-center' }, [
|
|
props.label !== void 0
|
|
? vue.h('div', { class: 'q-uploader__title' }, [ props.label ])
|
|
: null,
|
|
|
|
vue.h('div', { class: 'q-uploader__subtitle' }, [
|
|
uploadSizeLabel.value + ' / ' + uploadProgressLabel.value
|
|
])
|
|
]),
|
|
|
|
getBtn(canAddFiles.value, 'add'),
|
|
getBtn(props.hideUploadBtn === false && canUpload.value === true, 'upload', state.upload),
|
|
getBtn(state.isUploading.value, 'clear', state.abort)
|
|
])
|
|
])
|
|
]
|
|
}
|
|
|
|
function getList () {
|
|
if (slots.list !== void 0) {
|
|
return slots.list(publicApi)
|
|
}
|
|
|
|
return state.files.value.map(file => vue.h('div', {
|
|
key: file.__key,
|
|
class: 'q-uploader__file relative-position'
|
|
+ (props.noThumbnails !== true && file.__img !== void 0 ? ' q-uploader__file--img' : '')
|
|
+ (
|
|
file.__status === 'failed'
|
|
? ' q-uploader__file--failed'
|
|
: (file.__status === 'uploaded' ? ' q-uploader__file--uploaded' : '')
|
|
),
|
|
style: props.noThumbnails !== true && file.__img !== void 0
|
|
? { backgroundImage: 'url("' + file.__img.src + '")' }
|
|
: null
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-uploader__file-header row flex-center no-wrap'
|
|
}, [
|
|
file.__status === 'failed'
|
|
? vue.h(QIcon, {
|
|
class: 'q-uploader__file-status',
|
|
name: $q.iconSet.type.negative,
|
|
color: 'negative'
|
|
})
|
|
: null,
|
|
|
|
vue.h('div', { class: 'q-uploader__file-header-content col' }, [
|
|
vue.h('div', { class: 'q-uploader__title' }, [ file.name ]),
|
|
vue.h('div', {
|
|
class: 'q-uploader__subtitle row items-center no-wrap'
|
|
}, [
|
|
file.__sizeLabel + ' / ' + file.__progressLabel
|
|
])
|
|
]),
|
|
|
|
file.__status === 'uploading'
|
|
? vue.h(QCircularProgress, {
|
|
value: file.__progress,
|
|
min: 0,
|
|
max: 1,
|
|
indeterminate: file.__progress === 0
|
|
})
|
|
: vue.h(QBtn, {
|
|
round: true,
|
|
dense: true,
|
|
flat: true,
|
|
icon: $q.iconSet.uploader[ file.__status === 'uploaded' ? 'done' : 'clear' ],
|
|
onClick: () => { removeFile(file); }
|
|
})
|
|
])
|
|
]))
|
|
}
|
|
|
|
vue.onBeforeUnmount(() => {
|
|
state.isUploading.value === true && state.abort();
|
|
state.files.value.length !== 0 && revokeImgURLs();
|
|
});
|
|
|
|
const publicApi = {};
|
|
|
|
for (const key in state) {
|
|
if (vue.isRef(state[ key ]) === true) {
|
|
injectProp(publicApi, key, () => state[ key ].value);
|
|
}
|
|
else { // method or non-computed prop
|
|
publicApi[ key ] = state[ key ];
|
|
}
|
|
}
|
|
|
|
Object.assign(publicApi, {
|
|
upload,
|
|
reset,
|
|
removeUploadedFiles,
|
|
removeQueuedFiles,
|
|
removeFile,
|
|
|
|
pickFiles,
|
|
addFiles
|
|
});
|
|
|
|
injectMultipleProps(publicApi, {
|
|
canAddFiles: () => canAddFiles.value,
|
|
canUpload: () => canUpload.value,
|
|
uploadSizeLabel: () => uploadSizeLabel.value,
|
|
uploadProgressLabel: () => uploadProgressLabel.value
|
|
});
|
|
|
|
// expose public api (methods & computed props)
|
|
expose({
|
|
...state,
|
|
|
|
upload,
|
|
reset,
|
|
removeUploadedFiles,
|
|
removeQueuedFiles,
|
|
removeFile,
|
|
|
|
pickFiles,
|
|
addFiles,
|
|
|
|
canAddFiles,
|
|
canUpload,
|
|
uploadSizeLabel,
|
|
uploadProgressLabel
|
|
});
|
|
|
|
return () => {
|
|
const children = [
|
|
vue.h('div', { class: colorClass.value }, getHeader()),
|
|
vue.h('div', { class: 'q-uploader__list scroll' }, getList()),
|
|
getDndNode('uploader')
|
|
];
|
|
|
|
state.isBusy.value === true && children.push(
|
|
vue.h('div', {
|
|
class: 'q-uploader__overlay absolute-full flex flex-center'
|
|
}, [ vue.h(QSpinner) ])
|
|
);
|
|
|
|
const data = { ref: rootRef, class: classes.value };
|
|
|
|
if (canAddFiles.value === true) {
|
|
Object.assign(data, { onDragover, onDragleave });
|
|
}
|
|
|
|
return vue.h('div', data, children)
|
|
}
|
|
}
|
|
|
|
const trueFn = () => true;
|
|
|
|
function getEmitsObject (emitsArray) {
|
|
const emitsObject = {};
|
|
|
|
emitsArray.forEach(val => {
|
|
emitsObject[ val ] = trueFn;
|
|
});
|
|
|
|
return emitsObject
|
|
}
|
|
|
|
const coreEmitsObject = getEmitsObject(coreEmits);
|
|
|
|
var createUploaderComponent = ({ name, props, emits, injectPlugin }) => createComponent({
|
|
name,
|
|
|
|
props: {
|
|
...coreProps,
|
|
...props
|
|
},
|
|
|
|
emits: isObject(emits) === true
|
|
? { ...coreEmitsObject, ...emits }
|
|
: [ ...coreEmits, ...emits ],
|
|
|
|
setup (_, { expose }) {
|
|
return getRenderer(injectPlugin, expose)
|
|
}
|
|
});
|
|
|
|
function getFn (prop) {
|
|
return typeof prop === 'function'
|
|
? prop
|
|
: () => prop
|
|
}
|
|
|
|
const props$2 = {
|
|
url: [ Function, String ],
|
|
method: {
|
|
type: [ Function, String ],
|
|
default: 'POST'
|
|
},
|
|
fieldName: {
|
|
type: [ Function, String ],
|
|
default: () => {
|
|
return file => file.name
|
|
}
|
|
},
|
|
headers: [ Function, Array ],
|
|
formFields: [ Function, Array ],
|
|
withCredentials: [ Function, Boolean ],
|
|
sendRaw: [ Function, Boolean ],
|
|
|
|
batch: [ Function, Boolean ],
|
|
factory: Function
|
|
};
|
|
|
|
const emits$1 = [ 'factoryFailed', 'uploaded', 'failed', 'uploading' ];
|
|
|
|
function injectPlugin ({ props, emit, helpers }) {
|
|
const xhrs = vue.ref([]);
|
|
const promises = vue.ref([]);
|
|
const workingThreads = vue.ref(0);
|
|
|
|
const xhrProps = vue.computed(() => ({
|
|
url: getFn(props.url),
|
|
method: getFn(props.method),
|
|
headers: getFn(props.headers),
|
|
formFields: getFn(props.formFields),
|
|
fieldName: getFn(props.fieldName),
|
|
withCredentials: getFn(props.withCredentials),
|
|
sendRaw: getFn(props.sendRaw),
|
|
batch: getFn(props.batch)
|
|
}));
|
|
|
|
const isUploading = vue.computed(() => workingThreads.value > 0);
|
|
const isBusy = vue.computed(() => promises.value.length !== 0);
|
|
|
|
let abortPromises;
|
|
|
|
function abort () {
|
|
xhrs.value.forEach(x => { x.abort(); });
|
|
|
|
if (promises.value.length !== 0) {
|
|
abortPromises = true;
|
|
}
|
|
}
|
|
|
|
function upload () {
|
|
const queue = helpers.queuedFiles.value.slice(0);
|
|
helpers.queuedFiles.value = [];
|
|
|
|
if (xhrProps.value.batch(queue)) {
|
|
runFactory(queue);
|
|
}
|
|
else {
|
|
queue.forEach(file => {
|
|
runFactory([ file ]);
|
|
});
|
|
}
|
|
}
|
|
|
|
function runFactory (files) {
|
|
workingThreads.value++;
|
|
|
|
if (typeof props.factory !== 'function') {
|
|
performUpload(files, {});
|
|
return
|
|
}
|
|
|
|
const res = props.factory(files);
|
|
|
|
if (!res) {
|
|
emit(
|
|
'factoryFailed',
|
|
new Error('QUploader: factory() does not return properly'),
|
|
files
|
|
);
|
|
workingThreads.value--;
|
|
}
|
|
else if (typeof res.catch === 'function' && typeof res.then === 'function') {
|
|
promises.value.push(res);
|
|
|
|
const failed = err => {
|
|
if (helpers.isAlive() === true) {
|
|
promises.value = promises.value.filter(p => p !== res);
|
|
|
|
if (promises.value.length === 0) {
|
|
abortPromises = false;
|
|
}
|
|
|
|
helpers.queuedFiles.value = helpers.queuedFiles.value.concat(files);
|
|
files.forEach(f => { helpers.updateFileStatus(f, 'failed'); });
|
|
|
|
emit('factoryFailed', err, files);
|
|
workingThreads.value--;
|
|
}
|
|
};
|
|
|
|
res.then(factory => {
|
|
if (abortPromises === true) {
|
|
failed(new Error('Aborted'));
|
|
}
|
|
else if (helpers.isAlive() === true) {
|
|
promises.value = promises.value.filter(p => p !== res);
|
|
performUpload(files, factory);
|
|
}
|
|
}).catch(failed);
|
|
}
|
|
else {
|
|
performUpload(files, res || {});
|
|
}
|
|
}
|
|
|
|
function performUpload (files, factory) {
|
|
const
|
|
form = new FormData(),
|
|
xhr = new XMLHttpRequest();
|
|
|
|
const getProp = (name, arg) => {
|
|
return factory[ name ] !== void 0
|
|
? getFn(factory[ name ])(arg)
|
|
: xhrProps.value[ name ](arg)
|
|
};
|
|
|
|
const url = getProp('url', files);
|
|
|
|
if (!url) {
|
|
console.error('q-uploader: invalid or no URL specified');
|
|
workingThreads.value--;
|
|
return
|
|
}
|
|
|
|
const fields = getProp('formFields', files);
|
|
fields !== void 0 && fields.forEach(field => {
|
|
form.append(field.name, field.value);
|
|
});
|
|
|
|
let
|
|
uploadIndex = 0,
|
|
uploadIndexSize = 0,
|
|
localUploadedSize = 0,
|
|
maxUploadSize = 0,
|
|
aborted;
|
|
|
|
xhr.upload.addEventListener('progress', e => {
|
|
if (aborted === true) { return }
|
|
|
|
const loaded = Math.min(maxUploadSize, e.loaded);
|
|
|
|
helpers.uploadedSize.value += loaded - localUploadedSize;
|
|
localUploadedSize = loaded;
|
|
|
|
let size = localUploadedSize - uploadIndexSize;
|
|
for (let i = uploadIndex; size > 0 && i < files.length; i++) {
|
|
const
|
|
file = files[ i ],
|
|
uploaded = size > file.size;
|
|
|
|
if (uploaded) {
|
|
size -= file.size;
|
|
uploadIndex++;
|
|
uploadIndexSize += file.size;
|
|
helpers.updateFileStatus(file, 'uploading', file.size);
|
|
}
|
|
else {
|
|
helpers.updateFileStatus(file, 'uploading', size);
|
|
return
|
|
}
|
|
}
|
|
}, false);
|
|
|
|
xhr.onreadystatechange = () => {
|
|
if (xhr.readyState < 4) {
|
|
return
|
|
}
|
|
|
|
if (xhr.status && xhr.status < 400) {
|
|
helpers.uploadedFiles.value = helpers.uploadedFiles.value.concat(files);
|
|
files.forEach(f => { helpers.updateFileStatus(f, 'uploaded'); });
|
|
emit('uploaded', { files, xhr });
|
|
}
|
|
else {
|
|
aborted = true;
|
|
helpers.uploadedSize.value -= localUploadedSize;
|
|
helpers.queuedFiles.value = helpers.queuedFiles.value.concat(files);
|
|
files.forEach(f => { helpers.updateFileStatus(f, 'failed'); });
|
|
emit('failed', { files, xhr });
|
|
}
|
|
|
|
workingThreads.value--;
|
|
xhrs.value = xhrs.value.filter(x => x !== xhr);
|
|
};
|
|
|
|
xhr.open(
|
|
getProp('method', files),
|
|
url
|
|
);
|
|
|
|
if (getProp('withCredentials', files) === true) {
|
|
xhr.withCredentials = true;
|
|
}
|
|
|
|
const headers = getProp('headers', files);
|
|
headers !== void 0 && headers.forEach(head => {
|
|
xhr.setRequestHeader(head.name, head.value);
|
|
});
|
|
|
|
const sendRaw = getProp('sendRaw', files);
|
|
|
|
files.forEach(file => {
|
|
helpers.updateFileStatus(file, 'uploading', 0);
|
|
if (sendRaw !== true) {
|
|
form.append(getProp('fieldName', file), file, file.name);
|
|
}
|
|
file.xhr = xhr;
|
|
file.__abort = () => { xhr.abort(); };
|
|
maxUploadSize += file.size;
|
|
});
|
|
|
|
emit('uploading', { files, xhr });
|
|
xhrs.value.push(xhr);
|
|
|
|
if (sendRaw === true) {
|
|
xhr.send(new Blob(files));
|
|
}
|
|
else {
|
|
xhr.send(form);
|
|
}
|
|
}
|
|
|
|
return {
|
|
isUploading,
|
|
isBusy,
|
|
|
|
abort,
|
|
upload
|
|
}
|
|
}
|
|
|
|
var xhrUploaderPlugin = {
|
|
name: 'QUploader',
|
|
props: props$2,
|
|
emits: emits$1,
|
|
injectPlugin
|
|
};
|
|
|
|
var QUploader = createUploaderComponent(xhrUploaderPlugin);
|
|
|
|
var QUploaderAddTrigger = createComponent({
|
|
name: 'QUploaderAddTrigger',
|
|
|
|
setup () {
|
|
const $trigger = vue.inject(uploaderKey, emptyRenderFn);
|
|
|
|
if ($trigger === emptyRenderFn) {
|
|
console.error('QUploaderAddTrigger needs to be child of QUploader');
|
|
}
|
|
|
|
return $trigger
|
|
}
|
|
});
|
|
|
|
var QVideo = createComponent({
|
|
name: 'QVideo',
|
|
|
|
props: {
|
|
...useRatioProps,
|
|
|
|
src: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
|
|
title: String,
|
|
|
|
fetchpriority: {
|
|
type: String,
|
|
default: 'auto'
|
|
},
|
|
loading: {
|
|
type: String,
|
|
default: 'eager'
|
|
},
|
|
referrerpolicy: {
|
|
type: String,
|
|
default: 'strict-origin-when-cross-origin'
|
|
}
|
|
},
|
|
|
|
setup (props) {
|
|
const ratioStyle = useRatio(props);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-video'
|
|
+ (props.ratio !== void 0 ? ' q-video--responsive' : '')
|
|
);
|
|
|
|
return () => vue.h('div', {
|
|
class: classes.value,
|
|
style: ratioStyle.value
|
|
}, [
|
|
vue.h('iframe', {
|
|
src: props.src,
|
|
title: props.title,
|
|
fetchpriority: props.fetchpriority,
|
|
loading: props.loading,
|
|
referrerpolicy: props.referrerpolicy,
|
|
frameborder: '0',
|
|
allowfullscreen: true
|
|
})
|
|
])
|
|
}
|
|
});
|
|
|
|
var components = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
QAjaxBar: QAjaxBar,
|
|
QAvatar: QAvatar,
|
|
QBadge: QBadge,
|
|
QBanner: QBanner,
|
|
QBar: QBar,
|
|
QBreadcrumbs: QBreadcrumbs,
|
|
QBreadcrumbsEl: QBreadcrumbsEl,
|
|
QBtn: QBtn,
|
|
QBtnDropdown: QBtnDropdown,
|
|
QBtnGroup: QBtnGroup,
|
|
QBtnToggle: QBtnToggle,
|
|
QCard: QCard,
|
|
QCardSection: QCardSection,
|
|
QCardActions: QCardActions,
|
|
QCarousel: QCarousel,
|
|
QCarouselSlide: QCarouselSlide,
|
|
QCarouselControl: QCarouselControl,
|
|
QChatMessage: QChatMessage,
|
|
QCheckbox: QCheckbox,
|
|
QChip: QChip,
|
|
QCircularProgress: QCircularProgress,
|
|
QColor: QColor,
|
|
QDate: QDate,
|
|
QDialog: QDialog,
|
|
QDrawer: QDrawer,
|
|
QEditor: QEditor,
|
|
QExpansionItem: QExpansionItem,
|
|
QFab: QFab,
|
|
QFabAction: QFabAction,
|
|
QField: QField,
|
|
QFile: QFile,
|
|
QFooter: QFooter,
|
|
QForm: QForm,
|
|
QFormChildMixin: QFormChildMixin,
|
|
QHeader: QHeader,
|
|
QIcon: QIcon,
|
|
QImg: QImg,
|
|
QInfiniteScroll: QInfiniteScroll,
|
|
QInnerLoading: QInnerLoading,
|
|
QInput: QInput,
|
|
QIntersection: QIntersection,
|
|
QList: QList,
|
|
QItem: QItem,
|
|
QItemSection: QItemSection,
|
|
QItemLabel: QItemLabel,
|
|
QKnob: QKnob,
|
|
QLayout: QLayout,
|
|
QMarkupTable: QMarkupTable,
|
|
QMenu: QMenu,
|
|
QNoSsr: QNoSsr,
|
|
QOptionGroup: QOptionGroup,
|
|
QPage: QPage,
|
|
QPageContainer: QPageContainer,
|
|
QPageScroller: QPageScroller,
|
|
QPageSticky: QPageSticky,
|
|
QPagination: QPagination,
|
|
QParallax: QParallax,
|
|
QPopupEdit: QPopupEdit,
|
|
QPopupProxy: QPopupProxy,
|
|
QLinearProgress: QLinearProgress,
|
|
QPullToRefresh: QPullToRefresh,
|
|
QRadio: QRadio,
|
|
QRange: QRange,
|
|
QRating: QRating,
|
|
QResizeObserver: QResizeObserver,
|
|
QResponsive: QResponsive,
|
|
QScrollArea: QScrollArea,
|
|
QScrollObserver: QScrollObserver,
|
|
QSelect: QSelect,
|
|
QSeparator: QSeparator,
|
|
QSkeleton: QSkeleton,
|
|
QSlideItem: QSlideItem,
|
|
QSlideTransition: QSlideTransition,
|
|
QSlider: QSlider,
|
|
QSpace: QSpace,
|
|
QSpinner: QSpinner,
|
|
QSpinnerAudio: QSpinnerAudio,
|
|
QSpinnerBall: QSpinnerBall,
|
|
QSpinnerBars: QSpinnerBars,
|
|
QSpinnerBox: QSpinnerBox,
|
|
QSpinnerClock: QSpinnerClock,
|
|
QSpinnerComment: QSpinnerComment,
|
|
QSpinnerCube: QSpinnerCube,
|
|
QSpinnerDots: QSpinnerDots,
|
|
QSpinnerFacebook: QSpinnerFacebook,
|
|
QSpinnerGears: QSpinnerGears,
|
|
QSpinnerGrid: QSpinnerGrid,
|
|
QSpinnerHearts: QSpinnerHearts,
|
|
QSpinnerHourglass: QSpinnerHourglass,
|
|
QSpinnerInfinity: QSpinnerInfinity,
|
|
QSpinnerIos: QSpinnerIos,
|
|
QSpinnerOrbit: QSpinnerOrbit,
|
|
QSpinnerOval: QSpinnerOval,
|
|
QSpinnerPie: QSpinnerPie,
|
|
QSpinnerPuff: QSpinnerPuff,
|
|
QSpinnerRadio: QSpinnerRadio,
|
|
QSpinnerRings: QSpinnerRings,
|
|
QSpinnerTail: QSpinnerTail,
|
|
QSplitter: QSplitter,
|
|
QStep: QStep,
|
|
QStepper: QStepper,
|
|
QStepperNavigation: QStepperNavigation,
|
|
QTabPanels: QTabPanels,
|
|
QTabPanel: QTabPanel,
|
|
QTable: QTable,
|
|
QTh: QTh,
|
|
QTr: QTr,
|
|
QTd: QTd,
|
|
QTabs: QTabs,
|
|
QTab: QTab,
|
|
QRouteTab: QRouteTab,
|
|
QTime: QTime,
|
|
QTimeline: QTimeline,
|
|
QTimelineEntry: QTimelineEntry,
|
|
QToggle: QToggle,
|
|
QToolbar: QToolbar,
|
|
QToolbarTitle: QToolbarTitle,
|
|
QTooltip: QTooltip,
|
|
QTree: QTree,
|
|
QUploader: QUploader,
|
|
QUploaderAddTrigger: QUploaderAddTrigger,
|
|
QVideo: QVideo,
|
|
QVirtualScroll: QVirtualScroll
|
|
});
|
|
|
|
/*
|
|
* depth
|
|
* < 0 --> close all chain
|
|
* 0 --> disabled
|
|
* > 0 --> close chain up to N parent
|
|
*/
|
|
|
|
function getDepth (value) {
|
|
if (value === false) {
|
|
return 0
|
|
}
|
|
if (value === true || value === void 0) {
|
|
return 1
|
|
}
|
|
|
|
const depth = parseInt(value, 10);
|
|
return isNaN(depth) ? 0 : depth
|
|
}
|
|
|
|
var ClosePopup = createDirective({
|
|
name: 'close-popup',
|
|
|
|
beforeMount (el, { value }) {
|
|
const ctx = {
|
|
depth: getDepth(value),
|
|
|
|
handler (evt) {
|
|
// allow @click to be emitted
|
|
ctx.depth !== 0 && setTimeout(() => {
|
|
const proxy = getPortalProxy(el);
|
|
if (proxy !== void 0) {
|
|
closePortals(proxy, evt, ctx.depth);
|
|
}
|
|
});
|
|
},
|
|
|
|
handlerKey (evt) {
|
|
isKeyCode(evt, 13) === true && ctx.handler(evt);
|
|
}
|
|
};
|
|
|
|
el.__qclosepopup = ctx;
|
|
|
|
el.addEventListener('click', ctx.handler);
|
|
el.addEventListener('keyup', ctx.handlerKey);
|
|
},
|
|
|
|
updated (el, { value, oldValue }) {
|
|
if (value !== oldValue) {
|
|
el.__qclosepopup.depth = getDepth(value);
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qclosepopup;
|
|
el.removeEventListener('click', ctx.handler);
|
|
el.removeEventListener('keyup', ctx.handlerKey);
|
|
delete el.__qclosepopup;
|
|
}
|
|
}
|
|
);
|
|
|
|
let id = 0;
|
|
let offsetBase = void 0;
|
|
|
|
function getAbsolutePosition (el, resize) {
|
|
if (offsetBase === void 0) {
|
|
offsetBase = document.createElement('div');
|
|
offsetBase.style.cssText = 'position: absolute; left: 0; top: 0';
|
|
document.body.appendChild(offsetBase);
|
|
}
|
|
|
|
const boundingRect = el.getBoundingClientRect();
|
|
const baseRect = offsetBase.getBoundingClientRect();
|
|
const { marginLeft, marginRight, marginTop, marginBottom } = window.getComputedStyle(el);
|
|
const marginH = parseInt(marginLeft, 10) + parseInt(marginRight, 10);
|
|
const marginV = parseInt(marginTop, 10) + parseInt(marginBottom, 10);
|
|
|
|
return {
|
|
left: boundingRect.left - baseRect.left,
|
|
top: boundingRect.top - baseRect.top,
|
|
width: boundingRect.right - boundingRect.left,
|
|
height: boundingRect.bottom - boundingRect.top,
|
|
widthM: boundingRect.right - boundingRect.left + (resize === true ? 0 : marginH),
|
|
heightM: boundingRect.bottom - boundingRect.top + (resize === true ? 0 : marginV),
|
|
marginH: resize === true ? marginH : 0,
|
|
marginV: resize === true ? marginV : 0
|
|
}
|
|
}
|
|
|
|
function getAbsoluteSize (el) {
|
|
return {
|
|
width: el.scrollWidth,
|
|
height: el.scrollHeight
|
|
}
|
|
}
|
|
|
|
// firefox rulez
|
|
const styleEdges = [ 'Top', 'Right', 'Bottom', 'Left' ];
|
|
const styleBorderRadiuses = [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ];
|
|
const reStyleSkipKey = /-block|-inline|block-|inline-/;
|
|
const reStyleSkipRule = /(-block|-inline|block-|inline-).*:/;
|
|
|
|
function getComputedStyle$1 (el, props) {
|
|
const style = window.getComputedStyle(el);
|
|
const fixed = {};
|
|
for (let i = 0; i < props.length; i++) {
|
|
const prop = props[ i ];
|
|
|
|
if (style[ prop ] === '') {
|
|
if (prop === 'cssText') {
|
|
const styleLen = style.length;
|
|
let val = '';
|
|
|
|
for (let i = 0; i < styleLen; i++) {
|
|
if (reStyleSkipKey.test(style[ i ]) !== true) {
|
|
val += style[ i ] + ': ' + style[ style[ i ] ] + '; ';
|
|
}
|
|
}
|
|
|
|
fixed[ prop ] = val;
|
|
}
|
|
else if ([ 'borderWidth', 'borderStyle', 'borderColor' ].indexOf(prop) > -1) {
|
|
const suffix = prop.replace('border', '');
|
|
let val = '';
|
|
for (let j = 0; j < styleEdges.length; j++) {
|
|
const subProp = 'border' + styleEdges[ j ] + suffix;
|
|
val += style[ subProp ] + ' ';
|
|
}
|
|
fixed[ prop ] = val;
|
|
}
|
|
else if (prop === 'borderRadius') {
|
|
let val1 = '';
|
|
let val2 = '';
|
|
for (let j = 0; j < styleBorderRadiuses.length; j++) {
|
|
const val = style[ styleBorderRadiuses[ j ] ].split(' ');
|
|
val1 += val[ 0 ] + ' ';
|
|
val2 += (val[ 1 ] === void 0 ? val[ 0 ] : val[ 1 ]) + ' ';
|
|
}
|
|
fixed[ prop ] = val1 + '/ ' + val2;
|
|
}
|
|
else {
|
|
fixed[ prop ] = style[ prop ];
|
|
}
|
|
}
|
|
else {
|
|
if (prop === 'cssText') {
|
|
fixed[ prop ] = style[ prop ]
|
|
.split(';')
|
|
.filter(val => reStyleSkipRule.test(val) !== true)
|
|
.join(';');
|
|
}
|
|
else {
|
|
fixed[ prop ] = style[ prop ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return fixed
|
|
}
|
|
|
|
const zIndexPositions = [ 'absolute', 'fixed', 'relative', 'sticky' ];
|
|
|
|
function getMaxZIndex (elStart) {
|
|
let el = elStart;
|
|
let maxIndex = 0;
|
|
|
|
while (el !== null && el !== document) {
|
|
const { position, zIndex } = window.getComputedStyle(el);
|
|
const zIndexNum = Number(zIndex);
|
|
|
|
if (
|
|
zIndexNum > maxIndex
|
|
&& (el === elStart || zIndexPositions.includes(position) === true)
|
|
) {
|
|
maxIndex = zIndexNum;
|
|
}
|
|
|
|
el = el.parentNode;
|
|
}
|
|
|
|
return maxIndex
|
|
}
|
|
|
|
function normalizeElements (opts) {
|
|
return {
|
|
from: opts.from,
|
|
to: opts.to !== void 0
|
|
? opts.to
|
|
: opts.from
|
|
}
|
|
}
|
|
|
|
function normalizeOptions (options) {
|
|
if (typeof options === 'number') {
|
|
options = {
|
|
duration: options
|
|
};
|
|
}
|
|
else if (typeof options === 'function') {
|
|
options = {
|
|
onEnd: options
|
|
};
|
|
}
|
|
|
|
return {
|
|
...options,
|
|
|
|
waitFor: options.waitFor === void 0 ? 0 : options.waitFor,
|
|
|
|
duration: isNaN(options.duration) === true ? 300 : parseInt(options.duration, 10),
|
|
easing: typeof options.easing === 'string' && options.easing.length !== 0 ? options.easing : 'ease-in-out',
|
|
delay: isNaN(options.delay) === true ? 0 : parseInt(options.delay, 10),
|
|
fill: typeof options.fill === 'string' && options.fill.length !== 0 ? options.fill : 'none',
|
|
|
|
resize: options.resize === true,
|
|
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
useCSS: options.useCSS === true || options.usecss === true,
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
hideFromClone: options.hideFromClone === true || options.hidefromclone === true,
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
keepToClone: options.keepToClone === true || options.keeptoclone === true,
|
|
|
|
tween: options.tween === true,
|
|
tweenFromOpacity: isNaN(options.tweenFromOpacity) === true ? 0.6 : parseFloat(options.tweenFromOpacity),
|
|
tweenToOpacity: isNaN(options.tweenToOpacity) === true ? 0.5 : parseFloat(options.tweenToOpacity)
|
|
}
|
|
}
|
|
|
|
function getElement (element) {
|
|
const type = typeof element;
|
|
|
|
return type === 'function'
|
|
? element()
|
|
: (
|
|
type === 'string'
|
|
? document.querySelector(element)
|
|
: element
|
|
)
|
|
}
|
|
|
|
function isValidElement (element) {
|
|
return element
|
|
&& element.ownerDocument === document
|
|
&& element.parentNode !== null
|
|
}
|
|
|
|
function morph (_options) {
|
|
let cancel = () => false;
|
|
let cancelStatus = false;
|
|
let endElementTo = true;
|
|
|
|
const elements = normalizeElements(_options);
|
|
const options = normalizeOptions(_options);
|
|
|
|
const elFrom = getElement(elements.from);
|
|
if (isValidElement(elFrom) !== true) {
|
|
// we return a cancel function that return false, meaning the cancel function failed
|
|
return cancel
|
|
}
|
|
// we clean other morphs running on this element
|
|
typeof elFrom.qMorphCancel === 'function' && elFrom.qMorphCancel();
|
|
|
|
let animationFromClone = void 0;
|
|
let animationFromTween = void 0;
|
|
let animationToClone = void 0;
|
|
let animationTo = void 0;
|
|
|
|
const elFromParent = elFrom.parentNode;
|
|
const elFromNext = elFrom.nextElementSibling;
|
|
|
|
// we get the dimensions and characteristics
|
|
// of the parent of the initial element before changes
|
|
const elFromPosition = getAbsolutePosition(elFrom, options.resize);
|
|
const {
|
|
width: elFromParentWidthBefore,
|
|
height: elFromParentHeightBefore
|
|
} = getAbsoluteSize(elFromParent);
|
|
const {
|
|
borderWidth: elFromBorderWidth,
|
|
borderStyle: elFromBorderStyle,
|
|
borderColor: elFromBorderColor,
|
|
borderRadius: elFromBorderRadius,
|
|
backgroundColor: elFromBackground,
|
|
transform: elFromTransform,
|
|
position: elFromPositioningType,
|
|
cssText: elFromCssText
|
|
} = getComputedStyle$1(elFrom, [ 'borderWidth', 'borderStyle', 'borderColor', 'borderRadius', 'backgroundColor', 'transform', 'position', 'cssText' ]);
|
|
const elFromClassSaved = elFrom.classList.toString();
|
|
const elFromStyleSaved = elFrom.style.cssText;
|
|
|
|
// we make a clone of the initial element and
|
|
// use it to display until the final element is ready
|
|
// and to change the occupied space during animation
|
|
const elFromClone = elFrom.cloneNode(true);
|
|
const elFromTween = options.tween === true ? elFrom.cloneNode(true) : void 0;
|
|
|
|
if (elFromTween !== void 0) {
|
|
elFromTween.className = elFromTween.classList.toString().split(' ').filter(c => /^bg-/.test(c) === false).join(' ');
|
|
}
|
|
|
|
// if the initial element is not going to be removed do not show the placeholder
|
|
options.hideFromClone === true && elFromClone.classList.add('q-morph--internal');
|
|
|
|
// prevent interaction with placeholder
|
|
elFromClone.setAttribute('aria-hidden', 'true');
|
|
elFromClone.style.transition = 'none';
|
|
elFromClone.style.animation = 'none';
|
|
elFromClone.style.pointerEvents = 'none';
|
|
elFromParent.insertBefore(elFromClone, elFromNext);
|
|
|
|
// we mark the element with its cleanup function
|
|
elFrom.qMorphCancel = () => {
|
|
cancelStatus = true;
|
|
|
|
// we clean the clone of the initial element
|
|
elFromClone.remove();
|
|
elFromTween !== void 0 && elFromTween.remove();
|
|
|
|
options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal');
|
|
|
|
// we remove the cleanup function from the element
|
|
elFrom.qMorphCancel = void 0;
|
|
};
|
|
|
|
// will be called after Vue catches up with the changes done by _options.onToggle() function
|
|
const calculateFinalState = () => {
|
|
const elTo = getElement(elements.to);
|
|
if (cancelStatus === true || isValidElement(elTo) !== true) {
|
|
typeof elFrom.qMorphCancel === 'function' && elFrom.qMorphCancel();
|
|
|
|
return
|
|
}
|
|
// we clean other morphs running on this element
|
|
elFrom !== elTo && typeof elTo.qMorphCancel === 'function' && elTo.qMorphCancel();
|
|
|
|
// we hide the final element and the clone of the initial element
|
|
// we don't hide the final element if we want both it and the animated one visible
|
|
options.keepToClone !== true && elTo.classList.add('q-morph--internal');
|
|
elFromClone.classList.add('q-morph--internal');
|
|
|
|
// we get the dimensions of the parent of the initial element after changes
|
|
// the difference is how much we should animate the clone
|
|
const {
|
|
width: elFromParentWidthAfter,
|
|
height: elFromParentHeightAfter
|
|
} = getAbsoluteSize(elFromParent);
|
|
|
|
// we get the dimensions of the parent of the final element before changes
|
|
const {
|
|
width: elToParentWidthBefore,
|
|
height: elToParentHeightBefore
|
|
} = getAbsoluteSize(elTo.parentNode);
|
|
|
|
// then we show the clone of the initial element if we don't want it hidden
|
|
options.hideFromClone !== true && elFromClone.classList.remove('q-morph--internal');
|
|
|
|
// we mark the element with its cleanup function
|
|
elTo.qMorphCancel = () => {
|
|
cancelStatus = true;
|
|
|
|
// we clean the clone of the initial element
|
|
elFromClone.remove();
|
|
elFromTween !== void 0 && elFromTween.remove();
|
|
|
|
options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal');
|
|
|
|
// we show the final element
|
|
options.keepToClone !== true && elTo.classList.remove('q-morph--internal');
|
|
|
|
// we remove the cleanup function from the elements
|
|
elFrom.qMorphCancel = void 0;
|
|
elTo.qMorphCancel = void 0;
|
|
};
|
|
|
|
// will be called after waitFor (give time to render the final element)
|
|
const animate = () => {
|
|
if (cancelStatus === true) {
|
|
typeof elTo.qMorphCancel === 'function' && elTo.qMorphCancel();
|
|
|
|
return
|
|
}
|
|
|
|
// now the animation starts, so we only need the clone
|
|
// of the initial element as a spacer
|
|
// we also hide it to calculate the dimensions of the
|
|
// parent of the final element after the changes
|
|
if (options.hideFromClone !== true) {
|
|
elFromClone.classList.add('q-morph--internal');
|
|
elFromClone.innerHTML = '';
|
|
elFromClone.style.left = 0;
|
|
elFromClone.style.right = 'unset';
|
|
elFromClone.style.top = 0;
|
|
elFromClone.style.bottom = 'unset';
|
|
elFromClone.style.transform = 'none';
|
|
}
|
|
|
|
// we show the final element
|
|
if (options.keepToClone !== true) {
|
|
elTo.classList.remove('q-morph--internal');
|
|
}
|
|
|
|
// we get the dimensions of the parent of the final element after changes
|
|
// the difference is how much we should animate the clone
|
|
const elToParent = elTo.parentNode;
|
|
const {
|
|
width: elToParentWidthAfter,
|
|
height: elToParentHeightAfter
|
|
} = getAbsoluteSize(elToParent);
|
|
|
|
const elToClone = elTo.cloneNode(options.keepToClone);
|
|
elToClone.setAttribute('aria-hidden', 'true');
|
|
if (options.keepToClone !== true) {
|
|
elToClone.style.left = 0;
|
|
elToClone.style.right = 'unset';
|
|
elToClone.style.top = 0;
|
|
elToClone.style.bottom = 'unset';
|
|
elToClone.style.transform = 'none';
|
|
elToClone.style.pointerEvents = 'none';
|
|
}
|
|
elToClone.classList.add('q-morph--internal');
|
|
|
|
// if elFrom is the same as elTo the next element is elFromClone
|
|
const elToNext = elTo === elFrom && elFromParent === elToParent ? elFromClone : elTo.nextElementSibling;
|
|
elToParent.insertBefore(elToClone, elToNext);
|
|
|
|
const {
|
|
borderWidth: elToBorderWidth,
|
|
borderStyle: elToBorderStyle,
|
|
borderColor: elToBorderColor,
|
|
borderRadius: elToBorderRadius,
|
|
backgroundColor: elToBackground,
|
|
transform: elToTransform,
|
|
position: elToPositioningType,
|
|
cssText: elToCssText
|
|
} = getComputedStyle$1(elTo, [ 'borderWidth', 'borderStyle', 'borderColor', 'borderRadius', 'backgroundColor', 'transform', 'position', 'cssText' ]);
|
|
const elToClassSaved = elTo.classList.toString();
|
|
const elToStyleSaved = elTo.style.cssText;
|
|
|
|
// we set the computed styles on the element (to be able to remove classes)
|
|
elTo.style.cssText = elToCssText;
|
|
elTo.style.transform = 'none';
|
|
elTo.style.animation = 'none';
|
|
elTo.style.transition = 'none';
|
|
// we strip the background classes (background color can no longer be animated if !important is used)
|
|
elTo.className = elToClassSaved.split(' ').filter(c => /^bg-/.test(c) === false).join(' ');
|
|
|
|
const elToPosition = getAbsolutePosition(elTo, options.resize);
|
|
|
|
const deltaX = elFromPosition.left - elToPosition.left;
|
|
const deltaY = elFromPosition.top - elToPosition.top;
|
|
const scaleX = elFromPosition.width / (elToPosition.width > 0 ? elToPosition.width : 10);
|
|
const scaleY = elFromPosition.height / (elToPosition.height > 0 ? elToPosition.height : 100);
|
|
|
|
const elFromParentWidthDiff = elFromParentWidthBefore - elFromParentWidthAfter;
|
|
const elFromParentHeightDiff = elFromParentHeightBefore - elFromParentHeightAfter;
|
|
const elToParentWidthDiff = elToParentWidthAfter - elToParentWidthBefore;
|
|
const elToParentHeightDiff = elToParentHeightAfter - elToParentHeightBefore;
|
|
|
|
const elFromCloneWidth = Math.max(elFromPosition.widthM, elFromParentWidthDiff);
|
|
const elFromCloneHeight = Math.max(elFromPosition.heightM, elFromParentHeightDiff);
|
|
const elToCloneWidth = Math.max(elToPosition.widthM, elToParentWidthDiff);
|
|
const elToCloneHeight = Math.max(elToPosition.heightM, elToParentHeightDiff);
|
|
|
|
const elSharedSize = elFrom === elTo
|
|
&& [ 'absolute', 'fixed' ].includes(elToPositioningType) === false
|
|
&& [ 'absolute', 'fixed' ].includes(elFromPositioningType) === false;
|
|
|
|
// if the final element has fixed position or if a parent
|
|
// has fixed position we need to animate it as fixed
|
|
let elToNeedsFixedPosition = elToPositioningType === 'fixed';
|
|
let parent = elToParent;
|
|
while (elToNeedsFixedPosition !== true && parent !== document) {
|
|
elToNeedsFixedPosition = window.getComputedStyle(parent).position === 'fixed';
|
|
parent = parent.parentNode;
|
|
}
|
|
|
|
// we show the spacer for the initial element
|
|
if (options.hideFromClone !== true) {
|
|
elFromClone.style.display = 'block';
|
|
elFromClone.style.flex = '0 0 auto';
|
|
elFromClone.style.opacity = 0;
|
|
elFromClone.style.minWidth = 'unset';
|
|
elFromClone.style.maxWidth = 'unset';
|
|
elFromClone.style.minHeight = 'unset';
|
|
elFromClone.style.maxHeight = 'unset';
|
|
elFromClone.classList.remove('q-morph--internal');
|
|
}
|
|
|
|
// we show the spacer for the final element
|
|
if (options.keepToClone !== true) {
|
|
elToClone.style.display = 'block';
|
|
elToClone.style.flex = '0 0 auto';
|
|
elToClone.style.opacity = 0;
|
|
elToClone.style.minWidth = 'unset';
|
|
elToClone.style.maxWidth = 'unset';
|
|
elToClone.style.minHeight = 'unset';
|
|
elToClone.style.maxHeight = 'unset';
|
|
}
|
|
elToClone.classList.remove('q-morph--internal');
|
|
|
|
// we apply classes specified by user
|
|
if (typeof options.classes === 'string') {
|
|
elTo.className += ' ' + options.classes;
|
|
}
|
|
|
|
// we apply styles specified by user
|
|
if (typeof options.style === 'string') {
|
|
elTo.style.cssText += ' ' + options.style;
|
|
}
|
|
else if (isObject(options.style) === true) {
|
|
for (const prop in options.style) {
|
|
elTo.style[ prop ] = options.style[ prop ];
|
|
}
|
|
}
|
|
|
|
const elFromZIndex = getMaxZIndex(elFromClone);
|
|
const elToZIndex = getMaxZIndex(elTo);
|
|
|
|
// we position the morphing element
|
|
// if we use fixed position for the final element we need to adjust for scroll
|
|
const documentScroll = elToNeedsFixedPosition === true
|
|
? document.documentElement
|
|
: { scrollLeft: 0, scrollTop: 0 };
|
|
elTo.style.position = elToNeedsFixedPosition === true ? 'fixed' : 'absolute';
|
|
elTo.style.left = `${ elToPosition.left - documentScroll.scrollLeft }px`;
|
|
elTo.style.right = 'unset';
|
|
elTo.style.top = `${ elToPosition.top - documentScroll.scrollTop }px`;
|
|
elTo.style.margin = 0;
|
|
|
|
if (options.resize === true) {
|
|
elTo.style.minWidth = 'unset';
|
|
elTo.style.maxWidth = 'unset';
|
|
elTo.style.minHeight = 'unset';
|
|
elTo.style.maxHeight = 'unset';
|
|
elTo.style.overflow = 'hidden';
|
|
elTo.style.overflowX = 'hidden';
|
|
elTo.style.overflowY = 'hidden';
|
|
}
|
|
|
|
document.body.appendChild(elTo);
|
|
|
|
if (elFromTween !== void 0) {
|
|
elFromTween.style.cssText = elFromCssText;
|
|
elFromTween.style.transform = 'none';
|
|
elFromTween.style.animation = 'none';
|
|
elFromTween.style.transition = 'none';
|
|
|
|
elFromTween.style.position = elTo.style.position;
|
|
elFromTween.style.left = `${ elFromPosition.left - documentScroll.scrollLeft }px`;
|
|
elFromTween.style.right = 'unset';
|
|
elFromTween.style.top = `${ elFromPosition.top - documentScroll.scrollTop }px`;
|
|
elFromTween.style.margin = 0;
|
|
elFromTween.style.pointerEvents = 'none';
|
|
|
|
if (options.resize === true) {
|
|
elFromTween.style.minWidth = 'unset';
|
|
elFromTween.style.maxWidth = 'unset';
|
|
elFromTween.style.minHeight = 'unset';
|
|
elFromTween.style.maxHeight = 'unset';
|
|
elFromTween.style.overflow = 'hidden';
|
|
elFromTween.style.overflowX = 'hidden';
|
|
elFromTween.style.overflowY = 'hidden';
|
|
}
|
|
|
|
document.body.appendChild(elFromTween);
|
|
}
|
|
|
|
const commonCleanup = aborted => {
|
|
// we put the element back in it's place
|
|
// and restore the styles and classes
|
|
if (elFrom === elTo && endElementTo !== true) {
|
|
elTo.style.cssText = elFromStyleSaved;
|
|
elTo.className = elFromClassSaved;
|
|
}
|
|
else {
|
|
elTo.style.cssText = elToStyleSaved;
|
|
elTo.className = elToClassSaved;
|
|
}
|
|
elToClone.parentNode === elToParent && elToParent.insertBefore(elTo, elToClone);
|
|
|
|
// we clean the spacers
|
|
elFromClone.remove();
|
|
elToClone.remove();
|
|
elFromTween !== void 0 && elFromTween.remove();
|
|
|
|
// cancel will be no longer available
|
|
cancel = () => false;
|
|
|
|
elFrom.qMorphCancel = void 0;
|
|
elTo.qMorphCancel = void 0;
|
|
|
|
// we are ready
|
|
if (typeof options.onEnd === 'function') {
|
|
options.onEnd(endElementTo === true ? 'to' : 'from', aborted === true);
|
|
}
|
|
};
|
|
|
|
if (options.useCSS !== true && typeof elTo.animate === 'function') {
|
|
const resizeFrom = options.resize === true
|
|
? {
|
|
transform: `translate(${ deltaX }px, ${ deltaY }px)`,
|
|
width: `${ elFromCloneWidth }px`,
|
|
height: `${ elFromCloneHeight }px`
|
|
}
|
|
: {
|
|
transform: `translate(${ deltaX }px, ${ deltaY }px) scale(${ scaleX }, ${ scaleY })`
|
|
};
|
|
const resizeTo = options.resize === true
|
|
? {
|
|
width: `${ elToCloneWidth }px`,
|
|
height: `${ elToCloneHeight }px`
|
|
}
|
|
: {};
|
|
const resizeFromTween = options.resize === true
|
|
? {
|
|
width: `${ elFromCloneWidth }px`,
|
|
height: `${ elFromCloneHeight }px`
|
|
}
|
|
: {};
|
|
const resizeToTween = options.resize === true
|
|
? {
|
|
transform: `translate(${ -1 * deltaX }px, ${ -1 * deltaY }px)`,
|
|
width: `${ elToCloneWidth }px`,
|
|
height: `${ elToCloneHeight }px`
|
|
}
|
|
: {
|
|
transform: `translate(${ -1 * deltaX }px, ${ -1 * deltaY }px) scale(${ 1 / scaleX }, ${ 1 / scaleY })`
|
|
};
|
|
const tweenFrom = elFromTween !== void 0
|
|
? { opacity: options.tweenToOpacity }
|
|
: { backgroundColor: elFromBackground };
|
|
const tweenTo = elFromTween !== void 0
|
|
? { opacity: 1 }
|
|
: { backgroundColor: elToBackground };
|
|
animationTo = elTo.animate([
|
|
{
|
|
margin: 0,
|
|
borderWidth: elFromBorderWidth,
|
|
borderStyle: elFromBorderStyle,
|
|
borderColor: elFromBorderColor,
|
|
borderRadius: elFromBorderRadius,
|
|
zIndex: elFromZIndex,
|
|
transformOrigin: '0 0',
|
|
...resizeFrom,
|
|
...tweenFrom
|
|
},
|
|
{
|
|
margin: 0,
|
|
borderWidth: elToBorderWidth,
|
|
borderStyle: elToBorderStyle,
|
|
borderColor: elToBorderColor,
|
|
borderRadius: elToBorderRadius,
|
|
zIndex: elToZIndex,
|
|
transformOrigin: '0 0',
|
|
transform: elToTransform,
|
|
...resizeTo,
|
|
...tweenTo
|
|
}
|
|
], {
|
|
duration: options.duration,
|
|
easing: options.easing,
|
|
fill: options.fill,
|
|
delay: options.delay
|
|
});
|
|
|
|
animationFromTween = elFromTween === void 0 ? void 0 : elFromTween.animate([
|
|
{
|
|
opacity: options.tweenFromOpacity,
|
|
margin: 0,
|
|
borderWidth: elFromBorderWidth,
|
|
borderStyle: elFromBorderStyle,
|
|
borderColor: elFromBorderColor,
|
|
borderRadius: elFromBorderRadius,
|
|
zIndex: elFromZIndex,
|
|
transformOrigin: '0 0',
|
|
transform: elFromTransform,
|
|
...resizeFromTween
|
|
},
|
|
{
|
|
opacity: 0,
|
|
margin: 0,
|
|
borderWidth: elToBorderWidth,
|
|
borderStyle: elToBorderStyle,
|
|
borderColor: elToBorderColor,
|
|
borderRadius: elToBorderRadius,
|
|
zIndex: elToZIndex,
|
|
transformOrigin: '0 0',
|
|
...resizeToTween
|
|
}
|
|
], {
|
|
duration: options.duration,
|
|
easing: options.easing,
|
|
fill: options.fill,
|
|
delay: options.delay
|
|
});
|
|
|
|
animationFromClone = options.hideFromClone === true || elSharedSize === true ? void 0 : elFromClone.animate([
|
|
{
|
|
margin: `${ elFromParentHeightDiff < 0 ? elFromParentHeightDiff / 2 : 0 }px ${ elFromParentWidthDiff < 0 ? elFromParentWidthDiff / 2 : 0 }px`,
|
|
width: `${ elFromCloneWidth + elFromPosition.marginH }px`,
|
|
height: `${ elFromCloneHeight + elFromPosition.marginV }px`
|
|
},
|
|
{
|
|
margin: 0,
|
|
width: 0,
|
|
height: 0
|
|
}
|
|
], {
|
|
duration: options.duration,
|
|
easing: options.easing,
|
|
fill: options.fill,
|
|
delay: options.delay
|
|
});
|
|
|
|
animationToClone = options.keepToClone === true ? void 0 : elToClone.animate([
|
|
elSharedSize === true
|
|
? {
|
|
margin: `${ elFromParentHeightDiff < 0 ? elFromParentHeightDiff / 2 : 0 }px ${ elFromParentWidthDiff < 0 ? elFromParentWidthDiff / 2 : 0 }px`,
|
|
width: `${ elFromCloneWidth + elFromPosition.marginH }px`,
|
|
height: `${ elFromCloneHeight + elFromPosition.marginV }px`
|
|
}
|
|
: {
|
|
margin: 0,
|
|
width: 0,
|
|
height: 0
|
|
},
|
|
{
|
|
margin: `${ elToParentHeightDiff < 0 ? elToParentHeightDiff / 2 : 0 }px ${ elToParentWidthDiff < 0 ? elToParentWidthDiff / 2 : 0 }px`,
|
|
width: `${ elToCloneWidth + elToPosition.marginH }px`,
|
|
height: `${ elToCloneHeight + elToPosition.marginV }px`
|
|
}
|
|
], {
|
|
duration: options.duration,
|
|
easing: options.easing,
|
|
fill: options.fill,
|
|
delay: options.delay
|
|
});
|
|
|
|
const cleanup = abort => {
|
|
animationFromClone !== void 0 && animationFromClone.cancel();
|
|
animationFromTween !== void 0 && animationFromTween.cancel();
|
|
animationToClone !== void 0 && animationToClone.cancel();
|
|
animationTo.cancel();
|
|
|
|
animationTo.removeEventListener('finish', cleanup);
|
|
animationTo.removeEventListener('cancel', cleanup);
|
|
|
|
commonCleanup(abort);
|
|
|
|
// we clean the animations
|
|
animationFromClone = void 0;
|
|
animationFromTween = void 0;
|
|
animationToClone = void 0;
|
|
animationTo = void 0;
|
|
};
|
|
|
|
elFrom.qMorphCancel = () => {
|
|
elFrom.qMorphCancel = void 0;
|
|
cancelStatus = true;
|
|
cleanup();
|
|
};
|
|
elTo.qMorphCancel = () => {
|
|
elTo.qMorphCancel = void 0;
|
|
cancelStatus = true;
|
|
cleanup();
|
|
};
|
|
|
|
animationTo.addEventListener('finish', cleanup);
|
|
animationTo.addEventListener('cancel', cleanup);
|
|
|
|
cancel = abort => {
|
|
// we are not in a morph that we can cancel
|
|
if (cancelStatus === true || animationTo === void 0) {
|
|
return false
|
|
}
|
|
|
|
if (abort === true) {
|
|
cleanup(true);
|
|
return true
|
|
}
|
|
|
|
endElementTo = endElementTo !== true;
|
|
|
|
animationFromClone !== void 0 && animationFromClone.reverse();
|
|
animationFromTween !== void 0 && animationFromTween.reverse();
|
|
animationToClone !== void 0 && animationToClone.reverse();
|
|
animationTo.reverse();
|
|
|
|
return true
|
|
};
|
|
}
|
|
else {
|
|
const qAnimId = `q-morph-anim-${ ++id }`;
|
|
const style = document.createElement('style');
|
|
const resizeFrom = options.resize === true
|
|
? `
|
|
transform: translate(${ deltaX }px, ${ deltaY }px);
|
|
width: ${ elFromCloneWidth }px;
|
|
height: ${ elFromCloneHeight }px;
|
|
`
|
|
: `transform: translate(${ deltaX }px, ${ deltaY }px) scale(${ scaleX }, ${ scaleY });`;
|
|
const resizeTo = options.resize === true
|
|
? `
|
|
width: ${ elToCloneWidth }px;
|
|
height: ${ elToCloneHeight }px;
|
|
`
|
|
: '';
|
|
const resizeFromTween = options.resize === true
|
|
? `
|
|
width: ${ elFromCloneWidth }px;
|
|
height: ${ elFromCloneHeight }px;
|
|
`
|
|
: '';
|
|
const resizeToTween = options.resize === true
|
|
? `
|
|
transform: translate(${ -1 * deltaX }px, ${ -1 * deltaY }px);
|
|
width: ${ elToCloneWidth }px;
|
|
height: ${ elToCloneHeight }px;
|
|
`
|
|
: `transform: translate(${ -1 * deltaX }px, ${ -1 * deltaY }px) scale(${ 1 / scaleX }, ${ 1 / scaleY });`;
|
|
const tweenFrom = elFromTween !== void 0
|
|
? `opacity: ${ options.tweenToOpacity };`
|
|
: `background-color: ${ elFromBackground };`;
|
|
const tweenTo = elFromTween !== void 0
|
|
? 'opacity: 1;'
|
|
: `background-color: ${ elToBackground };`;
|
|
const keyframesFromTween = elFromTween === void 0
|
|
? ''
|
|
: `
|
|
@keyframes ${ qAnimId }-from-tween {
|
|
0% {
|
|
opacity: ${ options.tweenFromOpacity };
|
|
margin: 0;
|
|
border-width: ${ elFromBorderWidth };
|
|
border-style: ${ elFromBorderStyle };
|
|
border-color: ${ elFromBorderColor };
|
|
border-radius: ${ elFromBorderRadius };
|
|
z-index: ${ elFromZIndex };
|
|
transform-origin: 0 0;
|
|
transform: ${ elFromTransform };
|
|
${ resizeFromTween }
|
|
}
|
|
|
|
100% {
|
|
opacity: 0;
|
|
margin: 0;
|
|
border-width: ${ elToBorderWidth };
|
|
border-style: ${ elToBorderStyle };
|
|
border-color: ${ elToBorderColor };
|
|
border-radius: ${ elToBorderRadius };
|
|
z-index: ${ elToZIndex };
|
|
transform-origin: 0 0;
|
|
${ resizeToTween }
|
|
}
|
|
}
|
|
`;
|
|
const keyframesFrom = options.hideFromClone === true || elSharedSize === true
|
|
? ''
|
|
: `
|
|
@keyframes ${ qAnimId }-from {
|
|
0% {
|
|
margin: ${ elFromParentHeightDiff < 0 ? elFromParentHeightDiff / 2 : 0 }px ${ elFromParentWidthDiff < 0 ? elFromParentWidthDiff / 2 : 0 }px;
|
|
width: ${ elFromCloneWidth + elFromPosition.marginH }px;
|
|
height: ${ elFromCloneHeight + elFromPosition.marginV }px;
|
|
}
|
|
|
|
100% {
|
|
margin: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
}
|
|
`;
|
|
const keyframeToStart = elSharedSize === true
|
|
? `
|
|
margin: ${ elFromParentHeightDiff < 0 ? elFromParentHeightDiff / 2 : 0 }px ${ elFromParentWidthDiff < 0 ? elFromParentWidthDiff / 2 : 0 }px;
|
|
width: ${ elFromCloneWidth + elFromPosition.marginH }px;
|
|
height: ${ elFromCloneHeight + elFromPosition.marginV }px;
|
|
`
|
|
: `
|
|
margin: 0;
|
|
width: 0;
|
|
height: 0;
|
|
`;
|
|
const keyframesTo = options.keepToClone === true
|
|
? ''
|
|
: `
|
|
@keyframes ${ qAnimId }-to {
|
|
0% {
|
|
${ keyframeToStart }
|
|
}
|
|
|
|
100% {
|
|
margin: ${ elToParentHeightDiff < 0 ? elToParentHeightDiff / 2 : 0 }px ${ elToParentWidthDiff < 0 ? elToParentWidthDiff / 2 : 0 }px;
|
|
width: ${ elToCloneWidth + elToPosition.marginH }px;
|
|
height: ${ elToCloneHeight + elToPosition.marginV }px;
|
|
}
|
|
}
|
|
`;
|
|
style.innerHTML = `
|
|
@keyframes ${ qAnimId } {
|
|
0% {
|
|
margin: 0;
|
|
border-width: ${ elFromBorderWidth };
|
|
border-style: ${ elFromBorderStyle };
|
|
border-color: ${ elFromBorderColor };
|
|
border-radius: ${ elFromBorderRadius };
|
|
background-color: ${ elFromBackground };
|
|
z-index: ${ elFromZIndex };
|
|
transform-origin: 0 0;
|
|
${ resizeFrom }
|
|
${ tweenFrom }
|
|
}
|
|
|
|
100% {
|
|
margin: 0;
|
|
border-width: ${ elToBorderWidth };
|
|
border-style: ${ elToBorderStyle };
|
|
border-color: ${ elToBorderColor };
|
|
border-radius: ${ elToBorderRadius };
|
|
background-color: ${ elToBackground };
|
|
z-index: ${ elToZIndex };
|
|
transform-origin: 0 0;
|
|
transform: ${ elToTransform };
|
|
${ resizeTo }
|
|
${ tweenTo }
|
|
}
|
|
}
|
|
|
|
${ keyframesFrom }
|
|
|
|
${ keyframesFromTween }
|
|
|
|
${ keyframesTo }
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
let animationDirection = 'normal';
|
|
|
|
elFromClone.style.animation = `${ options.duration }ms ${ options.easing } ${ options.delay }ms ${ animationDirection } ${ options.fill } ${ qAnimId }-from`;
|
|
if (elFromTween !== void 0) {
|
|
elFromTween.style.animation = `${ options.duration }ms ${ options.easing } ${ options.delay }ms ${ animationDirection } ${ options.fill } ${ qAnimId }-from-tween`;
|
|
}
|
|
elToClone.style.animation = `${ options.duration }ms ${ options.easing } ${ options.delay }ms ${ animationDirection } ${ options.fill } ${ qAnimId }-to`;
|
|
elTo.style.animation = `${ options.duration }ms ${ options.easing } ${ options.delay }ms ${ animationDirection } ${ options.fill } ${ qAnimId }`;
|
|
|
|
const cleanup = evt => {
|
|
if (evt === Object(evt) && evt.animationName !== qAnimId) {
|
|
return
|
|
}
|
|
|
|
elTo.removeEventListener('animationend', cleanup);
|
|
elTo.removeEventListener('animationcancel', cleanup);
|
|
|
|
commonCleanup();
|
|
|
|
// we clean the animations
|
|
style.remove();
|
|
};
|
|
|
|
elFrom.qMorphCancel = () => {
|
|
elFrom.qMorphCancel = void 0;
|
|
cancelStatus = true;
|
|
cleanup();
|
|
};
|
|
elTo.qMorphCancel = () => {
|
|
elTo.qMorphCancel = void 0;
|
|
cancelStatus = true;
|
|
cleanup();
|
|
};
|
|
|
|
elTo.addEventListener('animationend', cleanup);
|
|
elTo.addEventListener('animationcancel', cleanup);
|
|
|
|
cancel = abort => {
|
|
// we are not in a morph that we can cancel
|
|
if (cancelStatus === true || !elTo || !elFromClone || !elToClone) {
|
|
return false
|
|
}
|
|
|
|
if (abort === true) {
|
|
cleanup();
|
|
|
|
return true
|
|
}
|
|
|
|
endElementTo = endElementTo !== true;
|
|
|
|
animationDirection = animationDirection === 'normal' ? 'reverse' : 'normal';
|
|
|
|
elFromClone.style.animationDirection = animationDirection;
|
|
elFromTween.style.animationDirection = animationDirection;
|
|
elToClone.style.animationDirection = animationDirection;
|
|
elTo.style.animationDirection = animationDirection;
|
|
|
|
return true
|
|
};
|
|
}
|
|
};
|
|
|
|
if (
|
|
options.waitFor > 0
|
|
|| options.waitFor === 'transitionend'
|
|
|| (options.waitFor === Object(options.waitFor) && typeof options.waitFor.then === 'function')
|
|
) {
|
|
const delayPromise = options.waitFor > 0
|
|
? new Promise(resolve => setTimeout(resolve, options.waitFor))
|
|
: (
|
|
options.waitFor === 'transitionend'
|
|
? new Promise(resolve => {
|
|
const endFn = () => {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
if (elTo) {
|
|
elTo.removeEventListener('transitionend', endFn);
|
|
elTo.removeEventListener('transitioncancel', endFn);
|
|
}
|
|
|
|
resolve();
|
|
};
|
|
|
|
let timer = setTimeout(endFn, 400);
|
|
|
|
elTo.addEventListener('transitionend', endFn);
|
|
elTo.addEventListener('transitioncancel', endFn);
|
|
})
|
|
: options.waitFor
|
|
);
|
|
|
|
delayPromise
|
|
.then(animate)
|
|
.catch(() => {
|
|
typeof elTo.qMorphCancel === 'function' && elTo.qMorphCancel();
|
|
});
|
|
}
|
|
else {
|
|
animate();
|
|
}
|
|
};
|
|
|
|
typeof _options.onToggle === 'function' && _options.onToggle();
|
|
requestAnimationFrame(calculateFinalState);
|
|
|
|
// we return the cancel function
|
|
// returns:
|
|
// false if the cancel cannot be performed (the morph ended already or has not started)
|
|
// true else
|
|
return abort => cancel(abort)
|
|
}
|
|
|
|
const morphGroups = {};
|
|
const props$1 = [
|
|
'duration', 'delay', 'easing', 'fill',
|
|
'classes', 'style', 'duration', 'resize',
|
|
'useCSS', 'hideFromClone', 'keepToClone', 'tween',
|
|
'tweenFromOpacity', 'tweenToOpacity',
|
|
'waitFor', 'onEnd'
|
|
];
|
|
const mods = [
|
|
'resize', 'useCSS', 'hideFromClone', 'keepToClone', 'tween'
|
|
];
|
|
|
|
function changeClass (ctx, action) {
|
|
if (ctx.clsAction !== action) {
|
|
ctx.clsAction = action;
|
|
ctx.el.classList[ action ]('q-morph--invisible');
|
|
}
|
|
}
|
|
|
|
function trigger (group) {
|
|
if (group.animating === true || group.queue.length < 2) {
|
|
return
|
|
}
|
|
|
|
const [ from, to ] = group.queue;
|
|
|
|
group.animating = true;
|
|
from.animating = true;
|
|
to.animating = true;
|
|
|
|
changeClass(from, 'remove');
|
|
changeClass(to, 'remove');
|
|
|
|
const cancelFn = morph({
|
|
from: from.el,
|
|
to: to.el,
|
|
onToggle () {
|
|
changeClass(from, 'add');
|
|
changeClass(to, 'remove');
|
|
},
|
|
...to.opts,
|
|
onEnd (dir, aborted) {
|
|
to.opts.onEnd !== void 0 && to.opts.onEnd(dir, aborted);
|
|
|
|
if (aborted === true) {
|
|
return
|
|
}
|
|
|
|
from.animating = false;
|
|
to.animating = false;
|
|
|
|
group.animating = false;
|
|
group.cancel = void 0;
|
|
group.queue.shift();
|
|
|
|
trigger(group);
|
|
}
|
|
});
|
|
|
|
group.cancel = () => {
|
|
cancelFn(true); // abort
|
|
group.cancel = void 0;
|
|
};
|
|
}
|
|
|
|
function updateModifiers (mod, ctx) {
|
|
const opts = ctx.opts;
|
|
|
|
mods.forEach(name => {
|
|
opts[ name ] = mod[ name ] === true;
|
|
});
|
|
}
|
|
|
|
function insertArgs (arg, ctx) {
|
|
const opts = typeof arg === 'string' && arg.length !== 0
|
|
? arg.split(':') : [];
|
|
|
|
ctx.name = opts[ 0 ];
|
|
ctx.group = opts[ 1 ];
|
|
|
|
Object.assign(ctx.opts, {
|
|
duration: isNaN(opts[ 2 ]) === true
|
|
? 300
|
|
: parseFloat(opts[ 2 ]),
|
|
waitFor: opts[ 3 ]
|
|
});
|
|
}
|
|
|
|
function updateArgs (arg, ctx) {
|
|
if (arg.group !== void 0) {
|
|
ctx.group = arg.group;
|
|
}
|
|
if (arg.name !== void 0) {
|
|
ctx.name = arg.name;
|
|
}
|
|
|
|
const opts = ctx.opts;
|
|
|
|
props$1.forEach(name => {
|
|
if (arg[ name ] !== void 0) {
|
|
opts[ name ] = arg[ name ];
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateModel (name, ctx) {
|
|
if (ctx.name === name) {
|
|
const group = morphGroups[ ctx.group ];
|
|
|
|
// if group is not registered
|
|
if (group === void 0) {
|
|
morphGroups[ ctx.group ] = {
|
|
name: ctx.group,
|
|
model: name,
|
|
queue: [ ctx ],
|
|
animating: false
|
|
};
|
|
|
|
changeClass(ctx, 'remove');
|
|
}
|
|
// if model changed
|
|
else if (group.model !== name) {
|
|
group.model = name;
|
|
group.queue.push(ctx);
|
|
|
|
if (group.animating === false && group.queue.length === 2) {
|
|
trigger(group);
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if (ctx.animating === false) {
|
|
changeClass(ctx, 'add');
|
|
}
|
|
}
|
|
|
|
function updateValue (ctx, value) {
|
|
let model;
|
|
|
|
if (Object(value) === value) {
|
|
model = '' + value.model;
|
|
updateArgs(value, ctx);
|
|
updateModifiers(value, ctx);
|
|
}
|
|
else {
|
|
model = '' + value;
|
|
}
|
|
|
|
if (model !== ctx.model) {
|
|
ctx.model = model;
|
|
updateModel(model, ctx);
|
|
}
|
|
else if (ctx.animating === false && ctx.clsAction !== void 0) {
|
|
// ensure HMR
|
|
ctx.el.classList[ ctx.clsAction ]('q-morph--invisible');
|
|
}
|
|
}
|
|
|
|
var Morph = createDirective({
|
|
name: 'morph',
|
|
|
|
mounted (el, binding) {
|
|
const ctx = {
|
|
el,
|
|
animating: false,
|
|
opts: {}
|
|
};
|
|
|
|
updateModifiers(binding.modifiers, ctx);
|
|
insertArgs(binding.arg, ctx);
|
|
updateValue(ctx, binding.value);
|
|
|
|
el.__qmorph = ctx;
|
|
},
|
|
|
|
updated (el, binding) {
|
|
updateValue(el.__qmorph, binding.value);
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qmorph;
|
|
|
|
const group = morphGroups[ ctx.group ];
|
|
|
|
if (group !== void 0) {
|
|
const index = group.queue.indexOf(ctx);
|
|
|
|
if (index !== -1) {
|
|
group.queue = group.queue.filter(item => item !== ctx);
|
|
|
|
if (group.queue.length === 0) {
|
|
group.cancel !== void 0 && group.cancel();
|
|
delete morphGroups[ ctx.group ];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx.clsAction === 'add') {
|
|
el.classList.remove('q-morph--invisible');
|
|
}
|
|
|
|
delete el.__qmorph;
|
|
}
|
|
}
|
|
);
|
|
|
|
const defaultCfg = {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
characterData: true,
|
|
attributeOldValue: true,
|
|
characterDataOldValue: true
|
|
};
|
|
|
|
function update$2 (el, ctx, value) {
|
|
ctx.handler = value;
|
|
ctx.observer !== void 0 && ctx.observer.disconnect();
|
|
|
|
ctx.observer = new MutationObserver(list => {
|
|
if (typeof ctx.handler === 'function') {
|
|
const res = ctx.handler(list);
|
|
if (res === false || ctx.once === true) {
|
|
destroy(el);
|
|
}
|
|
}
|
|
});
|
|
|
|
ctx.observer.observe(el, ctx.opts);
|
|
}
|
|
|
|
function destroy (el) {
|
|
const ctx = el.__qmutation;
|
|
|
|
if (ctx !== void 0) {
|
|
ctx.observer !== void 0 && ctx.observer.disconnect();
|
|
delete el.__qmutation;
|
|
}
|
|
}
|
|
|
|
var Mutation = createDirective({
|
|
name: 'mutation',
|
|
|
|
mounted (el, { modifiers: { once, ...mod }, value }) {
|
|
const ctx = {
|
|
once,
|
|
opts: Object.keys(mod).length === 0
|
|
? defaultCfg
|
|
: mod
|
|
};
|
|
|
|
update$2(el, ctx, value);
|
|
|
|
el.__qmutation = ctx;
|
|
},
|
|
|
|
updated (el, { oldValue, value }) {
|
|
const ctx = el.__qmutation;
|
|
if (ctx !== void 0 && oldValue !== value) {
|
|
update$2(el, ctx, value);
|
|
}
|
|
},
|
|
|
|
beforeUnmount: destroy
|
|
}
|
|
);
|
|
|
|
const { passive } = listenOpts;
|
|
|
|
function update$1 (ctx, { value, oldValue }) {
|
|
if (typeof value !== 'function') {
|
|
ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, passive);
|
|
return
|
|
}
|
|
|
|
ctx.handler = value;
|
|
if (typeof oldValue !== 'function') {
|
|
ctx.scrollTarget.addEventListener('scroll', ctx.scroll, passive);
|
|
ctx.scroll();
|
|
}
|
|
}
|
|
|
|
var ScrollFire = createDirective({
|
|
name: 'scroll-fire',
|
|
|
|
mounted (el, binding) {
|
|
const ctx = {
|
|
scrollTarget: getScrollTarget(el),
|
|
scroll: debounce(() => {
|
|
let containerBottom, elBottom;
|
|
|
|
if (ctx.scrollTarget === window) {
|
|
elBottom = el.getBoundingClientRect().bottom;
|
|
containerBottom = window.innerHeight;
|
|
}
|
|
else {
|
|
elBottom = offset(el).top + height(el);
|
|
containerBottom = offset(ctx.scrollTarget).top + height(ctx.scrollTarget);
|
|
}
|
|
|
|
if (elBottom > 0 && elBottom < containerBottom) {
|
|
ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, passive);
|
|
ctx.handler(el);
|
|
}
|
|
}, 25)
|
|
};
|
|
|
|
update$1(ctx, binding);
|
|
|
|
el.__qscrollfire = ctx;
|
|
},
|
|
|
|
updated (el, binding) {
|
|
if (binding.value !== binding.oldValue) {
|
|
update$1(el.__qscrollfire, binding);
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qscrollfire;
|
|
ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, passive);
|
|
ctx.scroll.cancel();
|
|
delete el.__qscrollfire;
|
|
}
|
|
}
|
|
);
|
|
|
|
function update (ctx, { value, oldValue }) {
|
|
if (typeof value !== 'function') {
|
|
ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, listenOpts.passive);
|
|
return
|
|
}
|
|
|
|
ctx.handler = value;
|
|
if (typeof oldValue !== 'function') {
|
|
ctx.scrollTarget.addEventListener('scroll', ctx.scroll, listenOpts.passive);
|
|
}
|
|
}
|
|
|
|
var Scroll = createDirective({
|
|
name: 'scroll',
|
|
|
|
mounted (el, binding) {
|
|
const ctx = {
|
|
scrollTarget: getScrollTarget(el),
|
|
scroll () {
|
|
ctx.handler(
|
|
getVerticalScrollPosition(ctx.scrollTarget),
|
|
getHorizontalScrollPosition(ctx.scrollTarget)
|
|
);
|
|
}
|
|
};
|
|
|
|
update(ctx, binding);
|
|
|
|
el.__qscroll = ctx;
|
|
},
|
|
|
|
updated (el, binding) {
|
|
if (el.__qscroll !== void 0 && binding.oldValue !== binding.value) {
|
|
update(el.__qscroll, binding);
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qscroll;
|
|
ctx.scrollTarget.removeEventListener('scroll', ctx.scroll, listenOpts.passive);
|
|
delete el.__qscroll;
|
|
}
|
|
}
|
|
);
|
|
|
|
var TouchHold = createDirective({
|
|
name: 'touch-hold',
|
|
|
|
beforeMount (el, binding) {
|
|
const { modifiers } = binding;
|
|
|
|
// early return, we don't need to do anything
|
|
if (modifiers.mouse !== true && client.has.touch !== true) {
|
|
return
|
|
}
|
|
|
|
const ctx = {
|
|
handler: binding.value,
|
|
noop,
|
|
|
|
mouseStart (evt) {
|
|
if (typeof ctx.handler === 'function' && leftClick(evt) === true) {
|
|
addEvt(ctx, 'temp', [
|
|
[ document, 'mousemove', 'move', 'passiveCapture' ],
|
|
[ document, 'click', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt, true);
|
|
}
|
|
},
|
|
|
|
touchStart (evt) {
|
|
if (evt.target !== void 0 && typeof ctx.handler === 'function') {
|
|
const target = evt.target;
|
|
addEvt(ctx, 'temp', [
|
|
[ target, 'touchmove', 'move', 'passiveCapture' ],
|
|
[ target, 'touchcancel', 'end', 'notPassiveCapture' ],
|
|
[ target, 'touchend', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt);
|
|
}
|
|
},
|
|
|
|
start (evt, mouseEvent) {
|
|
ctx.origin = position(evt);
|
|
|
|
const startTime = Date.now();
|
|
|
|
if (client.is.mobile === true) {
|
|
document.body.classList.add('non-selectable');
|
|
clearSelection();
|
|
|
|
ctx.styleCleanup = withDelay => {
|
|
ctx.styleCleanup = void 0;
|
|
|
|
const remove = () => {
|
|
document.body.classList.remove('non-selectable');
|
|
};
|
|
|
|
if (withDelay === true) {
|
|
clearSelection();
|
|
setTimeout(remove, 10);
|
|
}
|
|
else { remove(); }
|
|
};
|
|
}
|
|
|
|
ctx.triggered = false;
|
|
ctx.sensitivity = mouseEvent === true
|
|
? ctx.mouseSensitivity
|
|
: ctx.touchSensitivity;
|
|
|
|
ctx.timer = setTimeout(() => {
|
|
ctx.timer = void 0;
|
|
clearSelection();
|
|
ctx.triggered = true;
|
|
|
|
ctx.handler({
|
|
evt,
|
|
touch: mouseEvent !== true,
|
|
mouse: mouseEvent === true,
|
|
position: ctx.origin,
|
|
duration: Date.now() - startTime
|
|
});
|
|
}, ctx.duration);
|
|
},
|
|
|
|
move (evt) {
|
|
const { top, left } = position(evt);
|
|
if (
|
|
ctx.timer !== void 0 && (
|
|
Math.abs(left - ctx.origin.left) >= ctx.sensitivity
|
|
|| Math.abs(top - ctx.origin.top) >= ctx.sensitivity
|
|
)
|
|
) {
|
|
clearTimeout(ctx.timer);
|
|
ctx.timer = void 0;
|
|
}
|
|
},
|
|
|
|
end (evt) {
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
// delay needed otherwise selection still occurs
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup(ctx.triggered);
|
|
|
|
if (ctx.triggered === true) {
|
|
evt !== void 0 && stopAndPrevent(evt);
|
|
}
|
|
else if (ctx.timer !== void 0) {
|
|
clearTimeout(ctx.timer);
|
|
ctx.timer = void 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
// duration in ms, touch in pixels, mouse in pixels
|
|
const data = [ 600, 5, 7 ];
|
|
|
|
if (typeof binding.arg === 'string' && binding.arg.length !== 0) {
|
|
binding.arg.split(':').forEach((val, index) => {
|
|
const v = parseInt(val, 10);
|
|
v && (data[ index ] = v);
|
|
});
|
|
}
|
|
|
|
[ ctx.duration, ctx.touchSensitivity, ctx.mouseSensitivity ] = data;
|
|
|
|
el.__qtouchhold = ctx;
|
|
|
|
if (modifiers.mouse === true) {
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true
|
|
? 'Capture'
|
|
: '';
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'mousedown', 'mouseStart', `passive${ capture }` ]
|
|
]);
|
|
}
|
|
|
|
client.has.touch === true && addEvt(ctx, 'main', [
|
|
[ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ],
|
|
[ el, 'touchend', 'noop', 'notPassiveCapture' ]
|
|
]);
|
|
},
|
|
|
|
updated (el, binding) {
|
|
const ctx = el.__qtouchhold;
|
|
|
|
if (ctx !== void 0 && binding.oldValue !== binding.value) {
|
|
typeof binding.value !== 'function' && ctx.end();
|
|
ctx.handler = binding.value;
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qtouchhold;
|
|
|
|
if (ctx !== void 0) {
|
|
cleanEvt(ctx, 'main');
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
ctx.timer !== void 0 && clearTimeout(ctx.timer);
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup();
|
|
|
|
delete el.__qtouchhold;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
const
|
|
keyCodes = {
|
|
esc: 27,
|
|
tab: 9,
|
|
enter: 13,
|
|
space: 32,
|
|
up: 38,
|
|
left: 37,
|
|
right: 39,
|
|
down: 40,
|
|
delete: [ 8, 46 ]
|
|
},
|
|
keyRegex = new RegExp(`^([\\d+]+|${ Object.keys(keyCodes).join('|') })$`, 'i');
|
|
|
|
function shouldEnd (evt, origin) {
|
|
const { top, left } = position(evt);
|
|
|
|
return Math.abs(left - origin.left) >= 7
|
|
|| Math.abs(top - origin.top) >= 7
|
|
}
|
|
|
|
var TouchRepeat = createDirective({
|
|
name: 'touch-repeat',
|
|
|
|
beforeMount (el, { modifiers, value, arg }) {
|
|
const keyboard = Object.keys(modifiers).reduce((acc, key) => {
|
|
if (keyRegex.test(key) === true) {
|
|
const keyCode = isNaN(parseInt(key, 10)) ? keyCodes[ key.toLowerCase() ] : parseInt(key, 10);
|
|
keyCode >= 0 && acc.push(keyCode);
|
|
}
|
|
return acc
|
|
}, []);
|
|
|
|
// early return, we don't need to do anything
|
|
if (
|
|
modifiers.mouse !== true
|
|
&& client.has.touch !== true
|
|
&& keyboard.length === 0
|
|
) {
|
|
return
|
|
}
|
|
|
|
const durations = typeof arg === 'string' && arg.length !== 0
|
|
? arg.split(':').map(val => parseInt(val, 10))
|
|
: [ 0, 600, 300 ];
|
|
|
|
const durationsLast = durations.length - 1;
|
|
|
|
const ctx = {
|
|
keyboard,
|
|
handler: value,
|
|
|
|
noop,
|
|
|
|
mouseStart (evt) {
|
|
if (ctx.event === void 0 && typeof ctx.handler === 'function' && leftClick(evt) === true) {
|
|
addEvt(ctx, 'temp', [
|
|
[ document, 'mousemove', 'move', 'passiveCapture' ],
|
|
[ document, 'click', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt, true);
|
|
}
|
|
},
|
|
|
|
keyboardStart (evt) {
|
|
if (typeof ctx.handler === 'function' && isKeyCode(evt, keyboard) === true) {
|
|
if (durations[ 0 ] === 0 || ctx.event !== void 0) {
|
|
stopAndPrevent(evt);
|
|
el.focus();
|
|
if (ctx.event !== void 0) {
|
|
return
|
|
}
|
|
}
|
|
|
|
addEvt(ctx, 'temp', [
|
|
[ document, 'keyup', 'end', 'notPassiveCapture' ],
|
|
[ document, 'click', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt, false, true);
|
|
}
|
|
},
|
|
|
|
touchStart (evt) {
|
|
if (evt.target !== void 0 && typeof ctx.handler === 'function') {
|
|
const target = evt.target;
|
|
addEvt(ctx, 'temp', [
|
|
[ target, 'touchmove', 'move', 'passiveCapture' ],
|
|
[ target, 'touchcancel', 'end', 'notPassiveCapture' ],
|
|
[ target, 'touchend', 'end', 'notPassiveCapture' ]
|
|
]);
|
|
ctx.start(evt);
|
|
}
|
|
},
|
|
|
|
start (evt, mouseEvent, keyboardEvent) {
|
|
if (keyboardEvent !== true) {
|
|
ctx.origin = position(evt);
|
|
}
|
|
|
|
function styleCleanup (withDelay) {
|
|
ctx.styleCleanup = void 0;
|
|
|
|
document.documentElement.style.cursor = '';
|
|
|
|
const remove = () => {
|
|
document.body.classList.remove('non-selectable');
|
|
};
|
|
|
|
if (withDelay === true) {
|
|
clearSelection();
|
|
setTimeout(remove, 10);
|
|
}
|
|
else { remove(); }
|
|
}
|
|
|
|
if (client.is.mobile === true) {
|
|
document.body.classList.add('non-selectable');
|
|
clearSelection();
|
|
ctx.styleCleanup = styleCleanup;
|
|
}
|
|
|
|
ctx.event = {
|
|
touch: mouseEvent !== true && keyboardEvent !== true,
|
|
mouse: mouseEvent === true,
|
|
keyboard: keyboardEvent === true,
|
|
startTime: Date.now(),
|
|
repeatCount: 0
|
|
};
|
|
|
|
const fn = () => {
|
|
ctx.timer = void 0;
|
|
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
if (ctx.event.repeatCount === 0) {
|
|
ctx.event.evt = evt;
|
|
|
|
if (keyboardEvent === true) {
|
|
ctx.event.keyCode = evt.keyCode;
|
|
}
|
|
else {
|
|
ctx.event.position = position(evt);
|
|
}
|
|
|
|
if (client.is.mobile !== true) {
|
|
document.documentElement.style.cursor = 'pointer';
|
|
document.body.classList.add('non-selectable');
|
|
clearSelection();
|
|
ctx.styleCleanup = styleCleanup;
|
|
}
|
|
}
|
|
|
|
ctx.event.duration = Date.now() - ctx.event.startTime;
|
|
ctx.event.repeatCount += 1;
|
|
|
|
ctx.handler(ctx.event);
|
|
|
|
const index = durationsLast < ctx.event.repeatCount
|
|
? durationsLast
|
|
: ctx.event.repeatCount;
|
|
|
|
ctx.timer = setTimeout(fn, durations[ index ]);
|
|
};
|
|
|
|
if (durations[ 0 ] === 0) {
|
|
fn();
|
|
}
|
|
else {
|
|
ctx.timer = setTimeout(fn, durations[ 0 ]);
|
|
}
|
|
},
|
|
|
|
move (evt) {
|
|
if (ctx.event !== void 0 && ctx.timer !== void 0 && shouldEnd(evt, ctx.origin) === true) {
|
|
clearTimeout(ctx.timer);
|
|
ctx.timer = void 0;
|
|
}
|
|
},
|
|
|
|
end (evt) {
|
|
if (ctx.event === void 0) {
|
|
return
|
|
}
|
|
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup(true);
|
|
evt !== void 0 && ctx.event.repeatCount > 0 && stopAndPrevent(evt);
|
|
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
if (ctx.timer !== void 0) {
|
|
clearTimeout(ctx.timer);
|
|
ctx.timer = void 0;
|
|
}
|
|
|
|
ctx.event = void 0;
|
|
}
|
|
};
|
|
|
|
el.__qtouchrepeat = ctx;
|
|
|
|
if (modifiers.mouse === true) {
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
const capture = modifiers.mouseCapture === true || modifiers.mousecapture === true
|
|
? 'Capture'
|
|
: '';
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'mousedown', 'mouseStart', `passive${ capture }` ]
|
|
]);
|
|
}
|
|
|
|
client.has.touch === true && addEvt(ctx, 'main', [
|
|
[ el, 'touchstart', 'touchStart', `passive${ modifiers.capture === true ? 'Capture' : '' }` ],
|
|
[ el, 'touchend', 'noop', 'passiveCapture' ]
|
|
]);
|
|
|
|
if (keyboard.length !== 0) {
|
|
// account for UMD too where modifiers will be lowercased to work
|
|
const capture = modifiers.keyCapture === true || modifiers.keycapture === true
|
|
? 'Capture'
|
|
: '';
|
|
|
|
addEvt(ctx, 'main', [
|
|
[ el, 'keydown', 'keyboardStart', `notPassive${ capture }` ]
|
|
]);
|
|
}
|
|
},
|
|
|
|
updated (el, { oldValue, value }) {
|
|
const ctx = el.__qtouchrepeat;
|
|
|
|
if (ctx !== void 0 && oldValue !== value) {
|
|
typeof value !== 'function' && ctx.end();
|
|
ctx.handler = value;
|
|
}
|
|
},
|
|
|
|
beforeUnmount (el) {
|
|
const ctx = el.__qtouchrepeat;
|
|
|
|
if (ctx !== void 0) {
|
|
ctx.timer !== void 0 && clearTimeout(ctx.timer);
|
|
|
|
cleanEvt(ctx, 'main');
|
|
cleanEvt(ctx, 'temp');
|
|
|
|
ctx.styleCleanup !== void 0 && ctx.styleCleanup();
|
|
|
|
delete el.__qtouchrepeat;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
var directives = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
ClosePopup: ClosePopup,
|
|
Intersection: Intersection,
|
|
Morph: Morph,
|
|
Mutation: Mutation,
|
|
Ripple: Ripple,
|
|
ScrollFire: ScrollFire,
|
|
Scroll: Scroll,
|
|
TouchHold: TouchHold,
|
|
TouchPan: TouchPan,
|
|
TouchRepeat: TouchRepeat,
|
|
TouchSwipe: TouchSwipe
|
|
});
|
|
|
|
function getCssVar (propName, element = document.body) {
|
|
if (typeof propName !== 'string') {
|
|
throw new TypeError('Expected a string as propName')
|
|
}
|
|
if (!(element instanceof Element)) {
|
|
throw new TypeError('Expected a DOM element')
|
|
}
|
|
|
|
return getComputedStyle(element).getPropertyValue(`--q-${ propName }`).trim() || null
|
|
}
|
|
|
|
let metaValue;
|
|
|
|
function getProp () {
|
|
return client.is.winphone
|
|
? 'msapplication-navbutton-color'
|
|
: (
|
|
client.is.safari
|
|
? 'apple-mobile-web-app-status-bar-style'
|
|
: 'theme-color' // Chrome, Firefox OS, Opera, Vivaldi, ...
|
|
)
|
|
}
|
|
|
|
function getMetaTag (v) {
|
|
const els = document.getElementsByTagName('META');
|
|
for (const i in els) {
|
|
if (els[ i ].name === v) {
|
|
return els[ i ]
|
|
}
|
|
}
|
|
}
|
|
|
|
function setColor (hexColor) {
|
|
if (metaValue === void 0) {
|
|
// cache it
|
|
metaValue = getProp();
|
|
}
|
|
|
|
let metaTag = getMetaTag(metaValue);
|
|
const newTag = metaTag === void 0;
|
|
|
|
if (newTag) {
|
|
metaTag = document.createElement('meta');
|
|
metaTag.setAttribute('name', metaValue);
|
|
}
|
|
|
|
metaTag.setAttribute('content', hexColor);
|
|
|
|
if (newTag) {
|
|
document.head.appendChild(metaTag);
|
|
}
|
|
}
|
|
|
|
var AddressbarColor = {
|
|
set: client.is.mobile === true && (
|
|
client.is.nativeMobile === true
|
|
|| client.is.winphone === true || client.is.safari === true
|
|
|| client.is.webkit === true || client.is.vivaldi === true
|
|
)
|
|
? hexColor => {
|
|
const val = hexColor || getCssVar('primary');
|
|
|
|
if (client.is.nativeMobile === true && window.StatusBar) {
|
|
window.StatusBar.backgroundColorByHexString(val);
|
|
}
|
|
else {
|
|
setColor(val);
|
|
}
|
|
}
|
|
: noop,
|
|
|
|
install ({ $q }) {
|
|
$q.addressbarColor = this;
|
|
$q.config.addressbarColor && this.set($q.config.addressbarColor);
|
|
}
|
|
};
|
|
|
|
const prefixes = {};
|
|
|
|
function assignFn (fn) {
|
|
Object.assign(Plugin$6, {
|
|
request: fn,
|
|
exit: fn,
|
|
toggle: fn
|
|
});
|
|
}
|
|
|
|
function getFullscreenElement () {
|
|
return (
|
|
document.fullscreenElement
|
|
|| document.mozFullScreenElement
|
|
|| document.webkitFullscreenElement
|
|
|| document.msFullscreenElement
|
|
|| null
|
|
)
|
|
}
|
|
|
|
function updateEl () {
|
|
const newEl = Plugin$6.activeEl = Plugin$6.isActive === false
|
|
? null
|
|
: getFullscreenElement();
|
|
|
|
changeGlobalNodesTarget(
|
|
newEl === null || newEl === document.documentElement
|
|
? document.body
|
|
: newEl
|
|
);
|
|
}
|
|
|
|
function togglePluginState () {
|
|
Plugin$6.isActive = Plugin$6.isActive === false;
|
|
updateEl();
|
|
}
|
|
|
|
// needed for consistency across browsers
|
|
function promisify (target, fn) {
|
|
try {
|
|
const res = target[ fn ]();
|
|
return res === void 0
|
|
? Promise.resolve()
|
|
: res
|
|
}
|
|
catch (err) {
|
|
return Promise.reject(err)
|
|
}
|
|
}
|
|
|
|
const Plugin$6 = defineReactivePlugin({
|
|
isActive: false,
|
|
activeEl: null
|
|
}, {
|
|
isCapable: false,
|
|
|
|
install ({ $q }) {
|
|
$q.fullscreen = this;
|
|
}
|
|
});
|
|
|
|
{
|
|
prefixes.request = [
|
|
'requestFullscreen',
|
|
'msRequestFullscreen', 'mozRequestFullScreen', 'webkitRequestFullscreen'
|
|
].find(request => document.documentElement[ request ] !== void 0);
|
|
|
|
Plugin$6.isCapable = prefixes.request !== void 0;
|
|
|
|
if (Plugin$6.isCapable === false) {
|
|
// it means the browser does NOT support it
|
|
assignFn(() => Promise.reject('Not capable'));
|
|
}
|
|
else {
|
|
Object.assign(Plugin$6, {
|
|
request (target) {
|
|
const el = target || document.documentElement;
|
|
const { activeEl } = Plugin$6;
|
|
|
|
if (el === activeEl) {
|
|
return Promise.resolve()
|
|
}
|
|
|
|
const queue = activeEl !== null && el.contains(activeEl) === true
|
|
? Plugin$6.exit()
|
|
: Promise.resolve();
|
|
|
|
return queue.finally(() => promisify(el, prefixes.request))
|
|
},
|
|
|
|
exit () {
|
|
return Plugin$6.isActive === true
|
|
? promisify(document, prefixes.exit)
|
|
: Promise.resolve()
|
|
},
|
|
|
|
toggle (target) {
|
|
return Plugin$6.isActive === true
|
|
? Plugin$6.exit()
|
|
: Plugin$6.request(target)
|
|
}
|
|
});
|
|
|
|
prefixes.exit = [
|
|
'exitFullscreen',
|
|
'msExitFullscreen', 'mozCancelFullScreen', 'webkitExitFullscreen'
|
|
].find(exit => document[ exit ]);
|
|
|
|
Plugin$6.isActive = Boolean(getFullscreenElement());
|
|
Plugin$6.isActive === true && updateEl()
|
|
|
|
;[
|
|
'onfullscreenchange',
|
|
'onmsfullscreenchange', 'onwebkitfullscreenchange'
|
|
].forEach(evt => {
|
|
document[ evt ] = togglePluginState;
|
|
});
|
|
}
|
|
}
|
|
|
|
const Plugin$5 = defineReactivePlugin({
|
|
appVisible: true
|
|
}, {
|
|
install ({ $q }) {
|
|
|
|
injectProp($q, 'appVisible', () => this.appVisible);
|
|
}
|
|
});
|
|
|
|
{
|
|
let prop, evt;
|
|
|
|
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
|
|
prop = 'hidden';
|
|
evt = 'visibilitychange';
|
|
}
|
|
else if (typeof document.msHidden !== 'undefined') {
|
|
prop = 'msHidden';
|
|
evt = 'msvisibilitychange';
|
|
}
|
|
else if (typeof document.webkitHidden !== 'undefined') {
|
|
prop = 'webkitHidden';
|
|
evt = 'webkitvisibilitychange';
|
|
}
|
|
|
|
if (evt && typeof document[ prop ] !== 'undefined') {
|
|
const update = () => { Plugin$5.appVisible = !document[ prop ]; };
|
|
document.addEventListener(evt, update, false);
|
|
}
|
|
}
|
|
|
|
var BottomSheet$1 = createComponent({
|
|
name: 'BottomSheetPlugin',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
title: String,
|
|
message: String,
|
|
actions: Array,
|
|
|
|
grid: Boolean,
|
|
|
|
cardClass: [ String, Array, Object ],
|
|
cardStyle: [ String, Array, Object ]
|
|
},
|
|
|
|
emits: [ 'ok', 'hide' ],
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const isDark = useDark(props, proxy.$q);
|
|
|
|
const dialogRef = vue.ref(null);
|
|
|
|
function show () {
|
|
dialogRef.value.show();
|
|
}
|
|
|
|
function hide () {
|
|
dialogRef.value.hide();
|
|
}
|
|
|
|
function onOk (action) {
|
|
emit('ok', action);
|
|
hide();
|
|
}
|
|
|
|
function onHide () {
|
|
emit('hide');
|
|
}
|
|
|
|
function getGrid () {
|
|
return props.actions.map(action => {
|
|
const img = action.avatar || action.img;
|
|
|
|
return action.label === void 0
|
|
? vue.h(QSeparator, {
|
|
class: 'col-all',
|
|
dark: isDark.value
|
|
})
|
|
: vue.h('div', {
|
|
class: [
|
|
'q-bottom-sheet__item q-hoverable q-focusable cursor-pointer relative-position',
|
|
action.class
|
|
],
|
|
style: action.style,
|
|
tabindex: 0,
|
|
role: 'listitem',
|
|
onClick () { onOk(action); },
|
|
onKeyup (e) { e.keyCode === 13 && onOk(action); }
|
|
}, [
|
|
vue.h('div', { class: 'q-focus-helper' }),
|
|
|
|
action.icon
|
|
? vue.h(QIcon, { name: action.icon, color: action.color })
|
|
: (
|
|
img
|
|
? vue.h('img', {
|
|
class: action.avatar ? 'q-bottom-sheet__avatar' : '',
|
|
src: img
|
|
})
|
|
: vue.h('div', { class: 'q-bottom-sheet__empty-icon' })
|
|
),
|
|
|
|
vue.h('div', action.label)
|
|
])
|
|
})
|
|
}
|
|
|
|
function getList () {
|
|
return props.actions.map(action => {
|
|
const img = action.avatar || action.img;
|
|
|
|
return action.label === void 0
|
|
? vue.h(QSeparator, { spaced: true, dark: isDark.value })
|
|
: vue.h(QItem, {
|
|
class: [ 'q-bottom-sheet__item', action.classes ],
|
|
style: action.style,
|
|
tabindex: 0,
|
|
clickable: true,
|
|
dark: isDark.value,
|
|
onClick () { onOk(action); }
|
|
}, () => [
|
|
vue.h(
|
|
QItemSection,
|
|
{ avatar: true },
|
|
() => (
|
|
action.icon
|
|
? vue.h(QIcon, { name: action.icon, color: action.color })
|
|
: (
|
|
img
|
|
? vue.h('img', {
|
|
class: action.avatar ? 'q-bottom-sheet__avatar' : '',
|
|
src: img
|
|
})
|
|
: null
|
|
)
|
|
)
|
|
),
|
|
|
|
vue.h(QItemSection, () => action.label)
|
|
])
|
|
})
|
|
}
|
|
|
|
function getCardContent () {
|
|
const child = [];
|
|
|
|
props.title && child.push(
|
|
vue.h(QCardSection, {
|
|
class: 'q-dialog__title'
|
|
}, () => props.title)
|
|
);
|
|
|
|
props.message && child.push(
|
|
vue.h(QCardSection, {
|
|
class: 'q-dialog__message'
|
|
}, () => props.message)
|
|
);
|
|
|
|
child.push(
|
|
props.grid === true
|
|
? vue.h('div', {
|
|
class: 'row items-stretch justify-start',
|
|
role: 'list'
|
|
}, getGrid())
|
|
: vue.h('div', {
|
|
role: 'list'
|
|
}, getList())
|
|
);
|
|
|
|
return child
|
|
}
|
|
|
|
function getContent () {
|
|
return [
|
|
vue.h(QCard, {
|
|
class: [
|
|
`q-bottom-sheet q-bottom-sheet--${ props.grid === true ? 'grid' : 'list' }`
|
|
+ (isDark.value === true ? ' q-bottom-sheet--dark q-dark' : ''),
|
|
props.cardClass
|
|
],
|
|
style: props.cardStyle
|
|
}, getCardContent)
|
|
]
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { show, hide });
|
|
|
|
return () => vue.h(QDialog, {
|
|
ref: dialogRef,
|
|
position: 'bottom',
|
|
onHide
|
|
}, getContent)
|
|
}
|
|
});
|
|
|
|
function merge (target, source) {
|
|
for (const key in source) {
|
|
if (key !== 'spinner' && Object(source[ key ]) === source[ key ]) {
|
|
target[ key ] = Object(target[ key ]) !== target[ key ]
|
|
? {}
|
|
: { ...target[ key ] };
|
|
|
|
merge(target[ key ], source[ key ]);
|
|
}
|
|
else {
|
|
target[ key ] = source[ key ];
|
|
}
|
|
}
|
|
}
|
|
|
|
function globalDialog (DefaultComponent, supportsCustomComponent, parentApp) {
|
|
return pluginProps => {
|
|
|
|
let DialogComponent, props;
|
|
const isCustom = supportsCustomComponent === true
|
|
&& pluginProps.component !== void 0;
|
|
|
|
if (isCustom === true) {
|
|
const { component, componentProps } = pluginProps;
|
|
|
|
DialogComponent = (typeof component === 'string')
|
|
? parentApp.component(component)
|
|
: component;
|
|
|
|
props = componentProps || {};
|
|
}
|
|
else {
|
|
const { class: klass, style, ...otherProps } = pluginProps;
|
|
|
|
DialogComponent = DefaultComponent;
|
|
props = otherProps;
|
|
klass !== void 0 && (otherProps.cardClass = klass);
|
|
style !== void 0 && (otherProps.cardStyle = style);
|
|
}
|
|
|
|
let vm, emittedOK = false;
|
|
const dialogRef = vue.ref(null);
|
|
const el = createGlobalNode(false, 'dialog');
|
|
|
|
const applyState = cmd => {
|
|
if (dialogRef.value !== null && dialogRef.value[ cmd ] !== void 0) {
|
|
dialogRef.value[ cmd ]();
|
|
return
|
|
}
|
|
|
|
const target = vm.$.subTree;
|
|
|
|
if (target && target.component) {
|
|
// account for "script setup" way of declaring component
|
|
if (target.component.proxy && target.component.proxy[ cmd ]) {
|
|
target.component.proxy[ cmd ]();
|
|
return
|
|
}
|
|
|
|
// account for "script setup" + async component way of declaring component
|
|
if (
|
|
target.component.subTree
|
|
&& target.component.subTree.component
|
|
&& target.component.subTree.component.proxy
|
|
&& target.component.subTree.component.proxy[ cmd ]
|
|
) {
|
|
target.component.subTree.component.proxy[ cmd ]();
|
|
return
|
|
}
|
|
}
|
|
|
|
console.error('[Quasar] Incorrectly defined Dialog component');
|
|
};
|
|
|
|
const
|
|
okFns = [],
|
|
cancelFns = [],
|
|
API = {
|
|
onOk (fn) {
|
|
okFns.push(fn);
|
|
return API
|
|
},
|
|
onCancel (fn) {
|
|
cancelFns.push(fn);
|
|
return API
|
|
},
|
|
onDismiss (fn) {
|
|
okFns.push(fn);
|
|
cancelFns.push(fn);
|
|
return API
|
|
},
|
|
hide () {
|
|
applyState('hide');
|
|
return API
|
|
},
|
|
update (componentProps) {
|
|
if (vm !== null) {
|
|
if (isCustom === true) {
|
|
Object.assign(props, componentProps);
|
|
}
|
|
else {
|
|
const { class: klass, style, ...cfg } = componentProps;
|
|
|
|
klass !== void 0 && (cfg.cardClass = klass);
|
|
style !== void 0 && (cfg.cardStyle = style);
|
|
merge(props, cfg);
|
|
}
|
|
|
|
vm.$forceUpdate();
|
|
}
|
|
|
|
return API
|
|
}
|
|
};
|
|
|
|
const onOk = data => {
|
|
emittedOK = true;
|
|
okFns.forEach(fn => { fn(data); });
|
|
};
|
|
|
|
const onHide = () => {
|
|
app.unmount(el);
|
|
removeGlobalNode(el);
|
|
app = null;
|
|
vm = null;
|
|
|
|
if (emittedOK !== true) {
|
|
cancelFns.forEach(fn => { fn(); });
|
|
}
|
|
};
|
|
|
|
let app = createChildApp({
|
|
name: 'QGlobalDialog',
|
|
setup: () => () => vue.h(DialogComponent, {
|
|
...props,
|
|
ref: dialogRef,
|
|
onOk,
|
|
onHide,
|
|
onVnodeMounted (...args) {
|
|
if (typeof props.onVnodeMounted === 'function') {
|
|
props.onVnodeMounted(...args);
|
|
}
|
|
|
|
vue.nextTick(() => applyState('show'));
|
|
}
|
|
})
|
|
}, parentApp);
|
|
|
|
vm = app.mount(el);
|
|
|
|
return API
|
|
}
|
|
}
|
|
|
|
var BottomSheet = {
|
|
install ({ $q, parentApp }) {
|
|
$q.bottomSheet = globalDialog(BottomSheet$1, false, parentApp);
|
|
if (this.__installed !== true) {
|
|
this.create = $q.bottomSheet;
|
|
}
|
|
}
|
|
};
|
|
|
|
function encode$1 (string) {
|
|
return encodeURIComponent(string)
|
|
}
|
|
|
|
function decode$1 (string) {
|
|
return decodeURIComponent(string)
|
|
}
|
|
|
|
function stringifyCookieValue (value) {
|
|
return encode$1(value === Object(value) ? JSON.stringify(value) : '' + value)
|
|
}
|
|
|
|
function read (string) {
|
|
if (string === '') {
|
|
return string
|
|
}
|
|
|
|
if (string.indexOf('"') === 0) {
|
|
// This is a quoted cookie as according to RFC2068, unescape...
|
|
string = string.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
}
|
|
|
|
// Replace server-side written pluses with spaces.
|
|
// If we can't decode the cookie, ignore it, it's unusable.
|
|
// If we can't parse the cookie, ignore it, it's unusable.
|
|
string = decode$1(string.replace(/\+/g, ' '));
|
|
|
|
try {
|
|
const parsed = JSON.parse(string);
|
|
|
|
if (parsed === Object(parsed) || Array.isArray(parsed) === true) {
|
|
string = parsed;
|
|
}
|
|
}
|
|
catch (e) {}
|
|
|
|
return string
|
|
}
|
|
|
|
function getString (msOffset) {
|
|
const time = new Date();
|
|
time.setMilliseconds(time.getMilliseconds() + msOffset);
|
|
return time.toUTCString()
|
|
}
|
|
|
|
function parseExpireString (str) {
|
|
let timestamp = 0;
|
|
|
|
const days = str.match(/(\d+)d/);
|
|
const hours = str.match(/(\d+)h/);
|
|
const minutes = str.match(/(\d+)m/);
|
|
const seconds = str.match(/(\d+)s/);
|
|
|
|
if (days) { timestamp += days[ 1 ] * 864e+5; }
|
|
if (hours) { timestamp += hours[ 1 ] * 36e+5; }
|
|
if (minutes) { timestamp += minutes[ 1 ] * 6e+4; }
|
|
if (seconds) { timestamp += seconds[ 1 ] * 1000; }
|
|
|
|
return timestamp === 0
|
|
? str
|
|
: getString(timestamp)
|
|
}
|
|
|
|
function set (key, val, opts = {}, ssr) {
|
|
let expire, expireValue;
|
|
|
|
if (opts.expires !== void 0) {
|
|
// if it's a Date Object
|
|
if (Object.prototype.toString.call(opts.expires) === '[object Date]') {
|
|
expire = opts.expires.toUTCString();
|
|
}
|
|
// if it's a String (eg. "15m", "1h", "13d", "1d 15m", "31s")
|
|
// possible units: d (days), h (hours), m (minutes), s (seconds)
|
|
else if (typeof opts.expires === 'string') {
|
|
expire = parseExpireString(opts.expires);
|
|
}
|
|
// otherwise it must be a Number (defined in days)
|
|
else {
|
|
expireValue = parseFloat(opts.expires);
|
|
expire = isNaN(expireValue) === false
|
|
? getString(expireValue * 864e+5)
|
|
: opts.expires;
|
|
}
|
|
}
|
|
|
|
const keyValue = `${ encode$1(key) }=${ stringifyCookieValue(val) }`;
|
|
|
|
const cookie = [
|
|
keyValue,
|
|
expire !== void 0 ? '; Expires=' + expire : '', // use expires attribute, max-age is not supported by IE
|
|
opts.path ? '; Path=' + opts.path : '',
|
|
opts.domain ? '; Domain=' + opts.domain : '',
|
|
opts.sameSite ? '; SameSite=' + opts.sameSite : '',
|
|
opts.httpOnly ? '; HttpOnly' : '',
|
|
opts.secure ? '; Secure' : '',
|
|
opts.other ? '; ' + opts.other : ''
|
|
].join('');
|
|
|
|
if (ssr) {
|
|
if (ssr.req.qCookies) {
|
|
ssr.req.qCookies.push(cookie);
|
|
}
|
|
else {
|
|
ssr.req.qCookies = [ cookie ];
|
|
}
|
|
|
|
ssr.res.setHeader('Set-Cookie', ssr.req.qCookies);
|
|
|
|
// make temporary update so future get()
|
|
// within same SSR timeframe would return the set value
|
|
|
|
let all = ssr.req.headers.cookie || '';
|
|
|
|
if (expire !== void 0 && expireValue < 0) {
|
|
const val = get(key, ssr);
|
|
if (val !== undefined) {
|
|
all = all
|
|
.replace(`${ key }=${ val }; `, '')
|
|
.replace(`; ${ key }=${ val }`, '')
|
|
.replace(`${ key }=${ val }`, '');
|
|
}
|
|
}
|
|
else {
|
|
all = all
|
|
? `${ keyValue }; ${ all }`
|
|
: cookie;
|
|
}
|
|
|
|
ssr.req.headers.cookie = all;
|
|
}
|
|
else {
|
|
document.cookie = cookie;
|
|
}
|
|
}
|
|
|
|
function get (key, ssr) {
|
|
const
|
|
cookieSource = ssr ? ssr.req.headers : document,
|
|
cookies = cookieSource.cookie ? cookieSource.cookie.split('; ') : [],
|
|
l = cookies.length;
|
|
let
|
|
result = key ? null : {},
|
|
i = 0,
|
|
parts,
|
|
name,
|
|
cookie;
|
|
|
|
for (; i < l; i++) {
|
|
parts = cookies[ i ].split('=');
|
|
name = decode$1(parts.shift());
|
|
cookie = parts.join('=');
|
|
|
|
if (!key) {
|
|
result[ name ] = cookie;
|
|
}
|
|
else if (key === name) {
|
|
result = read(cookie);
|
|
break
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function remove (key, options, ssr) {
|
|
set(
|
|
key,
|
|
'',
|
|
{ expires: -1, ...options },
|
|
ssr
|
|
);
|
|
}
|
|
|
|
function has (key, ssr) {
|
|
return get(key, ssr) !== null
|
|
}
|
|
|
|
function getObject (ssr) {
|
|
return {
|
|
get: key => get(key, ssr),
|
|
set: (key, val, opts) => set(key, val, opts, ssr),
|
|
has: key => has(key, ssr),
|
|
remove: (key, options) => remove(key, options, ssr),
|
|
getAll: () => get(null, ssr)
|
|
}
|
|
}
|
|
|
|
const Plugin$4 = {
|
|
install ({ $q, ssrContext }) {
|
|
$q.cookies = this;
|
|
}
|
|
};
|
|
|
|
{
|
|
Object.assign(Plugin$4, getObject());
|
|
}
|
|
|
|
var DialogPlugin = createComponent({
|
|
name: 'DialogPlugin',
|
|
|
|
props: {
|
|
...useDarkProps,
|
|
|
|
title: String,
|
|
message: String,
|
|
prompt: Object,
|
|
options: Object,
|
|
progress: [ Boolean, Object ],
|
|
|
|
html: Boolean,
|
|
|
|
ok: {
|
|
type: [ String, Object, Boolean ],
|
|
default: true
|
|
},
|
|
cancel: [ String, Object, Boolean ],
|
|
focus: {
|
|
type: String,
|
|
default: 'ok',
|
|
validator: v => [ 'ok', 'cancel', 'none' ].includes(v)
|
|
},
|
|
|
|
stackButtons: Boolean,
|
|
color: String,
|
|
|
|
cardClass: [ String, Array, Object ],
|
|
cardStyle: [ String, Array, Object ]
|
|
},
|
|
|
|
emits: [ 'ok', 'hide' ],
|
|
|
|
setup (props, { emit }) {
|
|
const { proxy } = vue.getCurrentInstance();
|
|
const { $q } = proxy;
|
|
|
|
const isDark = useDark(props, $q);
|
|
|
|
const dialogRef = vue.ref(null);
|
|
|
|
const model = vue.ref(
|
|
props.prompt !== void 0
|
|
? props.prompt.model
|
|
: (props.options !== void 0 ? props.options.model : void 0)
|
|
);
|
|
|
|
const classes = vue.computed(() =>
|
|
'q-dialog-plugin'
|
|
+ (isDark.value === true ? ' q-dialog-plugin--dark q-dark' : '')
|
|
+ (props.progress !== false ? ' q-dialog-plugin--progress' : '')
|
|
);
|
|
|
|
const vmColor = vue.computed(() =>
|
|
props.color || (isDark.value === true ? 'amber' : 'primary')
|
|
);
|
|
|
|
const spinner = vue.computed(() => (
|
|
props.progress === false
|
|
? null
|
|
: (
|
|
isObject(props.progress) === true
|
|
? {
|
|
component: props.progress.spinner || QSpinner,
|
|
props: { color: props.progress.color || vmColor.value }
|
|
}
|
|
: {
|
|
component: QSpinner,
|
|
props: { color: vmColor.value }
|
|
}
|
|
)
|
|
));
|
|
|
|
const hasForm = vue.computed(() =>
|
|
props.prompt !== void 0 || props.options !== void 0
|
|
);
|
|
|
|
const formProps = vue.computed(() => {
|
|
if (hasForm.value !== true) {
|
|
return {}
|
|
}
|
|
|
|
const { model, isValid, items, ...formProps } = props.prompt !== void 0
|
|
? props.prompt
|
|
: props.options;
|
|
|
|
return formProps
|
|
});
|
|
|
|
const okLabel = vue.computed(() => (
|
|
isObject(props.ok) === true
|
|
? $q.lang.label.ok
|
|
: (
|
|
props.ok === true
|
|
? $q.lang.label.ok
|
|
: props.ok
|
|
)
|
|
));
|
|
|
|
const cancelLabel = vue.computed(() => (
|
|
isObject(props.cancel) === true
|
|
? $q.lang.label.cancel
|
|
: (
|
|
props.cancel === true
|
|
? $q.lang.label.cancel
|
|
: props.cancel
|
|
)
|
|
));
|
|
|
|
const okDisabled = vue.computed(() => {
|
|
if (props.prompt !== void 0) {
|
|
return props.prompt.isValid !== void 0
|
|
&& props.prompt.isValid(model.value) !== true
|
|
}
|
|
if (props.options !== void 0) {
|
|
return props.options.isValid !== void 0
|
|
&& props.options.isValid(model.value) !== true
|
|
}
|
|
return false
|
|
});
|
|
|
|
const okProps = vue.computed(() => ({
|
|
color: vmColor.value,
|
|
label: okLabel.value,
|
|
ripple: false,
|
|
disable: okDisabled.value,
|
|
...(isObject(props.ok) === true ? props.ok : { flat: true }),
|
|
'data-autofocus': (props.focus === 'ok' && hasForm.value !== true) || void 0,
|
|
onClick: onOk
|
|
}));
|
|
|
|
const cancelProps = vue.computed(() => ({
|
|
color: vmColor.value,
|
|
label: cancelLabel.value,
|
|
ripple: false,
|
|
...(isObject(props.cancel) === true ? props.cancel : { flat: true }),
|
|
'data-autofocus': (props.focus === 'cancel' && hasForm.value !== true) || void 0,
|
|
onClick: onCancel
|
|
}));
|
|
|
|
vue.watch(() => props.prompt && props.prompt.model, onUpdateModel);
|
|
vue.watch(() => props.options && props.options.model, onUpdateModel);
|
|
|
|
function show () {
|
|
dialogRef.value.show();
|
|
}
|
|
|
|
function hide () {
|
|
dialogRef.value.hide();
|
|
}
|
|
|
|
function onOk () {
|
|
emit('ok', vue.toRaw(model.value));
|
|
hide();
|
|
}
|
|
|
|
function onCancel () {
|
|
hide();
|
|
}
|
|
|
|
function onDialogHide () {
|
|
emit('hide');
|
|
}
|
|
|
|
function onUpdateModel (val) {
|
|
model.value = val;
|
|
}
|
|
|
|
function onInputKeyup (evt) {
|
|
// if ENTER key
|
|
if (
|
|
okDisabled.value !== true
|
|
&& props.prompt.type !== 'textarea'
|
|
&& isKeyCode(evt, 13) === true
|
|
) {
|
|
onOk();
|
|
}
|
|
}
|
|
|
|
function getSection (classes, text) {
|
|
return props.html === true
|
|
? vue.h(QCardSection, {
|
|
class: classes,
|
|
innerHTML: text
|
|
})
|
|
: vue.h(QCardSection, { class: classes }, () => text)
|
|
}
|
|
|
|
function getPrompt () {
|
|
return [
|
|
vue.h(QInput, {
|
|
color: vmColor.value,
|
|
dense: true,
|
|
autofocus: true,
|
|
dark: isDark.value,
|
|
...formProps.value,
|
|
modelValue: model.value,
|
|
'onUpdate:modelValue': onUpdateModel,
|
|
onKeyup: onInputKeyup
|
|
})
|
|
]
|
|
}
|
|
|
|
function getOptions () {
|
|
return [
|
|
vue.h(QOptionGroup, {
|
|
color: vmColor.value,
|
|
options: props.options.items,
|
|
dark: isDark.value,
|
|
...formProps.value,
|
|
modelValue: model.value,
|
|
'onUpdate:modelValue': onUpdateModel
|
|
})
|
|
]
|
|
}
|
|
|
|
function getButtons () {
|
|
const child = [];
|
|
|
|
props.cancel && child.push(
|
|
vue.h(QBtn, cancelProps.value)
|
|
);
|
|
|
|
props.ok && child.push(
|
|
vue.h(QBtn, okProps.value)
|
|
);
|
|
|
|
return vue.h(QCardActions, {
|
|
class: props.stackButtons === true ? 'items-end' : '',
|
|
vertical: props.stackButtons,
|
|
align: 'right'
|
|
}, () => child)
|
|
}
|
|
|
|
function getCardContent () {
|
|
const child = [];
|
|
|
|
props.title && child.push(
|
|
getSection('q-dialog__title', props.title)
|
|
);
|
|
|
|
props.progress !== false && child.push(
|
|
vue.h(
|
|
QCardSection,
|
|
{ class: 'q-dialog__progress' },
|
|
() => vue.h(spinner.value.component, spinner.value.props)
|
|
)
|
|
);
|
|
|
|
props.message && child.push(
|
|
getSection('q-dialog__message', props.message)
|
|
);
|
|
|
|
if (props.prompt !== void 0) {
|
|
child.push(
|
|
vue.h(
|
|
QCardSection,
|
|
{ class: 'scroll q-dialog-plugin__form' },
|
|
getPrompt
|
|
)
|
|
);
|
|
}
|
|
else if (props.options !== void 0) {
|
|
child.push(
|
|
vue.h(QSeparator, { dark: isDark.value }),
|
|
vue.h(
|
|
QCardSection,
|
|
{ class: 'scroll q-dialog-plugin__form' },
|
|
getOptions
|
|
),
|
|
vue.h(QSeparator, { dark: isDark.value })
|
|
);
|
|
}
|
|
|
|
if (props.ok || props.cancel) {
|
|
child.push(getButtons());
|
|
}
|
|
|
|
return child
|
|
}
|
|
|
|
function getContent () {
|
|
return [
|
|
vue.h(QCard, {
|
|
class: [
|
|
classes.value,
|
|
props.cardClass
|
|
],
|
|
style: props.cardStyle,
|
|
dark: isDark.value
|
|
}, getCardContent)
|
|
]
|
|
}
|
|
|
|
// expose public methods
|
|
Object.assign(proxy, { show, hide });
|
|
|
|
return () => vue.h(QDialog, {
|
|
ref: dialogRef,
|
|
onHide: onDialogHide
|
|
}, getContent)
|
|
}
|
|
});
|
|
|
|
var Dialog = {
|
|
install ({ $q, parentApp }) {
|
|
$q.dialog = globalDialog(DialogPlugin, true, parentApp);
|
|
if (this.__installed !== true) {
|
|
this.create = $q.dialog;
|
|
}
|
|
}
|
|
};
|
|
|
|
const barRef = vue.ref(null);
|
|
|
|
const Plugin$3 = defineReactivePlugin({
|
|
isActive: false
|
|
}, {
|
|
start: noop,
|
|
stop: noop,
|
|
increment: noop,
|
|
setDefaults: noop,
|
|
|
|
install ({ $q, parentApp }) {
|
|
$q.loadingBar = this;
|
|
|
|
if (this.__installed === true) {
|
|
if ($q.config.loadingBar !== void 0) {
|
|
this.setDefaults($q.config.loadingBar);
|
|
}
|
|
return
|
|
}
|
|
|
|
const props = vue.ref(
|
|
$q.config.loadingBar !== void 0
|
|
? { ...$q.config.loadingBar }
|
|
: {}
|
|
);
|
|
|
|
function onStart () {
|
|
Plugin$3.isActive = true;
|
|
}
|
|
|
|
function onStop () {
|
|
Plugin$3.isActive = false;
|
|
}
|
|
|
|
const el = createGlobalNode('q-loading-bar');
|
|
|
|
createChildApp({
|
|
name: 'LoadingBar',
|
|
|
|
// hide App from Vue devtools
|
|
devtools: { hide: true },
|
|
|
|
setup: () => () => vue.h(QAjaxBar, { ...props.value, onStart, onStop, ref: barRef })
|
|
}, parentApp).mount(el);
|
|
|
|
Object.assign(this, {
|
|
start (speed) {
|
|
barRef.value.start(speed);
|
|
},
|
|
stop () {
|
|
barRef.value.stop();
|
|
},
|
|
increment () {
|
|
barRef.value.increment.apply(null, arguments);
|
|
},
|
|
setDefaults (opts) {
|
|
if (isObject(opts) === true) {
|
|
Object.assign(props.value, opts);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
let
|
|
app,
|
|
vm,
|
|
uid$1 = 0,
|
|
timeout = null,
|
|
props = {},
|
|
activeGroups = {};
|
|
|
|
const originalDefaults = {
|
|
group: '__default_quasar_group__',
|
|
delay: 0,
|
|
message: false,
|
|
html: false,
|
|
spinnerSize: 80,
|
|
spinnerColor: '',
|
|
messageColor: '',
|
|
backgroundColor: '',
|
|
boxClass: '',
|
|
spinner: QSpinner,
|
|
customClass: ''
|
|
};
|
|
|
|
const defaults$1 = { ...originalDefaults };
|
|
|
|
function registerProps (opts) {
|
|
if (opts && opts.group !== void 0 && activeGroups[ opts.group ] !== void 0) {
|
|
return Object.assign(activeGroups[ opts.group ], opts)
|
|
}
|
|
|
|
const newProps = isObject(opts) === true && opts.ignoreDefaults === true
|
|
? { ...originalDefaults, ...opts }
|
|
: { ...defaults$1, ...opts };
|
|
|
|
activeGroups[ newProps.group ] = newProps;
|
|
return newProps
|
|
}
|
|
|
|
const Plugin$2 = defineReactivePlugin({
|
|
isActive: false
|
|
}, {
|
|
show (opts) {
|
|
|
|
props = registerProps(opts);
|
|
const { group } = props;
|
|
|
|
Plugin$2.isActive = true;
|
|
|
|
if (app !== void 0) {
|
|
props.uid = uid$1;
|
|
vm.$forceUpdate();
|
|
}
|
|
else {
|
|
props.uid = ++uid$1;
|
|
timeout !== null && clearTimeout(timeout);
|
|
|
|
timeout = setTimeout(() => {
|
|
timeout = null;
|
|
|
|
const el = createGlobalNode('q-loading');
|
|
|
|
app = createChildApp({
|
|
name: 'QLoading',
|
|
|
|
setup () {
|
|
vue.onMounted(() => {
|
|
preventScroll(true);
|
|
});
|
|
|
|
function onAfterLeave () {
|
|
// might be called to finalize
|
|
// previous leave, even if it was cancelled
|
|
if (Plugin$2.isActive !== true && app !== void 0) {
|
|
preventScroll(false);
|
|
app.unmount(el);
|
|
removeGlobalNode(el);
|
|
app = void 0;
|
|
vm = void 0;
|
|
}
|
|
}
|
|
|
|
function getContent () {
|
|
if (Plugin$2.isActive !== true) {
|
|
return null
|
|
}
|
|
|
|
const content = [
|
|
vue.h(props.spinner, {
|
|
class: 'q-loading__spinner',
|
|
color: props.spinnerColor,
|
|
size: props.spinnerSize
|
|
})
|
|
];
|
|
|
|
props.message && content.push(
|
|
vue.h('div', {
|
|
class: 'q-loading__message'
|
|
+ (props.messageColor ? ` text-${ props.messageColor }` : ''),
|
|
[ props.html === true ? 'innerHTML' : 'textContent' ]: props.message
|
|
})
|
|
);
|
|
|
|
return vue.h('div', {
|
|
class: 'q-loading fullscreen flex flex-center z-max ' + props.customClass.trim(),
|
|
key: props.uid
|
|
}, [
|
|
vue.h('div', {
|
|
class: 'q-loading__backdrop'
|
|
+ (props.backgroundColor ? ` bg-${ props.backgroundColor }` : '')
|
|
}),
|
|
|
|
vue.h('div', {
|
|
class: 'q-loading__box column items-center ' + props.boxClass
|
|
}, content)
|
|
])
|
|
}
|
|
|
|
return () => vue.h(vue.Transition, {
|
|
name: 'q-transition--fade',
|
|
appear: true,
|
|
onAfterLeave
|
|
}, getContent)
|
|
}
|
|
}, Plugin$2.__parentApp);
|
|
|
|
vm = app.mount(el);
|
|
}, props.delay);
|
|
}
|
|
|
|
return paramProps => {
|
|
// if we don't have params (or not an Object param) then we need to hide this group
|
|
if (paramProps === void 0 || Object(paramProps) !== paramProps) {
|
|
Plugin$2.hide(group);
|
|
return
|
|
}
|
|
|
|
// else we have params so we need to update this group
|
|
Plugin$2.show({ ...paramProps, group });
|
|
}
|
|
},
|
|
|
|
hide (group) {
|
|
if (Plugin$2.isActive === true) {
|
|
if (group === void 0) {
|
|
// clear out any active groups
|
|
activeGroups = {};
|
|
}
|
|
else if (activeGroups[ group ] === void 0) {
|
|
// we've already hidden it so nothing to do
|
|
return
|
|
}
|
|
else {
|
|
// remove active group
|
|
delete activeGroups[ group ];
|
|
|
|
const keys = Object.keys(activeGroups);
|
|
|
|
// if there are other groups registered then
|
|
// show last registered one since that one is still active
|
|
if (keys.length !== 0) {
|
|
// get last registered group
|
|
const lastGroup = keys[ keys.length - 1 ];
|
|
Plugin$2.show({ group: lastGroup });
|
|
return
|
|
}
|
|
}
|
|
|
|
if (timeout !== null) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
}
|
|
|
|
Plugin$2.isActive = false;
|
|
}
|
|
},
|
|
|
|
setDefaults (opts) {
|
|
{
|
|
isObject(opts) === true && Object.assign(defaults$1, opts);
|
|
}
|
|
},
|
|
|
|
install ({ $q, parentApp }) {
|
|
$q.loading = this;
|
|
|
|
{
|
|
Plugin$2.__parentApp = parentApp;
|
|
|
|
if ($q.config.loading !== void 0) {
|
|
this.setDefaults($q.config.loading);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let updateId = null, currentClientMeta;
|
|
const clientList = [];
|
|
|
|
function normalize (meta) {
|
|
if (meta.title) {
|
|
meta.title = meta.titleTemplate
|
|
? meta.titleTemplate(meta.title)
|
|
: meta.title;
|
|
delete meta.titleTemplate;
|
|
}
|
|
[ [ 'meta', 'content' ], [ 'link', 'href' ] ].forEach(type => {
|
|
const
|
|
metaType = meta[ type[ 0 ] ],
|
|
metaProp = type[ 1 ];
|
|
|
|
for (const name in metaType) {
|
|
const metaLink = metaType[ name ];
|
|
|
|
if (metaLink.template) {
|
|
if (Object.keys(metaLink).length === 1) {
|
|
delete metaType[ name ];
|
|
}
|
|
else {
|
|
metaLink[ metaProp ] = metaLink.template(metaLink[ metaProp ] || '');
|
|
delete metaLink.template;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function changed (old, def) {
|
|
if (Object.keys(old).length !== Object.keys(def).length) {
|
|
return true
|
|
}
|
|
for (const key in old) {
|
|
if (old[ key ] !== def[ key ]) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
function bodyFilter (name) {
|
|
return [ 'class', 'style' ].includes(name) === false
|
|
}
|
|
|
|
function htmlFilter (name) {
|
|
return [ 'lang', 'dir' ].includes(name) === false
|
|
}
|
|
|
|
function diff (meta, other) {
|
|
const add = {}, remove = {};
|
|
|
|
if (meta === void 0) {
|
|
return { add: other, remove }
|
|
}
|
|
|
|
if (meta.title !== other.title) {
|
|
add.title = other.title;
|
|
}
|
|
[ 'meta', 'link', 'script', 'htmlAttr', 'bodyAttr' ].forEach(type => {
|
|
const old = meta[ type ], cur = other[ type ];
|
|
remove[ type ] = [];
|
|
|
|
if (old === void 0 || old === null) {
|
|
add[ type ] = cur;
|
|
return
|
|
}
|
|
|
|
add[ type ] = {};
|
|
|
|
for (const key in old) {
|
|
if (cur.hasOwnProperty(key) === false) {
|
|
remove[ type ].push(key);
|
|
}
|
|
}
|
|
for (const key in cur) {
|
|
if (old.hasOwnProperty(key) === false) {
|
|
add[ type ][ key ] = cur[ key ];
|
|
}
|
|
else if (changed(old[ key ], cur[ key ]) === true) {
|
|
remove[ type ].push(key);
|
|
add[ type ][ key ] = cur[ key ];
|
|
}
|
|
}
|
|
});
|
|
|
|
return { add, remove }
|
|
}
|
|
|
|
function apply ({ add, remove }) {
|
|
if (add.title) {
|
|
document.title = add.title;
|
|
}
|
|
|
|
if (Object.keys(remove).length !== 0) {
|
|
[ 'meta', 'link', 'script' ].forEach(type => {
|
|
remove[ type ].forEach(name => {
|
|
document.head.querySelector(`${ type }[data-qmeta="${ name }"]`).remove();
|
|
});
|
|
});
|
|
remove.htmlAttr.filter(htmlFilter).forEach(name => {
|
|
document.documentElement.removeAttribute(name);
|
|
});
|
|
remove.bodyAttr.filter(bodyFilter).forEach(name => {
|
|
document.body.removeAttribute(name);
|
|
});
|
|
}
|
|
[ 'meta', 'link', 'script' ].forEach(type => {
|
|
const metaType = add[ type ];
|
|
|
|
for (const name in metaType) {
|
|
const tag = document.createElement(type);
|
|
for (const att in metaType[ name ]) {
|
|
if (att !== 'innerHTML') {
|
|
tag.setAttribute(att, metaType[ name ][ att ]);
|
|
}
|
|
}
|
|
tag.setAttribute('data-qmeta', name);
|
|
if (type === 'script') {
|
|
tag.innerHTML = metaType[ name ].innerHTML || '';
|
|
}
|
|
document.head.appendChild(tag);
|
|
}
|
|
});
|
|
Object.keys(add.htmlAttr).filter(htmlFilter).forEach(name => {
|
|
document.documentElement.setAttribute(name, add.htmlAttr[ name ] || '');
|
|
});
|
|
Object.keys(add.bodyAttr).filter(bodyFilter).forEach(name => {
|
|
document.body.setAttribute(name, add.bodyAttr[ name ] || '');
|
|
});
|
|
}
|
|
|
|
function updateClientMeta () {
|
|
updateId = null;
|
|
|
|
const data = {
|
|
title: '',
|
|
titleTemplate: null,
|
|
meta: {},
|
|
link: {},
|
|
script: {},
|
|
htmlAttr: {},
|
|
bodyAttr: {}
|
|
};
|
|
|
|
for (let i = 0; i < clientList.length; i++) {
|
|
const { active, val } = clientList[ i ];
|
|
|
|
if (active === true) {
|
|
extend(true, data, val);
|
|
}
|
|
}
|
|
|
|
normalize(data);
|
|
|
|
apply(diff(currentClientMeta, data));
|
|
currentClientMeta = data;
|
|
}
|
|
|
|
function planClientUpdate () {
|
|
updateId !== null && clearTimeout(updateId);
|
|
updateId = setTimeout(updateClientMeta, 50);
|
|
}
|
|
|
|
var Meta = {
|
|
install (opts) {
|
|
if (this.__installed !== true && isRuntimeSsrPreHydration.value === true) {
|
|
currentClientMeta = window.__Q_META__;
|
|
document.getElementById('qmeta-init').remove();
|
|
}
|
|
}
|
|
};
|
|
|
|
let uid = 0;
|
|
|
|
const defaults = {};
|
|
const groups = {};
|
|
const notificationsList = {};
|
|
const positionClass = {};
|
|
const emptyRE = /^\s*$/;
|
|
const notifRefs = [];
|
|
|
|
const positionList = [
|
|
'top-left', 'top-right',
|
|
'bottom-left', 'bottom-right',
|
|
'top', 'bottom', 'left', 'right', 'center'
|
|
];
|
|
|
|
const badgePositions = [
|
|
'top-left', 'top-right',
|
|
'bottom-left', 'bottom-right'
|
|
];
|
|
|
|
const notifTypes = {
|
|
positive: {
|
|
icon: $q => $q.iconSet.type.positive,
|
|
color: 'positive'
|
|
},
|
|
|
|
negative: {
|
|
icon: $q => $q.iconSet.type.negative,
|
|
color: 'negative'
|
|
},
|
|
|
|
warning: {
|
|
icon: $q => $q.iconSet.type.warning,
|
|
color: 'warning',
|
|
textColor: 'dark'
|
|
},
|
|
|
|
info: {
|
|
icon: $q => $q.iconSet.type.info,
|
|
color: 'info'
|
|
},
|
|
|
|
ongoing: {
|
|
group: false,
|
|
timeout: 0,
|
|
spinner: true,
|
|
color: 'grey-8'
|
|
}
|
|
};
|
|
|
|
function addNotification (config, $q, originalApi) {
|
|
if (!config) {
|
|
return logError('parameter required')
|
|
}
|
|
|
|
let Api;
|
|
const notif = { textColor: 'white' };
|
|
|
|
if (config.ignoreDefaults !== true) {
|
|
Object.assign(notif, defaults);
|
|
}
|
|
|
|
if (isObject(config) === false) {
|
|
if (notif.type) {
|
|
Object.assign(notif, notifTypes[ notif.type ]);
|
|
}
|
|
|
|
config = { message: config };
|
|
}
|
|
|
|
Object.assign(notif, notifTypes[ config.type || notif.type ], config);
|
|
|
|
if (typeof notif.icon === 'function') {
|
|
notif.icon = notif.icon($q);
|
|
}
|
|
|
|
if (!notif.spinner) {
|
|
notif.spinner = false;
|
|
}
|
|
else {
|
|
if (notif.spinner === true) {
|
|
notif.spinner = QSpinner;
|
|
}
|
|
|
|
notif.spinner = vue.markRaw(notif.spinner);
|
|
}
|
|
|
|
notif.meta = {
|
|
hasMedia: Boolean(notif.spinner !== false || notif.icon || notif.avatar),
|
|
hasText: hasContent(notif.message) || hasContent(notif.caption)
|
|
};
|
|
|
|
if (notif.position) {
|
|
if (positionList.includes(notif.position) === false) {
|
|
return logError('wrong position', config)
|
|
}
|
|
}
|
|
else {
|
|
notif.position = 'bottom';
|
|
}
|
|
|
|
if (notif.timeout === void 0) {
|
|
notif.timeout = 5000;
|
|
}
|
|
else {
|
|
const t = parseInt(notif.timeout, 10);
|
|
if (isNaN(t) || t < 0) {
|
|
return logError('wrong timeout', config)
|
|
}
|
|
notif.timeout = t;
|
|
}
|
|
|
|
if (notif.timeout === 0) {
|
|
notif.progress = false;
|
|
}
|
|
else if (notif.progress === true) {
|
|
notif.meta.progressClass = 'q-notification__progress' + (
|
|
notif.progressClass
|
|
? ` ${ notif.progressClass }`
|
|
: ''
|
|
);
|
|
|
|
notif.meta.progressStyle = {
|
|
animationDuration: `${ notif.timeout + 1000 }ms`
|
|
};
|
|
}
|
|
|
|
const actions = (
|
|
Array.isArray(config.actions) === true
|
|
? config.actions
|
|
: []
|
|
).concat(
|
|
config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true
|
|
? defaults.actions
|
|
: []
|
|
).concat(
|
|
notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true
|
|
? notifTypes[ config.type ].actions
|
|
: []
|
|
);
|
|
|
|
const { closeBtn } = notif;
|
|
closeBtn && actions.push({
|
|
label: typeof closeBtn === 'string'
|
|
? closeBtn
|
|
: $q.lang.label.close
|
|
});
|
|
|
|
notif.actions = actions.map(({ handler, noDismiss, ...item }) => ({
|
|
flat: true,
|
|
...item,
|
|
onClick: typeof handler === 'function'
|
|
? () => {
|
|
handler();
|
|
noDismiss !== true && dismiss();
|
|
}
|
|
: () => { dismiss(); }
|
|
}));
|
|
|
|
if (notif.multiLine === void 0) {
|
|
notif.multiLine = notif.actions.length > 1;
|
|
}
|
|
|
|
Object.assign(notif.meta, {
|
|
class: 'q-notification row items-stretch'
|
|
+ ` q-notification--${ notif.multiLine === true ? 'multi-line' : 'standard' }`
|
|
+ (notif.color !== void 0 ? ` bg-${ notif.color }` : '')
|
|
+ (notif.textColor !== void 0 ? ` text-${ notif.textColor }` : '')
|
|
+ (notif.classes !== void 0 ? ` ${ notif.classes }` : ''),
|
|
|
|
wrapperClass: 'q-notification__wrapper col relative-position border-radius-inherit '
|
|
+ (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center'),
|
|
|
|
contentClass: 'q-notification__content row items-center'
|
|
+ (notif.multiLine === true ? '' : ' col'),
|
|
|
|
leftClass: notif.meta.hasText === true ? 'additional' : 'single',
|
|
|
|
attrs: {
|
|
role: 'alert',
|
|
...notif.attrs
|
|
}
|
|
});
|
|
|
|
if (notif.group === false) {
|
|
notif.group = void 0;
|
|
notif.meta.group = void 0;
|
|
}
|
|
else {
|
|
if (notif.group === void 0 || notif.group === true) {
|
|
// do not replace notifications with different buttons
|
|
notif.group = [
|
|
notif.message,
|
|
notif.caption,
|
|
notif.multiline
|
|
].concat(
|
|
notif.actions.map(props => `${ props.label }*${ props.icon }`)
|
|
).join('|');
|
|
}
|
|
|
|
notif.meta.group = notif.group + '|' + notif.position;
|
|
}
|
|
|
|
if (notif.actions.length === 0) {
|
|
notif.actions = void 0;
|
|
}
|
|
else {
|
|
notif.meta.actionsClass = 'q-notification__actions row items-center '
|
|
+ (notif.multiLine === true ? 'justify-end' : 'col-auto')
|
|
+ (notif.meta.hasMedia === true ? ' q-notification__actions--with-media' : '');
|
|
}
|
|
|
|
if (originalApi !== void 0) {
|
|
// reset timeout if any
|
|
if (originalApi.notif.meta.timer) {
|
|
clearTimeout(originalApi.notif.meta.timer);
|
|
originalApi.notif.meta.timer = void 0;
|
|
}
|
|
|
|
// retain uid
|
|
notif.meta.uid = originalApi.notif.meta.uid;
|
|
|
|
// replace notif
|
|
const index = notificationsList[ notif.position ].value.indexOf(originalApi.notif);
|
|
notificationsList[ notif.position ].value[ index ] = notif;
|
|
}
|
|
else {
|
|
const original = groups[ notif.meta.group ];
|
|
|
|
// woohoo, it's a new notification
|
|
if (original === void 0) {
|
|
notif.meta.uid = uid++;
|
|
notif.meta.badge = 1;
|
|
|
|
if ([ 'left', 'right', 'center' ].indexOf(notif.position) !== -1) {
|
|
notificationsList[ notif.position ].value.splice(
|
|
Math.floor(notificationsList[ notif.position ].value.length / 2),
|
|
0,
|
|
notif
|
|
);
|
|
}
|
|
else {
|
|
const action = notif.position.indexOf('top') > -1 ? 'unshift' : 'push';
|
|
notificationsList[ notif.position ].value[ action ](notif);
|
|
}
|
|
|
|
if (notif.group !== void 0) {
|
|
groups[ notif.meta.group ] = notif;
|
|
}
|
|
}
|
|
// ok, so it's NOT a new one
|
|
else {
|
|
// reset timeout if any
|
|
if (original.meta.timer) {
|
|
clearTimeout(original.meta.timer);
|
|
original.meta.timer = void 0;
|
|
}
|
|
|
|
if (notif.badgePosition !== void 0) {
|
|
if (badgePositions.includes(notif.badgePosition) === false) {
|
|
return logError('wrong badgePosition', config)
|
|
}
|
|
}
|
|
else {
|
|
notif.badgePosition = `top-${ notif.position.indexOf('left') > -1 ? 'right' : 'left' }`;
|
|
}
|
|
|
|
notif.meta.uid = original.meta.uid;
|
|
notif.meta.badge = original.meta.badge + 1;
|
|
notif.meta.badgeClass = `q-notification__badge q-notification__badge--${ notif.badgePosition }`
|
|
+ (notif.badgeColor !== void 0 ? ` bg-${ notif.badgeColor }` : '')
|
|
+ (notif.badgeTextColor !== void 0 ? ` text-${ notif.badgeTextColor }` : '')
|
|
+ (notif.badgeClass ? ` ${ notif.badgeClass }` : '');
|
|
|
|
const index = notificationsList[ notif.position ].value.indexOf(original);
|
|
notificationsList[ notif.position ].value[ index ] = groups[ notif.meta.group ] = notif;
|
|
}
|
|
}
|
|
|
|
const dismiss = () => {
|
|
removeNotification(notif);
|
|
Api = void 0;
|
|
};
|
|
|
|
if (notif.timeout > 0) {
|
|
notif.meta.timer = setTimeout(() => {
|
|
notif.meta.timer = void 0;
|
|
dismiss();
|
|
}, notif.timeout + /* show duration */ 1000);
|
|
}
|
|
|
|
// only non-groupable can be updated
|
|
if (notif.group !== void 0) {
|
|
return props => {
|
|
if (props !== void 0) {
|
|
logError('trying to update a grouped one which is forbidden', config);
|
|
}
|
|
else {
|
|
dismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
Api = {
|
|
dismiss,
|
|
config,
|
|
notif
|
|
};
|
|
|
|
if (originalApi !== void 0) {
|
|
Object.assign(originalApi, Api);
|
|
return
|
|
}
|
|
|
|
return props => {
|
|
// if notification wasn't previously dismissed
|
|
if (Api !== void 0) {
|
|
// if no params, then we must dismiss the notification
|
|
if (props === void 0) {
|
|
Api.dismiss();
|
|
}
|
|
// otherwise we're updating it
|
|
else {
|
|
const newNotif = Object.assign({}, Api.config, props, {
|
|
group: false,
|
|
position: notif.position
|
|
});
|
|
|
|
addNotification(newNotif, $q, Api);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeNotification (notif) {
|
|
if (notif.meta.timer) {
|
|
clearTimeout(notif.meta.timer);
|
|
notif.meta.timer = void 0;
|
|
}
|
|
|
|
const index = notificationsList[ notif.position ].value.indexOf(notif);
|
|
if (index !== -1) {
|
|
if (notif.group !== void 0) {
|
|
delete groups[ notif.meta.group ];
|
|
}
|
|
|
|
const el = notifRefs[ '' + notif.meta.uid ];
|
|
|
|
if (el) {
|
|
const { width, height } = getComputedStyle(el);
|
|
|
|
el.style.left = `${ el.offsetLeft }px`;
|
|
el.style.width = width;
|
|
el.style.height = height;
|
|
}
|
|
|
|
notificationsList[ notif.position ].value.splice(index, 1);
|
|
|
|
if (typeof notif.onDismiss === 'function') {
|
|
notif.onDismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasContent (str) {
|
|
return str !== void 0
|
|
&& str !== null
|
|
&& emptyRE.test(str) !== true
|
|
}
|
|
|
|
function logError (error, config) {
|
|
console.error(`Notify: ${ error }`, config);
|
|
return false
|
|
}
|
|
|
|
function getComponent () {
|
|
return createComponent({
|
|
name: 'QNotifications',
|
|
|
|
// hide App from Vue devtools
|
|
devtools: { hide: true },
|
|
|
|
setup () {
|
|
return () => vue.h('div', { class: 'q-notifications' }, positionList.map(pos => {
|
|
return vue.h(vue.TransitionGroup, {
|
|
key: pos,
|
|
class: positionClass[ pos ],
|
|
tag: 'div',
|
|
name: `q-notification--${ pos }`
|
|
}, () => notificationsList[ pos ].value.map(notif => {
|
|
const meta = notif.meta;
|
|
const mainChild = [];
|
|
|
|
if (meta.hasMedia === true) {
|
|
if (notif.spinner !== false) {
|
|
mainChild.push(
|
|
vue.h(notif.spinner, {
|
|
class: 'q-notification__spinner q-notification__spinner--' + meta.leftClass,
|
|
color: notif.spinnerColor,
|
|
size: notif.spinnerSize
|
|
})
|
|
);
|
|
}
|
|
else if (notif.icon) {
|
|
mainChild.push(
|
|
vue.h(QIcon, {
|
|
class: 'q-notification__icon q-notification__icon--' + meta.leftClass,
|
|
name: notif.icon,
|
|
color: notif.iconColor,
|
|
size: notif.iconSize,
|
|
role: 'img'
|
|
})
|
|
);
|
|
}
|
|
else if (notif.avatar) {
|
|
mainChild.push(
|
|
vue.h(QAvatar, {
|
|
class: 'q-notification__avatar q-notification__avatar--' + meta.leftClass
|
|
}, () => vue.h('img', { src: notif.avatar, 'aria-hidden': 'true' }))
|
|
);
|
|
}
|
|
}
|
|
|
|
if (meta.hasText === true) {
|
|
let msgChild;
|
|
const msgData = { class: 'q-notification__message col' };
|
|
|
|
if (notif.html === true) {
|
|
msgData.innerHTML = notif.caption
|
|
? `<div>${ notif.message }</div><div class="q-notification__caption">${ notif.caption }</div>`
|
|
: notif.message;
|
|
}
|
|
else {
|
|
const msgNode = [ notif.message ];
|
|
msgChild = notif.caption
|
|
? [
|
|
vue.h('div', msgNode),
|
|
vue.h('div', { class: 'q-notification__caption' }, [ notif.caption ])
|
|
]
|
|
: msgNode;
|
|
}
|
|
|
|
mainChild.push(
|
|
vue.h('div', msgData, msgChild)
|
|
);
|
|
}
|
|
|
|
const child = [
|
|
vue.h('div', { class: meta.contentClass }, mainChild)
|
|
];
|
|
|
|
notif.progress === true && child.push(
|
|
vue.h('div', {
|
|
key: `${ meta.uid }|p|${ meta.badge }`,
|
|
class: meta.progressClass,
|
|
style: meta.progressStyle
|
|
})
|
|
);
|
|
|
|
notif.actions !== void 0 && child.push(
|
|
vue.h('div', {
|
|
class: meta.actionsClass
|
|
}, notif.actions.map(props => vue.h(QBtn, props)))
|
|
);
|
|
|
|
meta.badge > 1 && child.push(
|
|
vue.h('div', {
|
|
key: `${ meta.uid }|${ meta.badge }`,
|
|
class: notif.meta.badgeClass,
|
|
style: notif.badgeStyle
|
|
}, [ meta.badge ])
|
|
);
|
|
|
|
return vue.h('div', {
|
|
ref: el => { notifRefs[ '' + meta.uid ] = el; },
|
|
key: meta.uid,
|
|
class: meta.class,
|
|
...meta.attrs
|
|
}, [
|
|
vue.h('div', { class: meta.wrapperClass }, child)
|
|
])
|
|
}))
|
|
}))
|
|
}
|
|
})
|
|
}
|
|
|
|
var Notify = {
|
|
setDefaults (opts) {
|
|
{
|
|
isObject(opts) === true && Object.assign(defaults, opts);
|
|
}
|
|
},
|
|
|
|
registerType (typeName, typeOpts) {
|
|
if (isObject(typeOpts) === true) {
|
|
notifTypes[ typeName ] = typeOpts;
|
|
}
|
|
},
|
|
|
|
install ({ $q, parentApp }) {
|
|
$q.notify = this.create = opts => addNotification(opts, $q);
|
|
|
|
$q.notify.setDefaults = this.setDefaults;
|
|
$q.notify.registerType = this.registerType;
|
|
|
|
if ($q.config.notify !== void 0) {
|
|
this.setDefaults($q.config.notify);
|
|
}
|
|
|
|
if (this.__installed !== true) {
|
|
positionList.forEach(pos => {
|
|
notificationsList[ pos ] = vue.ref([]);
|
|
|
|
const
|
|
vert = [ 'left', 'center', 'right' ].includes(pos) === true ? 'center' : (pos.indexOf('top') > -1 ? 'top' : 'bottom'),
|
|
align = pos.indexOf('left') > -1 ? 'start' : (pos.indexOf('right') > -1 ? 'end' : 'center'),
|
|
classes = [ 'left', 'right' ].includes(pos) ? `items-${ pos === 'left' ? 'start' : 'end' } justify-center` : (pos === 'center' ? 'flex-center' : `items-${ align }`);
|
|
|
|
positionClass[ pos ] = `q-notifications__list q-notifications__list--${ vert } fixed column no-wrap ${ classes }`;
|
|
});
|
|
|
|
const el = createGlobalNode('q-notify');
|
|
createChildApp(getComponent(), parentApp).mount(el);
|
|
}
|
|
}
|
|
};
|
|
|
|
function encode (value) {
|
|
if (isDate(value) === true) {
|
|
return '__q_date|' + value.toUTCString()
|
|
}
|
|
if (isRegexp(value) === true) {
|
|
return '__q_expr|' + value.source
|
|
}
|
|
if (typeof value === 'number') {
|
|
return '__q_numb|' + value
|
|
}
|
|
if (typeof value === 'boolean') {
|
|
return '__q_bool|' + (value ? '1' : '0')
|
|
}
|
|
if (typeof value === 'string') {
|
|
return '__q_strn|' + value
|
|
}
|
|
if (typeof value === 'function') {
|
|
return '__q_strn|' + value.toString()
|
|
}
|
|
if (value === Object(value)) {
|
|
return '__q_objt|' + JSON.stringify(value)
|
|
}
|
|
|
|
// hmm, we don't know what to do with it,
|
|
// so just return it as is
|
|
return value
|
|
}
|
|
|
|
function decode (value) {
|
|
const length = value.length;
|
|
if (length < 9) {
|
|
// then it wasn't encoded by us
|
|
return value
|
|
}
|
|
|
|
const type = value.substring(0, 8);
|
|
const source = value.substring(9);
|
|
|
|
switch (type) {
|
|
case '__q_date':
|
|
return new Date(source)
|
|
|
|
case '__q_expr':
|
|
return new RegExp(source)
|
|
|
|
case '__q_numb':
|
|
return Number(source)
|
|
|
|
case '__q_bool':
|
|
return Boolean(source === '1')
|
|
|
|
case '__q_strn':
|
|
return '' + source
|
|
|
|
case '__q_objt':
|
|
return JSON.parse(source)
|
|
|
|
default:
|
|
// hmm, we reached here, we don't know the type,
|
|
// then it means it wasn't encoded by us, so just
|
|
// return whatever value it is
|
|
return value
|
|
}
|
|
}
|
|
|
|
function getEmptyStorage () {
|
|
const getVal = () => null;
|
|
|
|
return {
|
|
has: () => false,
|
|
getLength: () => 0,
|
|
getItem: getVal,
|
|
getIndex: getVal,
|
|
getKey: getVal,
|
|
getAll: () => {},
|
|
getAllKeys: () => [],
|
|
set: noop,
|
|
remove: noop,
|
|
clear: noop,
|
|
isEmpty: () => true
|
|
}
|
|
}
|
|
|
|
function getStorage (type) {
|
|
const
|
|
webStorage = window[ type + 'Storage' ],
|
|
get = key => {
|
|
const item = webStorage.getItem(key);
|
|
return item
|
|
? decode(item)
|
|
: null
|
|
};
|
|
|
|
return {
|
|
has: key => webStorage.getItem(key) !== null,
|
|
getLength: () => webStorage.length,
|
|
getItem: get,
|
|
getIndex: index => {
|
|
return index < webStorage.length
|
|
? get(webStorage.key(index))
|
|
: null
|
|
},
|
|
getKey: index => {
|
|
return index < webStorage.length
|
|
? webStorage.key(index)
|
|
: null
|
|
},
|
|
getAll: () => {
|
|
let key;
|
|
const result = {}, len = webStorage.length;
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
key = webStorage.key(i);
|
|
result[ key ] = get(key);
|
|
}
|
|
|
|
return result
|
|
},
|
|
getAllKeys: () => {
|
|
const result = [], len = webStorage.length;
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
result.push(webStorage.key(i));
|
|
}
|
|
|
|
return result
|
|
},
|
|
set: (key, value) => { webStorage.setItem(key, encode(value)); },
|
|
remove: key => { webStorage.removeItem(key); },
|
|
clear: () => { webStorage.clear(); },
|
|
isEmpty: () => webStorage.length === 0
|
|
}
|
|
}
|
|
|
|
const storage$1 = client.has.webStorage === false
|
|
? getEmptyStorage()
|
|
: getStorage('local');
|
|
|
|
const Plugin$1 = {
|
|
install ({ $q }) {
|
|
$q.localStorage = storage$1;
|
|
}
|
|
};
|
|
|
|
Object.assign(Plugin$1, storage$1);
|
|
|
|
const storage = client.has.webStorage === false
|
|
? getEmptyStorage()
|
|
: getStorage('session');
|
|
|
|
const Plugin = {
|
|
install ({ $q }) {
|
|
$q.sessionStorage = storage;
|
|
}
|
|
};
|
|
|
|
Object.assign(Plugin, storage);
|
|
|
|
var plugins = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
AddressbarColor: AddressbarColor,
|
|
AppFullscreen: Plugin$6,
|
|
AppVisibility: Plugin$5,
|
|
BottomSheet: BottomSheet,
|
|
Cookies: Plugin$4,
|
|
Dark: Plugin$9,
|
|
Dialog: Dialog,
|
|
LoadingBar: Plugin$3,
|
|
Loading: Plugin$2,
|
|
Meta: Meta,
|
|
Notify: Notify,
|
|
Platform: Platform,
|
|
Screen: Screen,
|
|
LocalStorage: Plugin$1,
|
|
SessionStorage: Plugin
|
|
});
|
|
|
|
function fallback (text) {
|
|
const area = document.createElement('textarea');
|
|
area.value = text;
|
|
area.contentEditable = 'true';
|
|
area.style.position = 'fixed'; // avoid scrolling to bottom
|
|
|
|
const fn = () => {};
|
|
addFocusout(fn);
|
|
|
|
document.body.appendChild(area);
|
|
area.focus();
|
|
area.select();
|
|
|
|
const res = document.execCommand('copy');
|
|
|
|
area.remove();
|
|
removeFocusout(fn);
|
|
|
|
return res
|
|
}
|
|
|
|
function copyToClipboard (text) {
|
|
return navigator.clipboard !== void 0
|
|
? navigator.clipboard.writeText(text)
|
|
: new Promise((resolve, reject) => {
|
|
const res = fallback(text);
|
|
if (res) {
|
|
resolve(true);
|
|
}
|
|
else {
|
|
reject(res);
|
|
}
|
|
})
|
|
}
|
|
|
|
var createMetaMixin = metaOptions => {
|
|
|
|
const mixin = {
|
|
activated () {
|
|
this.__qMeta.active = true;
|
|
planClientUpdate();
|
|
},
|
|
|
|
deactivated () {
|
|
this.__qMeta.active = false;
|
|
planClientUpdate();
|
|
},
|
|
|
|
unmounted () {
|
|
clientList.splice(clientList.indexOf(this.__qMeta), 1);
|
|
planClientUpdate();
|
|
this.__qMeta = void 0;
|
|
}
|
|
};
|
|
|
|
if (typeof metaOptions === 'function') {
|
|
Object.assign(mixin, {
|
|
computed: {
|
|
__qMetaOptions () {
|
|
return metaOptions.call(this) || {}
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
__qMetaOptions (val) {
|
|
this.__qMeta.val = val;
|
|
this.__qMeta.active === true && planClientUpdate();
|
|
}
|
|
},
|
|
|
|
created () {
|
|
this.__qMeta = { active: true, val: this.__qMetaOptions };
|
|
clientList.push(this.__qMeta);
|
|
planClientUpdate();
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
mixin.created = function () {
|
|
this.__qMeta = { active: true, val: metaOptions };
|
|
clientList.push(this.__qMeta);
|
|
planClientUpdate();
|
|
};
|
|
}
|
|
|
|
return mixin
|
|
};
|
|
|
|
/**
|
|
* Forked from tiny-emitter
|
|
* Copyright (c) 2017 Scott Corgan
|
|
*/
|
|
|
|
class EventBus {
|
|
constructor () {
|
|
this.__stack = {};
|
|
}
|
|
|
|
on (name, callback, ctx) {
|
|
(this.__stack[ name ] || (this.__stack[ name ] = [])).push({
|
|
fn: callback,
|
|
ctx
|
|
});
|
|
|
|
return this // chainable
|
|
}
|
|
|
|
once (name, callback, ctx) {
|
|
const listener = (...args) => {
|
|
this.off(name, listener);
|
|
callback.apply(ctx, args);
|
|
};
|
|
|
|
listener.__callback = callback;
|
|
return this.on(name, listener, ctx) // chainable
|
|
}
|
|
|
|
emit (name) {
|
|
const list = this.__stack[ name ];
|
|
|
|
if (list !== void 0) {
|
|
const params = [].slice.call(arguments, 1);
|
|
list.forEach(entry => {
|
|
entry.fn.apply(entry.ctx, params);
|
|
});
|
|
}
|
|
|
|
return this // chainable
|
|
}
|
|
|
|
off (name, callback) {
|
|
const list = this.__stack[ name ];
|
|
|
|
if (list === void 0) {
|
|
return this // chainable
|
|
}
|
|
|
|
if (callback === void 0) {
|
|
delete this.__stack[ name ];
|
|
return this // chainable
|
|
}
|
|
|
|
const liveEvents = list.filter(
|
|
entry => entry.fn !== callback && entry.fn.__callback !== callback
|
|
);
|
|
|
|
if (liveEvents.length !== 0) {
|
|
this.__stack[ name ] = liveEvents;
|
|
}
|
|
else {
|
|
delete this.__stack[ name ];
|
|
}
|
|
|
|
return this // chainable
|
|
}
|
|
}
|
|
|
|
function clean (link) {
|
|
// allow time for iOS
|
|
setTimeout(() => {
|
|
window.URL.revokeObjectURL(link.href);
|
|
}, 10000);
|
|
|
|
link.remove();
|
|
}
|
|
|
|
/**
|
|
* Forces browser to download file with specified content
|
|
*
|
|
* @param {*} fileName - String
|
|
* @param {*} rawData - String | ArrayBuffer | ArrayBufferView | Blob
|
|
* @param {*} opts - String (mimeType) or Object
|
|
* Object form: { mimeType?: String, byteOrderMark?: String | Uint8Array, encoding?: String }
|
|
* @returns Boolean | Error
|
|
*
|
|
* mimeType - Examples: 'application/octect-stream' (default), 'text/plain', 'application/json',
|
|
* 'text/plain;charset=UTF-8', 'video/mp4', 'image/png', 'application/pdf'
|
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
|
*
|
|
* byteOrderMark - (BOM) Example: '\uFEFF'
|
|
* https://en.wikipedia.org/wiki/Byte_order_mark
|
|
*
|
|
* encoding - Performs a TextEncoder.encode() over the rawData;
|
|
* Example: 'windows-1252' (ANSI, a subset of ISO-8859-1)
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
|
|
*/
|
|
function exportFile (fileName, rawData, opts = {}) {
|
|
const { mimeType, byteOrderMark, encoding } = typeof opts === 'string'
|
|
? { mimeType: opts }
|
|
: opts;
|
|
|
|
const data = encoding !== void 0
|
|
? (new TextEncoder(encoding)).encode([ rawData ])
|
|
: rawData;
|
|
|
|
const blobData = byteOrderMark !== void 0 ? [ byteOrderMark, data ] : [ data ];
|
|
const blob = new Blob(blobData, { type: mimeType || 'application/octet-stream' });
|
|
const link = document.createElement('a');
|
|
|
|
link.href = window.URL.createObjectURL(blob);
|
|
link.setAttribute('download', fileName);
|
|
|
|
// Check for "download" attribute support;
|
|
// If not supported, open this in new window
|
|
if (typeof link.download === 'undefined') {
|
|
link.setAttribute('target', '_blank');
|
|
}
|
|
|
|
link.classList.add('hidden');
|
|
link.style.position = 'fixed'; // avoid scrolling to bottom
|
|
document.body.appendChild(link);
|
|
|
|
try {
|
|
link.click();
|
|
clean(link);
|
|
return true
|
|
}
|
|
catch (err) {
|
|
clean(link);
|
|
return err
|
|
}
|
|
}
|
|
|
|
function parseFeatures (winFeatures) {
|
|
const cfg = Object.assign({ noopener: true }, winFeatures);
|
|
const feat = [];
|
|
for (const key in cfg) {
|
|
const value = cfg[ key ];
|
|
if (value === true) {
|
|
feat.push(key);
|
|
}
|
|
else if (isNumber(value) || (typeof value === 'string' && value !== '')) {
|
|
feat.push(key + '=' + value);
|
|
}
|
|
}
|
|
return feat.join(',')
|
|
}
|
|
|
|
function openWindow (url, reject, windowFeatures) {
|
|
let open = window.open;
|
|
|
|
if (Platform.is.cordova === true) {
|
|
if (cordova !== void 0 && cordova.InAppBrowser !== void 0 && cordova.InAppBrowser.open !== void 0) {
|
|
open = cordova.InAppBrowser.open;
|
|
}
|
|
else if (navigator !== void 0 && navigator.app !== void 0) {
|
|
return navigator.app.loadUrl(url, {
|
|
openExternal: true
|
|
})
|
|
}
|
|
}
|
|
|
|
const win = open(url, '_blank', parseFeatures(windowFeatures));
|
|
|
|
if (win) {
|
|
Platform.is.desktop && win.focus();
|
|
return win
|
|
}
|
|
else {
|
|
reject && reject();
|
|
}
|
|
}
|
|
|
|
var openUrl = (url, reject, windowFeatures) => {
|
|
if (
|
|
Platform.is.ios === true
|
|
&& window.SafariViewController !== void 0
|
|
) {
|
|
window.SafariViewController.isAvailable(available => {
|
|
if (available) {
|
|
window.SafariViewController.show(
|
|
{ url },
|
|
noop,
|
|
reject
|
|
);
|
|
}
|
|
else {
|
|
openWindow(url, reject, windowFeatures);
|
|
}
|
|
});
|
|
return
|
|
}
|
|
|
|
return openWindow(url, reject, windowFeatures)
|
|
};
|
|
|
|
function parsePromises (sequentialPromises) {
|
|
const isList = Array.isArray(sequentialPromises);
|
|
|
|
if (isList === true) {
|
|
const totalJobs = sequentialPromises.length;
|
|
return {
|
|
isList,
|
|
totalJobs,
|
|
resultAggregator: Array(totalJobs).fill(null)
|
|
}
|
|
}
|
|
|
|
const resultKeys = Object.keys(sequentialPromises);
|
|
const resultAggregator = {};
|
|
resultKeys.forEach(keyName => { resultAggregator[ keyName ] = null; });
|
|
|
|
return {
|
|
isList,
|
|
totalJobs: resultKeys.length,
|
|
resultAggregator,
|
|
resultKeys
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run a list of Promises sequentially, optionally on multiple threads.
|
|
*
|
|
* @param {*} sequentialPromises - Array of Functions or Object with Functions as values
|
|
* Array of Function form: [ (resultAggregator: Array) => Promise<any>, ... ]
|
|
* Object form: { [key: string]: (resultAggregator: object) => Promise<any>, ... }
|
|
* @param {*} opts - Optional options Object
|
|
* Object form: { threadsNumber?: number, abortOnFail?: boolean }
|
|
* Default: { threadsNumber: 1, abortOnFail: true }
|
|
* When configuring threadsNumber AND using http requests, be
|
|
* aware of the maximum threads that the hosting browser
|
|
* supports (usually 5); any number of threads above that
|
|
* won't add any real benefits
|
|
* @returns Promise<Array<Object> | Object>
|
|
* With opts.abortOnFail set to true (which is default):
|
|
* When sequentialPromises param is Array:
|
|
* The Promise resolves with an Array of Objects of the following form:
|
|
* [ { key: number, status: 'fulfilled', value: any }, ... ]
|
|
* The Promise rejects with an Object of the following form:
|
|
* { key: number, status: 'rejected', reason: Error, resultAggregator: array }
|
|
* When sequentialPromises param is Object:
|
|
* The Promise resolves with an Object of the following form:
|
|
* { [key: string]: { key: string, status: 'fulfilled', value: any }, ... }
|
|
* The Promise rejects with an Object of the following form:
|
|
* { key: string, status: 'rejected', reason: Error, resultAggregator: object }
|
|
* With opts.abortOnFail set to false:
|
|
* The Promise is never rejected (no catch() needed)
|
|
* The Promise resolves with:
|
|
* An Array of Objects (when sequentialPromises param is also an Array) of the following form:
|
|
* [ { key: number, status: 'fulfilled', value: any } | { status: 'rejected', reason: Error }, ... ]
|
|
* An Object (when sequentialPromises param is also an Object) of the following form:
|
|
* { [key: string]: { key: string, status: 'fulfilled', value: any } | { key: string, status: 'rejected', reason: Error }, ... }
|
|
*/
|
|
function runSequentialPromises (
|
|
sequentialPromises,
|
|
{ threadsNumber = 1, abortOnFail = true } = {}
|
|
) {
|
|
let jobIndex = -1, hasAborted = false;
|
|
|
|
const { isList, totalJobs, resultAggregator, resultKeys } = parsePromises(sequentialPromises);
|
|
|
|
const getPromiseThread = () => new Promise((resolve, reject) => {
|
|
function runNextPromise () {
|
|
const currentJobIndex = ++jobIndex;
|
|
|
|
if (hasAborted === true || currentJobIndex >= totalJobs) {
|
|
resolve();
|
|
return
|
|
}
|
|
|
|
const key = isList === true ? currentJobIndex : resultKeys[ currentJobIndex ];
|
|
|
|
sequentialPromises[ key ](resultAggregator)
|
|
.then(value => {
|
|
if (hasAborted === true) {
|
|
resolve();
|
|
return // early exit
|
|
}
|
|
|
|
resultAggregator[ key ] = { key, status: 'fulfilled', value };
|
|
|
|
// timeout so it doesn't interfere with the .catch() below
|
|
setTimeout(runNextPromise);
|
|
})
|
|
.catch(reason => {
|
|
if (hasAborted === true) {
|
|
resolve();
|
|
return // early exit
|
|
}
|
|
|
|
const result = { key, status: 'rejected', reason };
|
|
resultAggregator[ key ] = result;
|
|
|
|
if (abortOnFail === true) {
|
|
hasAborted = true;
|
|
reject({ ...result, resultAggregator });
|
|
return // early exit
|
|
}
|
|
|
|
// timeout so no interference
|
|
setTimeout(runNextPromise);
|
|
});
|
|
}
|
|
|
|
runNextPromise();
|
|
});
|
|
|
|
const threads = Array(threadsNumber).fill(getPromiseThread());
|
|
return Promise.all(threads).then(() => resultAggregator)
|
|
}
|
|
|
|
var utils = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
clone: cloneDeep,
|
|
colors: colors,
|
|
copyToClipboard: copyToClipboard,
|
|
createMetaMixin: createMetaMixin,
|
|
createUploaderComponent: createUploaderComponent,
|
|
date: date,
|
|
debounce: debounce,
|
|
dom: dom,
|
|
EventBus: EventBus,
|
|
event: event,
|
|
exportFile: exportFile,
|
|
extend: extend,
|
|
format: format,
|
|
frameDebounce: frameDebounce,
|
|
getCssVar: getCssVar,
|
|
noop: noop,
|
|
is: is,
|
|
morph: morph,
|
|
openURL: openUrl,
|
|
patterns: patterns,
|
|
runSequentialPromises: runSequentialPromises,
|
|
scroll: scroll,
|
|
setCssVar: setCssVar,
|
|
throttle: throttle,
|
|
uid: uid$3
|
|
});
|
|
|
|
// To be used for the custom component
|
|
// used on a Dialog plugin
|
|
|
|
function useDialogPluginComponent () {
|
|
const { emit, proxy } = vue.getCurrentInstance();
|
|
|
|
// we need a Vue reference to the QDialog
|
|
// component so we can handle it;
|
|
// <q-dialog ref="dialogRef" ...
|
|
// make sure that the setup() in which this
|
|
// function is called returns dialogRef variable
|
|
const dialogRef = vue.ref(null);
|
|
|
|
function show () { dialogRef.value.show(); }
|
|
function hide () { dialogRef.value.hide(); }
|
|
|
|
function onDialogOK (payload) {
|
|
emit('ok', payload);
|
|
hide();
|
|
}
|
|
|
|
function onDialogHide () { emit('hide'); }
|
|
|
|
// expose public methods required by Dialog plugin
|
|
Object.assign(proxy, { show, hide });
|
|
|
|
return {
|
|
dialogRef,
|
|
onDialogHide,
|
|
onDialogOK,
|
|
onDialogCancel: hide
|
|
}
|
|
}
|
|
|
|
// Don't forget to update the types in "ui/types/composables.d.ts"
|
|
const emits = [ 'ok', 'hide' ];
|
|
|
|
useDialogPluginComponent.emits = emits;
|
|
useDialogPluginComponent.emitsObject = getEmitsObject(emits);
|
|
|
|
function useMeta (metaOptions) {
|
|
{
|
|
const meta = { active: true };
|
|
|
|
if (typeof metaOptions === 'function') {
|
|
const content = vue.computed(metaOptions);
|
|
meta.val = content.value;
|
|
|
|
vue.watch(content, val => {
|
|
meta.val = val;
|
|
meta.active === true && planClientUpdate();
|
|
});
|
|
}
|
|
else {
|
|
meta.val = metaOptions;
|
|
}
|
|
|
|
clientList.push(meta);
|
|
planClientUpdate();
|
|
|
|
vue.onActivated(() => {
|
|
meta.active = true;
|
|
planClientUpdate();
|
|
});
|
|
|
|
vue.onDeactivated(() => {
|
|
meta.active = false;
|
|
planClientUpdate();
|
|
});
|
|
|
|
vue.onUnmounted(() => {
|
|
clientList.splice(clientList.indexOf(meta), 1);
|
|
planClientUpdate();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the $q instance.
|
|
* Equivalent to `this.$q` inside templates.
|
|
*/
|
|
function useQuasar () {
|
|
return vue.inject(quasarKey)
|
|
}
|
|
|
|
var composables = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
useDialogPluginComponent: useDialogPluginComponent,
|
|
useFormChild: useFormChild,
|
|
useMeta: useMeta,
|
|
useQuasar: useQuasar
|
|
});
|
|
|
|
/**
|
|
* UMD entry-point
|
|
*/
|
|
|
|
var index_umd = {
|
|
version: '2.12.5',
|
|
install (app, opts) {
|
|
installQuasar(app, {
|
|
components,
|
|
directives,
|
|
plugins,
|
|
...opts
|
|
});
|
|
},
|
|
lang: Plugin$8,
|
|
iconSet: Plugin$7,
|
|
...components,
|
|
...directives,
|
|
...plugins,
|
|
...composables,
|
|
...utils
|
|
};
|
|
|
|
return index_umd;
|
|
|
|
}));
|