You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
40946 lines
1.1 MiB
40946 lines
1.1 MiB
/*! |
|
* Quasar Framework v2.12.2 |
|
* (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.2' }; |
|
|
|
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) { |
|
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; |
|
} |
|
|
|
if (val === void 0 || val === null) { |
|
return |
|
} |
|
|
|
updateInputValue('', props.multiple !== true, true); |
|
|
|
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 = () => { |
|
this.off(name, listener); |
|
callback.apply(ctx, arguments); |
|
}; |
|
|
|
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.2', |
|
install (app, opts) { |
|
installQuasar(app, { |
|
components, |
|
directives, |
|
plugins, |
|
...opts |
|
}); |
|
}, |
|
lang: Plugin$8, |
|
iconSet: Plugin$7, |
|
...components, |
|
...directives, |
|
...plugins, |
|
...composables, |
|
...utils |
|
}; |
|
|
|
return index_umd; |
|
|
|
}));
|