vuetify
Version:
Vue Material Component Framework
234 lines (233 loc) • 7.15 kB
JavaScript
import { Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps, createElementVNode as _createElementVNode } from "vue";
// Components
import { VBtn } from "../VBtn/index.js";
import { VDefaultsProvider } from "../VDefaultsProvider/index.js";
import { makeVSnackbarProps, VSnackbar } from "../VSnackbar/VSnackbar.js"; // Composables
import { useSnackbarQueue } from "./queue.js";
import { useDelay } from "../../composables/delay.js";
import { useDocumentVisibility } from "../../composables/documentVisibility.js";
import { useLocale } from "../../composables/locale.js"; // Utilities
import { computed, mergeProps, ref, shallowRef, toRef, triggerRef, watch } from 'vue';
import { genericComponent, omit, propsFactory, useRender } from "../../util/index.js"; // Types
export const makeVSnackbarQueueProps = propsFactory({
// TODO: Port this to Snackbar on dev
closable: [Boolean, String],
closeText: {
type: String,
default: '$vuetify.dismiss'
},
collapsed: Boolean,
displayStrategy: {
type: String,
default: 'hold'
},
modelValue: {
type: Array,
default: () => []
},
totalVisible: {
type: [Number, String],
default: 1
},
gap: {
type: [Number, String],
default: 8
},
...omit(makeVSnackbarProps(), ['modelValue', 'collapsed', 'queueIndex', 'queueGap'])
}, 'VSnackbarQueue');
export const VSnackbarQueue = genericComponent()({
name: 'VSnackbarQueue',
inheritAttrs: false,
props: makeVSnackbarQueueProps(),
emits: {
'update:modelValue': val => true
},
setup(props, {
attrs,
emit,
slots
}) {
const {
t
} = useLocale();
const documentVisibility = useDocumentVisibility();
const queue = useSnackbarQueue(props);
const isHovered = shallowRef(false);
const {
runOpenDelay,
runCloseDelay
} = useDelay({
openDelay: 0,
closeDelay: 500
}, val => {
isHovered.value = val;
updateDynamicProps();
});
let _lastId = 0;
const visibleItems = ref([]);
const limit = toRef(() => Number(props.totalVisible));
watch(() => props.modelValue.length, showNext);
function removeItem(id) {
visibleItems.value = visibleItems.value.filter(x => x.id !== id);
if (visibleItems.value.length === 0) {
isHovered.value = false;
}
showNext();
}
function showNext() {
if (!props.modelValue.length) return;
const activeCount = visibleItems.value.filter(x => x.active).length;
if (activeCount >= limit.value) {
if (props.displayStrategy !== 'overflow') return;
// Dismiss oldest active items to make room
visibleItems.value.filter(x => x.active).slice(limit.value - 1).forEach(item => {
item.active = false;
item.onDismiss?.('overflow');
});
}
const [next, ...rest] = props.modelValue;
emit('update:modelValue', rest);
const item = typeof next === 'string' ? {
text: next
} : next;
const {
promise,
success,
error,
onDismiss,
...itemProps
} = item;
const newItem = {
id: _lastId++,
item: {
...(promise ? {
timeout: -1,
loading: true
} : {}),
...itemProps
},
active: true,
onDismiss
};
visibleItems.value.unshift(newItem);
updateDynamicProps();
promise?.then(data => {
if (!newItem.active) return;
newItem.item = success?.(data) ?? {
...newItem.item,
timeout: 1
};
updateDynamicProps();
triggerRef(visibleItems);
}, data => {
if (!newItem.active) return;
newItem.item = error?.(data) ?? {
...newItem.item,
timeout: 1
};
updateDynamicProps();
triggerRef(visibleItems);
});
}
function dismiss(id, reason) {
const item = visibleItems.value.find(x => x.id === id);
if (!item) return;
item.active = false;
item.onDismiss?.(reason);
updateDynamicProps();
}
function clear() {
emit('update:modelValue', []);
visibleItems.value.toReversed().forEach((item, i) => setTimeout(() => {
item.active = false;
item.onDismiss?.('cleared');
}, 100 * i));
}
const btnProps = computed(() => ({
color: typeof props.closable === 'string' ? props.closable : undefined,
text: t(props.closeText)
}));
function updateDynamicProps() {
let activeIndex = 0;
visibleItems.value.forEach(({
item,
active
}) => {
item.queueIndex = activeIndex;
if (active) activeIndex++;
});
if (!props.collapsed || isHovered.value) {
visibleItems.value.forEach(({
item
}) => item.collapsed = undefined);
return;
}
for (const {
item
} of visibleItems.value) {
item.collapsed = item.queueIndex > 0 ? {
width: queue.lastItemSize.value.width,
height: queue.lastItemSize.value.height
} : undefined;
}
}
watch(queue.lastItemSize, updateDynamicProps);
watch(() => props.collapsed, updateDynamicProps);
useRender(() => {
const hasActions = !!(props.closable || slots.actions);
const snackbarProps = omit(VSnackbar.filterProps(props), ['modelValue', 'collapsed']);
const pauseAll = documentVisibility.value === 'hidden' || props.collapsed && isHovered.value;
return _createElementVNode(_Fragment, null, [visibleItems.value.map(({
id,
item,
active
}) => slots.item ? _createVNode(VDefaultsProvider, {
"defaults": {
VSnackbar: item
}
}, {
default: () => [slots.item({
item
})]
}) : _createVNode(VSnackbar, _mergeProps({
"key": id
}, attrs, snackbarProps, item, pauseAll ? {
timeout: -1
} : {}, {
"queueGap": Number(props.gap),
"contentProps": mergeProps(snackbarProps.contentProps, {
onMouseenter: runOpenDelay,
onMouseleave: () => runCloseDelay()
}),
"modelValue": active,
"onUpdate:modelValue": () => dismiss(id, 'auto'),
"onAfterLeave": () => removeItem(id)
}), {
header: slots.header ? () => slots.header?.({
item
}) : undefined,
text: slots.text ? () => slots.text?.({
item
}) : undefined,
actions: hasActions ? () => _createElementVNode(_Fragment, null, [!slots.actions ? _createVNode(VBtn, _mergeProps(btnProps.value, {
"onClick": () => dismiss(id, 'dismissed')
}), null) : _createVNode(VDefaultsProvider, {
"defaults": {
VBtn: btnProps.value
}
}, {
default: () => [slots.actions({
item,
props: {
onClick: () => dismiss(id, 'dismissed')
}
})]
})]) : undefined
}))]);
});
return {
clear
};
}
});
//# sourceMappingURL=VSnackbarQueue.js.map