routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,234 @@
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
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export { VSnackbarQueue } from './VSnackbarQueue.js';
+2
View File
@@ -0,0 +1,2 @@
export { VSnackbarQueue } from "./VSnackbarQueue.js";
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","names":["VSnackbarQueue"],"sources":["../../../src/components/VSnackbarQueue/index.ts"],"sourcesContent":["export { VSnackbarQueue } from './VSnackbarQueue'\n"],"mappings":"SAASA,cAAc","ignoreList":[]}
+25
View File
@@ -0,0 +1,25 @@
import type { InjectionKey, Ref } from 'vue';
export interface SnackbarQueueItemState {
height: number;
width: number;
}
export interface SnackbarQueueProvide {
register: (id: string) => void;
unregister: (id: string) => void;
setSize: (id: string, height: number, width: number) => void;
getOffset: (id: string) => number | null;
items: Ref<Map<string, SnackbarQueueItemState>>;
gap: Ref<number>;
lastItemSize: Ref<{
height: number;
width: number;
}>;
}
export declare const VSnackbarQueueSymbol: InjectionKey<SnackbarQueueProvide>;
export declare function useSnackbarQueue(props: {
gap: string | number;
}): SnackbarQueueProvide;
export declare function useSnackbarItem(isActive: Ref<boolean>, contentEl: () => HTMLElement | undefined): {
id: string;
offset: import("vue").ComputedRef<number | null>;
} | null;
+88
View File
@@ -0,0 +1,88 @@
// Composables
import { useResizeObserver } from "../../composables/resizeObserver.js"; // Utilities
import { computed, inject, onBeforeUnmount, provide, ref, toRef, useId, watch } from 'vue';
// Types
export const VSnackbarQueueSymbol = Symbol.for('vuetify:v-snackbar-queue');
export function useSnackbarQueue(props) {
const items = ref(new Map());
const gap = toRef(() => Number(props.gap));
function register(id) {
items.value.set(id, {
height: 0,
width: 0
});
}
function unregister(id) {
items.value.delete(id);
}
function setSize(id, height, width) {
const item = items.value.get(id);
if (!item || item.height === height && item.width === width) return;
item.height = height;
item.width = width;
}
const lastItemSize = computed(() => {
for (const {
width,
height
} of [...items.value.values()].toReversed()) {
if (!width || !height) continue;
return {
width,
height
};
}
return {
width: 0,
height: 0
};
});
function getOffset(id) {
if (!items.value.has(id)) return null;
let offset = 0;
for (const [itemId, state] of [...items.value.entries()].toReversed()) {
if (itemId === id) break;
offset += state.height + gap.value;
}
return offset;
}
const state = {
register,
unregister,
setSize,
getOffset,
items,
gap,
lastItemSize
};
provide(VSnackbarQueueSymbol, state);
return state;
}
export function useSnackbarItem(isActive, contentEl) {
const queue = inject(VSnackbarQueueSymbol, null);
if (!queue) return null;
const id = useId();
queue.register(id);
onBeforeUnmount(() => queue.unregister(id));
watch(isActive, val => !val && queue.unregister(id), {
flush: 'sync'
});
const {
resizeRef,
contentRect
} = useResizeObserver();
watch(contentEl, el => {
resizeRef.value = el ?? null;
});
watch(contentRect, rect => {
if (rect?.width) queue.setSize(id, rect.height, rect.width);
});
const offset = computed(() => queue.getOffset(id));
return {
id,
offset
};
}
//# sourceMappingURL=queue.js.map
File diff suppressed because one or more lines are too long