vuetify
Version:
Vue Material Component Framework
226 lines (225 loc) • 7.62 kB
JavaScript
import { createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
// Styles
import "./VSnackbar.css";
// Components
import { VDefaultsProvider } from "../VDefaultsProvider/index.js";
import { VOverlay } from "../VOverlay/index.js";
import { makeVOverlayProps } from "../VOverlay/VOverlay.js";
import { VProgressLinear } from "../VProgressLinear/index.js"; // Composables
import { useLayout } from "../../composables/index.js";
import { forwardRefs } from "../../composables/forwardRefs.js";
import { VuetifyLayoutKey } from "../../composables/layout.js";
import { makeLocationProps } from "../../composables/location.js";
import { makePositionProps, usePosition } from "../../composables/position.js";
import { useProxiedModel } from "../../composables/proxiedModel.js";
import { makeRoundedProps, useRounded } from "../../composables/rounded.js";
import { useScopeId } from "../../composables/scopeId.js";
import { makeThemeProps, provideTheme } from "../../composables/theme.js";
import { useToggleScope } from "../../composables/toggleScope.js";
import { genOverlays, makeVariantProps, useVariant } from "../../composables/variant.js"; // Utilities
import { computed, inject, mergeProps, nextTick, onMounted, onScopeDispose, ref, shallowRef, watch, watchEffect } from 'vue';
import { genericComponent, omit, propsFactory, refElement, useRender } from "../../util/index.js"; // Types
function useCountdown(milliseconds) {
const time = shallowRef(milliseconds());
let timer = -1;
function clear() {
clearInterval(timer);
}
function reset() {
clear();
nextTick(() => time.value = milliseconds());
}
function start(el) {
const style = el ? getComputedStyle(el) : {
transitionDuration: 0.2
};
const interval = parseFloat(style.transitionDuration) * 1000 || 200;
clear();
if (time.value <= 0) return;
const startTime = performance.now();
timer = window.setInterval(() => {
const elapsed = performance.now() - startTime + interval;
time.value = Math.max(milliseconds() - elapsed, 0);
if (time.value <= 0) clear();
}, interval);
}
onScopeDispose(clear);
return {
clear,
time,
start,
reset
};
}
export const makeVSnackbarProps = propsFactory({
multiLine: Boolean,
text: String,
timer: [Boolean, String],
timeout: {
type: [Number, String],
default: 5000
},
vertical: Boolean,
...makeLocationProps({
location: 'bottom'
}),
...makePositionProps(),
...makeRoundedProps(),
...makeVariantProps(),
...makeThemeProps(),
...omit(makeVOverlayProps({
transition: 'v-snackbar-transition'
}), ['persistent', 'noClickAnimation', 'scrim', 'scrollStrategy'])
}, 'VSnackbar');
export const VSnackbar = genericComponent()({
name: 'VSnackbar',
props: makeVSnackbarProps(),
emits: {
'update:modelValue': v => true
},
setup(props, _ref) {
let {
slots
} = _ref;
const isActive = useProxiedModel(props, 'modelValue');
const {
positionClasses
} = usePosition(props);
const {
scopeId
} = useScopeId();
const {
themeClasses
} = provideTheme(props);
const {
colorClasses,
colorStyles,
variantClasses
} = useVariant(props);
const {
roundedClasses
} = useRounded(props);
const countdown = useCountdown(() => Number(props.timeout));
const overlay = ref();
const timerRef = ref();
const isHovering = shallowRef(false);
const startY = shallowRef(0);
const mainStyles = ref();
const hasLayout = inject(VuetifyLayoutKey, undefined);
useToggleScope(() => !!hasLayout, () => {
const layout = useLayout();
watchEffect(() => {
mainStyles.value = layout.mainStyles.value;
});
});
watch(isActive, startTimeout);
watch(() => props.timeout, startTimeout);
onMounted(() => {
if (isActive.value) startTimeout();
});
let activeTimeout = -1;
function startTimeout() {
countdown.reset();
window.clearTimeout(activeTimeout);
const timeout = Number(props.timeout);
if (!isActive.value || timeout === -1) return;
const element = refElement(timerRef.value);
countdown.start(element);
activeTimeout = window.setTimeout(() => {
isActive.value = false;
}, timeout);
}
function clearTimeout() {
countdown.reset();
window.clearTimeout(activeTimeout);
}
function onPointerenter() {
isHovering.value = true;
clearTimeout();
}
function onPointerleave() {
isHovering.value = false;
startTimeout();
}
function onTouchstart(event) {
startY.value = event.touches[0].clientY;
}
function onTouchend(event) {
if (Math.abs(startY.value - event.changedTouches[0].clientY) > 50) {
isActive.value = false;
}
}
function onAfterLeave() {
if (isHovering.value) onPointerleave();
}
const locationClasses = computed(() => {
return props.location.split(' ').reduce((acc, loc) => {
acc[`v-snackbar--${loc}`] = true;
return acc;
}, {});
});
useRender(() => {
const overlayProps = VOverlay.filterProps(props);
const hasContent = !!(slots.default || slots.text || props.text);
return _createVNode(VOverlay, _mergeProps({
"ref": overlay,
"class": ['v-snackbar', {
'v-snackbar--active': isActive.value,
'v-snackbar--multi-line': props.multiLine && !props.vertical,
'v-snackbar--timer': !!props.timer,
'v-snackbar--vertical': props.vertical
}, locationClasses.value, positionClasses.value, props.class],
"style": [mainStyles.value, props.style]
}, overlayProps, {
"modelValue": isActive.value,
"onUpdate:modelValue": $event => isActive.value = $event,
"contentProps": mergeProps({
class: ['v-snackbar__wrapper', themeClasses.value, colorClasses.value, roundedClasses.value, variantClasses.value],
style: [colorStyles.value],
onPointerenter,
onPointerleave
}, overlayProps.contentProps),
"persistent": true,
"noClickAnimation": true,
"scrim": false,
"scrollStrategy": "none",
"_disableGlobalStack": true,
"onTouchstartPassive": onTouchstart,
"onTouchend": onTouchend,
"onAfterLeave": onAfterLeave
}, scopeId), {
default: () => [genOverlays(false, 'v-snackbar'), props.timer && !isHovering.value && _createVNode("div", {
"key": "timer",
"class": "v-snackbar__timer"
}, [_createVNode(VProgressLinear, {
"ref": timerRef,
"color": typeof props.timer === 'string' ? props.timer : 'info',
"max": props.timeout,
"model-value": countdown.time.value
}, null)]), hasContent && _createVNode("div", {
"key": "content",
"class": "v-snackbar__content",
"role": "status",
"aria-live": "polite"
}, [slots.text?.() ?? props.text, slots.default?.()]), slots.actions && _createVNode(VDefaultsProvider, {
"defaults": {
VBtn: {
variant: 'text',
ripple: false,
slim: true
}
}
}, {
default: () => [_createVNode("div", {
"class": "v-snackbar__actions"
}, [slots.actions({
isActive
})])]
})],
activator: slots.activator
});
});
return forwardRefs({}, overlay);
}
});
//# sourceMappingURL=VSnackbar.js.map