perf: optimization of tabbar display (#4169)
* perf: optimization of tabbar display * fix: ci error * chore: typo * chore: typopull/48/MERGE
parent
8987067b5a
commit
0faf7810b6
|
@ -40,11 +40,11 @@
|
|||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.12",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,11 +40,11 @@
|
|||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"element-plus": "^2.8.0",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@
|
|||
"@nolebase/vitepress-plugin-git-changelog": "^2.4.0",
|
||||
"@vite-pwa/vitepress": "^0.5.0",
|
||||
"vitepress": "^1.3.2",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ export async function typescript(): Promise<Linter.Config[]> {
|
|||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
|
||||
// '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-empty-function': [
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"node": ">=20",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.0",
|
||||
"packageManager": "pnpm@9.7.1",
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"lucide-vue-next": "^0.427.0",
|
||||
"vue": "^3.4.38"
|
||||
"lucide-vue-next": "^0.428.0",
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@vue/shared": "^3.4.38",
|
||||
"@vue/shared": "^3.4.37",
|
||||
"clsx": "^2.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,20 +107,23 @@ type MergeAll<
|
|||
? MergeAll<Rest, Merge<R, F>>
|
||||
: R;
|
||||
|
||||
export {
|
||||
type AnyFunction,
|
||||
type AnyNormalFunction,
|
||||
type AnyPromiseFunction,
|
||||
type DeepPartial,
|
||||
type DeepReadonly,
|
||||
type IntervalHandle,
|
||||
type MaybeComputedRef,
|
||||
type MaybeReadonlyRef,
|
||||
type Merge,
|
||||
type MergeAll,
|
||||
type NonNullable,
|
||||
type Nullable,
|
||||
type ReadonlyRecordable,
|
||||
type Recordable,
|
||||
type TimeoutHandle,
|
||||
type EmitType = (name: Name, ...args: any[]) => void;
|
||||
|
||||
export type {
|
||||
AnyFunction,
|
||||
AnyNormalFunction,
|
||||
AnyPromiseFunction,
|
||||
DeepPartial,
|
||||
DeepReadonly,
|
||||
EmitType,
|
||||
IntervalHandle,
|
||||
MaybeComputedRef,
|
||||
MaybeReadonlyRef,
|
||||
Merge,
|
||||
MergeAll,
|
||||
NonNullable,
|
||||
Nullable,
|
||||
ReadonlyRecordable,
|
||||
Recordable,
|
||||
TimeoutHandle,
|
||||
};
|
||||
|
|
|
@ -36,10 +36,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"radix-vue": "^1.9.4",
|
||||
"sortablejs": "^1.15.2",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.4.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sortablejs": "^1.15.8"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { CSSProperties } from 'vue';
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
|
||||
|
@ -14,6 +14,7 @@ import { useCssVar, useDebounceFn } from '@vueuse/core';
|
|||
* @zh_CN content style
|
||||
*/
|
||||
function useContentStyle() {
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
const contentElement = ref<HTMLDivElement | null>(null);
|
||||
const visibleDomRect = ref<null | VisibleDomRect>(null);
|
||||
const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
|
||||
|
@ -41,12 +42,15 @@ function useContentStyle() {
|
|||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (contentElement.value) {
|
||||
const observer = new ResizeObserver(debouncedCalcHeight);
|
||||
observer.observe(contentElement.value);
|
||||
}
|
||||
});
|
||||
if (contentElement.value && !resizeObserver) {
|
||||
resizeObserver = new ResizeObserver(debouncedCalcHeight);
|
||||
resizeObserver.observe(contentElement.value);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
});
|
||||
|
||||
return { contentElement, overlayStyle, visibleDomRect };
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('useSortable', () => {
|
|||
expect(Sortable.default.create).toHaveBeenCalledWith(
|
||||
mockElement,
|
||||
expect.objectContaining({
|
||||
animation: 100,
|
||||
animation: 300,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
...customOptions,
|
||||
|
|
|
@ -18,7 +18,7 @@ function useSortable<T extends HTMLElement>(
|
|||
// Sortable?.default?.mount?.(AutoScroll);
|
||||
|
||||
const sortable = Sortable?.default?.create?.(sortableContainer, {
|
||||
animation: 100,
|
||||
animation: 300,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
...options,
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"dependencies": {
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"vue": "^3.4.38"
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
"@vben-core/icons": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"vue": "^3.4.38"
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"vue": "^3.4.38"
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,10 +46,10 @@
|
|||
"@vben-core/icons": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"lucide-vue-next": "^0.427.0",
|
||||
"lucide-vue-next": "^0.428.0",
|
||||
"radix-vue": "^1.9.4",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
|
@ -11,6 +11,10 @@ interface Props {
|
|||
scrollBarClass?: any;
|
||||
shadow?: boolean;
|
||||
shadowBorder?: boolean;
|
||||
shadowBottom?: boolean;
|
||||
shadowLeft?: boolean;
|
||||
shadowRight?: boolean;
|
||||
shadowTop?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -18,29 +22,66 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
horizontal: false,
|
||||
shadow: false,
|
||||
shadowBorder: false,
|
||||
shadowBottom: true,
|
||||
shadowLeft: false,
|
||||
shadowRight: false,
|
||||
shadowTop: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
scrollAt: [{ bottom: boolean; left: boolean; right: boolean; top: boolean }];
|
||||
}>();
|
||||
|
||||
const isAtTop = ref(true);
|
||||
const isAtRight = ref(false);
|
||||
const isAtBottom = ref(false);
|
||||
const isAtLeft = ref(true);
|
||||
|
||||
const showShadowTop = computed(() => props.shadow && props.shadowTop);
|
||||
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
|
||||
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
|
||||
const showShadowRight = computed(() => props.shadow && props.shadowRight);
|
||||
|
||||
const computedShadowClasses = computed(() => ({
|
||||
'shadow-both':
|
||||
!isAtLeft.value &&
|
||||
!isAtRight.value &&
|
||||
showShadowLeft.value &&
|
||||
showShadowRight.value,
|
||||
'shadow-left': !isAtLeft.value && showShadowLeft.value,
|
||||
'shadow-right': !isAtRight.value && showShadowRight.value,
|
||||
}));
|
||||
|
||||
function handleScroll(event: Event) {
|
||||
const target = event.target as HTMLElement;
|
||||
const scrollTop = target?.scrollTop ?? 0;
|
||||
const scrollLeft = target?.scrollLeft ?? 0;
|
||||
const offsetHeight = target?.offsetHeight ?? 0;
|
||||
const offsetWidth = target?.offsetWidth ?? 0;
|
||||
const scrollHeight = target?.scrollHeight ?? 0;
|
||||
const scrollWidth = target?.scrollWidth ?? 0;
|
||||
isAtTop.value = scrollTop <= 0;
|
||||
isAtLeft.value = scrollLeft <= 0;
|
||||
isAtBottom.value = scrollTop + offsetHeight >= scrollHeight;
|
||||
isAtRight.value = scrollLeft + offsetWidth >= scrollWidth;
|
||||
|
||||
emit('scrollAt', {
|
||||
bottom: isAtBottom.value,
|
||||
left: isAtLeft.value,
|
||||
right: isAtRight.value,
|
||||
top: isAtTop.value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollArea
|
||||
:class="[cn(props.class)]"
|
||||
:class="[cn(props.class), computedShadowClasses]"
|
||||
:on-scroll="handleScroll"
|
||||
class="relative"
|
||||
class="vben-scrollbar relative"
|
||||
>
|
||||
<div
|
||||
v-if="shadow"
|
||||
v-if="showShadowTop"
|
||||
:class="{
|
||||
'opacity-100': !isAtTop,
|
||||
'border-border border-t': shadowBorder && !isAtTop,
|
||||
|
@ -49,7 +90,7 @@ function handleScroll(event: Event) {
|
|||
></div>
|
||||
<slot></slot>
|
||||
<div
|
||||
v-if="shadow"
|
||||
v-if="showShadowBottom"
|
||||
:class="{
|
||||
'opacity-100': !isAtTop && !isAtBottom,
|
||||
'border-border border-b': shadowBorder && !isAtTop && !isAtBottom,
|
||||
|
@ -65,6 +106,31 @@ function handleScroll(event: Event) {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vben-scrollbar {
|
||||
&:not(.shadow-both).shadow-left {
|
||||
mask-image: linear-gradient(90deg, transparent, #000 16px);
|
||||
}
|
||||
|
||||
&:not(.shadow-both).shadow-right {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
#000 0%,
|
||||
#000 calc(100% - 16px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&.shadow-both {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
#000 16px,
|
||||
#000 calc(100% - 16px),
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-top-shadow {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"@vben-core/icons": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue": "^3.4.38"
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import type { TabDefinition } from '@vben-core/typings';
|
|||
|
||||
import type { TabConfig, TabsProps } from '../../types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { MdiPin, X } from '@vben-core/icons';
|
||||
import { VbenContextMenu, VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
||||
|
@ -20,17 +20,17 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
contentClass: 'vben-tabs-content',
|
||||
contextMenus: () => [],
|
||||
gap: 7,
|
||||
maxWidth: 150,
|
||||
minWidth: 80,
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>();
|
||||
const emit = defineEmits<{
|
||||
close: [string];
|
||||
unpin: [TabDefinition];
|
||||
}>();
|
||||
const active = defineModel<string>('active');
|
||||
|
||||
const contentRef = ref();
|
||||
const tabRef = ref();
|
||||
const tabWidth = ref<number>(props.maxWidth);
|
||||
|
||||
const style = computed(() => {
|
||||
const { gap } = props;
|
||||
|
@ -53,148 +53,118 @@ const tabsView = computed((): TabConfig[] => {
|
|||
};
|
||||
});
|
||||
});
|
||||
|
||||
watch(active, () => {
|
||||
scrollIntoView();
|
||||
});
|
||||
|
||||
function scrollIntoView() {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector(`.tabs-chrome__item.is-active`);
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="style" class="tabs-chrome size-full flex-1 overflow-hidden pt-1">
|
||||
<VbenScrollbar
|
||||
id="tabs-scrollbar"
|
||||
class="tabs-chrome__scrollbar h-full"
|
||||
horizontal
|
||||
scroll-bar-class="z-10 hidden"
|
||||
>
|
||||
<!-- footer -> 4px -->
|
||||
<div
|
||||
ref="contentRef"
|
||||
:class="contentClass"
|
||||
:style="style"
|
||||
class="tabs-chrome !flex h-full w-max pr-6"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
ref="contentRef"
|
||||
:class="contentClass"
|
||||
class="relative !flex h-full w-max"
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
ref="tabRef"
|
||||
:class="[{ 'is-active': tab.key === active, dragable: !tab.affixTab }]"
|
||||
:data-active-tab="active"
|
||||
:data-index="i"
|
||||
class="tabs-chrome__item draggable group relative -mr-3 flex h-full select-none items-center"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
ref="tabRef"
|
||||
:class="[
|
||||
{ 'is-active': tab.key === active, dragable: !tab.affixTab },
|
||||
]"
|
||||
:data-active-tab="active"
|
||||
:data-index="i"
|
||||
:style="{
|
||||
width: `${tabWidth}px`,
|
||||
left: `${(tabWidth - gap * 2) * i}px`,
|
||||
}"
|
||||
class="tabs-chrome__item group absolute flex h-full select-none items-center transition-all"
|
||||
@click="active = tab.key"
|
||||
>
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
:menus="contextMenus"
|
||||
:modal="false"
|
||||
item-class="pr-6"
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
:menus="contextMenus"
|
||||
:modal="false"
|
||||
item-class="pr-6"
|
||||
>
|
||||
<div class="relative size-full px-1">
|
||||
<!-- divider -->
|
||||
<div
|
||||
v-if="i !== 0 && tab.key !== active"
|
||||
class="tabs-chrome__divider bg-foreground/50 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
|
||||
></div>
|
||||
<!-- background -->
|
||||
<div
|
||||
class="tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
|
||||
>
|
||||
<div class="size-full">
|
||||
<!-- divider -->
|
||||
<div
|
||||
v-if="i !== 0 && tab.key !== active"
|
||||
class="tabs-chrome__divider bg-foreground/60 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
|
||||
></div>
|
||||
<!-- background -->
|
||||
<div
|
||||
class="tabs-chrome__background absolute z-[1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
|
||||
>
|
||||
<div
|
||||
class="tabs-chrome__background-content group-[.is-active]:bg-primary/15 dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
|
||||
></div>
|
||||
<svg
|
||||
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
|
||||
</svg>
|
||||
<svg
|
||||
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-chrome__background-content group-[.is-active]:bg-heavy dark:group-[.is-active]:bg-accent h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
|
||||
></div>
|
||||
<svg
|
||||
class="tabs-chrome__background-before group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 left-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
|
||||
</svg>
|
||||
<svg
|
||||
class="tabs-chrome__background-after group-[.is-active]:fill-primary/15 dark:group-[.is-active]:fill-accent absolute bottom-0 right-[-1px] fill-transparent transition-all duration-150"
|
||||
height="7"
|
||||
width="7"
|
||||
>
|
||||
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- extra -->
|
||||
<div
|
||||
class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]"
|
||||
>
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="
|
||||
!tab.affixTab && tabsView.length > 1 && tab.closable
|
||||
"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<MdiPin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
<!-- extra -->
|
||||
<div
|
||||
class="tabs-chrome__extra absolute right-[var(--gap)] top-1/2 z-[3] size-4 translate-y-[-50%]"
|
||||
>
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<MdiPin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:text-accent-foreground text-accent-foreground/80 group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="tabs-chrome__item-main group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground text-accent-foreground absolute left-0 right-0 z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-4 duration-150 group-hover:pr-3"
|
||||
>
|
||||
<VbenIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="ml-[var(--gap)] flex size-4 items-center overflow-hidden"
|
||||
fallback
|
||||
/>
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="tabs-chrome__item-main group-[.is-active]:text-accent-foreground dark:group-[.is-active]:text-accent-foreground text-accent-foreground z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pl-2 pr-4 duration-150"
|
||||
>
|
||||
<VbenIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="mr-1 flex size-4 items-center overflow-hidden"
|
||||
fallback
|
||||
/>
|
||||
|
||||
<span
|
||||
class="tabs-chrome__label ml-[var(--gap)] flex-1 overflow-hidden whitespace-nowrap text-sm"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</VbenContextMenu>
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</VbenContextMenu>
|
||||
</div>
|
||||
<!-- footer -->
|
||||
<!-- <div class="bg-background h-1"></div> -->
|
||||
</VbenScrollbar>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tabs-chrome {
|
||||
.dragging {
|
||||
.tabs-chrome__item-main {
|
||||
/* .dragging { */
|
||||
|
||||
/* .tabs-chrome__item-main {
|
||||
@apply pr-0;
|
||||
}
|
||||
} */
|
||||
|
||||
.tabs-chrome__extra {
|
||||
/* .tabs-chrome__extra {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
/* } */
|
||||
|
||||
&__item:not(.dragging) {
|
||||
@apply cursor-pointer;
|
||||
|
||||
&__item {
|
||||
&:hover:not(.is-active) {
|
||||
& + .tabs-chrome__item {
|
||||
.tabs-chrome__divider {
|
||||
|
@ -207,13 +177,10 @@ function scrollIntoView() {
|
|||
}
|
||||
|
||||
.tabs-chrome__background {
|
||||
&-content {
|
||||
@apply bg-accent mx-1 rounded-md pb-2;
|
||||
}
|
||||
@apply pb-[2px];
|
||||
|
||||
&-before,
|
||||
&-after {
|
||||
@apply fill-primary/0;
|
||||
&-content {
|
||||
@apply bg-accent-hover mx-[2px] rounded-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,30 +193,7 @@ function scrollIntoView() {
|
|||
@apply opacity-0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-chrome__background {
|
||||
@apply opacity-100;
|
||||
|
||||
/* &-content {
|
||||
@apply bg-accent;
|
||||
}
|
||||
|
||||
&-before,
|
||||
&-after {
|
||||
@apply fill-heavy;
|
||||
} */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__scrollbar,
|
||||
&__label {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
#000 0%,
|
||||
#000 calc(100% - 16px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,10 +3,10 @@ import type { TabDefinition } from '@vben-core/typings';
|
|||
|
||||
import type { TabConfig, TabsProps } from '../../types';
|
||||
|
||||
import { computed, watch } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { MdiPin, X } from '@vben-core/icons';
|
||||
import { VbenContextMenu, VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
||||
|
@ -21,7 +21,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
tabs: () => [],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ close: [string]; unpin: [TabDefinition] }>();
|
||||
const emit = defineEmits<{
|
||||
close: [string];
|
||||
unpin: [TabDefinition];
|
||||
}>();
|
||||
const active = defineModel<string>('active');
|
||||
|
||||
const typeWithClass = computed(() => {
|
||||
|
@ -55,108 +58,71 @@ const tabsView = computed((): TabConfig[] => {
|
|||
};
|
||||
});
|
||||
});
|
||||
|
||||
watch(active, () => {
|
||||
scrollIntoView();
|
||||
});
|
||||
|
||||
function scrollIntoView() {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector(`.tabs-chrome__item.is-active`);
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="size-full flex-1 overflow-hidden">
|
||||
<VbenScrollbar
|
||||
id="tabs-scrollbar"
|
||||
class="tabs-scrollbar h-full"
|
||||
horizontal
|
||||
scroll-bar-class="z-10 hidden"
|
||||
>
|
||||
<div
|
||||
:class="contentClass"
|
||||
class="relative !flex h-full w-max items-center pr-6"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
:class="contentClass"
|
||||
class="relative !flex h-full w-max items-center"
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
:class="[
|
||||
{
|
||||
'is-active dark:bg-accent bg-primary/15': tab.key === active,
|
||||
dragable: !tab.affixTab,
|
||||
},
|
||||
typeWithClass.content,
|
||||
]"
|
||||
:data-index="i"
|
||||
class="tab-item [&:not(.is-active)]:hover:bg-accent group relative flex cursor-pointer select-none"
|
||||
data-tab-item="true"
|
||||
@click="active = tab.key"
|
||||
>
|
||||
<TransitionGroup name="slide-left">
|
||||
<div
|
||||
v-for="(tab, i) in tabsView"
|
||||
:key="tab.key"
|
||||
:class="[
|
||||
{
|
||||
'is-active dark:bg-accent bg-primary/15': tab.key === active,
|
||||
dragable: !tab.affixTab,
|
||||
},
|
||||
typeWithClass.content,
|
||||
]"
|
||||
:data-index="i"
|
||||
class="tabs-chrome__item [&:not(.is-active)]:hover:bg-accent group relative flex cursor-pointer select-none transition-all duration-300"
|
||||
@click="active = tab.key"
|
||||
>
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
:menus="contextMenus"
|
||||
:modal="false"
|
||||
item-class="pr-6"
|
||||
<VbenContextMenu
|
||||
:handler-data="tab"
|
||||
:menus="contextMenus"
|
||||
:modal="false"
|
||||
item-class="pr-6"
|
||||
>
|
||||
<div class="relative flex size-full items-center">
|
||||
<!-- extra -->
|
||||
<div
|
||||
class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden"
|
||||
>
|
||||
<div class="relative flex size-full items-center">
|
||||
<!-- extra -->
|
||||
<div
|
||||
class="absolute right-1.5 top-1/2 z-[3] translate-y-[-50%] overflow-hidden"
|
||||
>
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="
|
||||
!tab.affixTab && tabsView.length > 1 && tab.closable
|
||||
"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<MdiPin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
<!-- close-icon -->
|
||||
<X
|
||||
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('close', tab.key)"
|
||||
/>
|
||||
<MdiPin
|
||||
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
|
||||
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
|
||||
@click.stop="() => emit('unpin', tab)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
|
||||
>
|
||||
<VbenIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="mr-2 flex size-4 items-center overflow-hidden"
|
||||
fallback
|
||||
/>
|
||||
<!-- tab-item-main -->
|
||||
<div
|
||||
class="text-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
|
||||
>
|
||||
<VbenIcon
|
||||
v-if="showIcon"
|
||||
:icon="tab.icon"
|
||||
class="mr-2 flex size-4 items-center overflow-hidden"
|
||||
fallback
|
||||
/>
|
||||
|
||||
<span
|
||||
class="flex-1 overflow-hidden whitespace-nowrap text-sm"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</VbenContextMenu>
|
||||
<span class="flex-1 overflow-hidden whitespace-nowrap text-sm">
|
||||
{{ tab.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</VbenContextMenu>
|
||||
</div>
|
||||
</VbenScrollbar>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tabs-scrollbar {
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
#000 0%,
|
||||
#000 calc(100% - 16px),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { Sortable } from '@vben-core/composables';
|
||||
import type { TabDefinition } from '@vben-core/typings';
|
||||
import type { TabsEmits, TabsProps } from './types';
|
||||
|
||||
import type { TabsProps } from './types';
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits, useSortable } from '@vben-core/composables';
|
||||
import { useForwardPropsEmits } from '@vben-core/composables';
|
||||
import { ChevronLeft, ChevronRight } from '@vben-core/icons';
|
||||
import { VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { Tabs, TabsChrome } from './components';
|
||||
import { useTabsDrag } from './use-tabs-drag';
|
||||
import { useTabsViewScroll } from './use-tabs-view-scroll';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
@ -24,136 +21,69 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
styleType: 'chrome',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [string];
|
||||
sortTabs: [number, number];
|
||||
unpin: [TabDefinition];
|
||||
}>();
|
||||
const emit = defineEmits<TabsEmits>();
|
||||
|
||||
const forward = useForwardPropsEmits(props, emit);
|
||||
|
||||
const { initScrollbar, scrollDirection } = useTabsViewScroll();
|
||||
const {
|
||||
handleScrollAt,
|
||||
scrollbarRef,
|
||||
scrollDirection,
|
||||
scrollIsAtLeft,
|
||||
scrollIsAtRight,
|
||||
showScrollButton,
|
||||
} = useTabsViewScroll(props);
|
||||
|
||||
const sortableInstance = ref<null | Sortable>(null);
|
||||
|
||||
// 可能会找到拖拽的子元素,这里需要确保拖拽的dom时tab元素
|
||||
function findParentElement(element: HTMLElement) {
|
||||
const parentCls = 'group';
|
||||
return element.classList.contains(parentCls)
|
||||
? element
|
||||
: element.closest(`.${parentCls}`);
|
||||
}
|
||||
|
||||
async function initTabsSortable() {
|
||||
await nextTick();
|
||||
const { contentClass } = props;
|
||||
|
||||
const el = document.querySelectorAll(`.${contentClass}`)?.[0] as HTMLElement;
|
||||
|
||||
const resetElState = () => {
|
||||
el.style.cursor = 'default';
|
||||
el.classList.remove('dragging');
|
||||
};
|
||||
|
||||
const { initializeSortable } = useSortable(el, {
|
||||
filter: (_evt, target: HTMLElement) => {
|
||||
const parent = findParentElement(target);
|
||||
const dragable = parent?.classList.contains('dragable');
|
||||
return !dragable || !props.dragable;
|
||||
},
|
||||
onEnd(evt) {
|
||||
const { newIndex, oldIndex } = evt;
|
||||
// const fromElement = evt.item;
|
||||
const { srcElement } = (evt as any).originalEvent;
|
||||
|
||||
if (!srcElement) {
|
||||
resetElState();
|
||||
return;
|
||||
}
|
||||
|
||||
const srcParent = findParentElement(srcElement);
|
||||
|
||||
if (!srcParent) {
|
||||
resetElState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!srcParent.classList.contains('dragable')) {
|
||||
resetElState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
oldIndex !== undefined &&
|
||||
newIndex !== undefined &&
|
||||
!Number.isNaN(oldIndex) &&
|
||||
!Number.isNaN(newIndex) &&
|
||||
oldIndex !== newIndex
|
||||
) {
|
||||
emit('sortTabs', oldIndex, newIndex);
|
||||
}
|
||||
resetElState();
|
||||
},
|
||||
onMove(evt) {
|
||||
const parent = findParentElement(evt.related);
|
||||
return parent?.classList.contains('dragable') && props.dragable;
|
||||
},
|
||||
onStart: () => {
|
||||
el.style.cursor = 'grabbing';
|
||||
el.classList.add('dragging');
|
||||
},
|
||||
});
|
||||
|
||||
sortableInstance.value = await initializeSortable();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await nextTick();
|
||||
initTabsSortable();
|
||||
initScrollbar();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.styleType,
|
||||
() => {
|
||||
sortableInstance.value?.destroy();
|
||||
init();
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
sortableInstance.value?.destroy();
|
||||
});
|
||||
useTabsDrag(props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'overflow-hidden': styleType !== 'chrome',
|
||||
}"
|
||||
class="flex h-full flex-1"
|
||||
>
|
||||
<div class="flex h-full flex-1 overflow-hidden">
|
||||
<!-- 左侧滚动按钮 -->
|
||||
<span
|
||||
class="hover:bg-muted text-muted-foreground cursor-pointer border-r px-2"
|
||||
v-show="showScrollButton"
|
||||
:class="{
|
||||
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtLeft,
|
||||
'pointer-events-none opacity-30': scrollIsAtLeft,
|
||||
}"
|
||||
class="border-r px-2"
|
||||
@click="scrollDirection('left')"
|
||||
>
|
||||
<ChevronLeft class="size-4 h-full" />
|
||||
</span>
|
||||
|
||||
<TabsChrome
|
||||
v-if="styleType === 'chrome'"
|
||||
v-bind="{ ...forward, ...$attrs, ...$props }"
|
||||
/>
|
||||
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
|
||||
<div
|
||||
:class="{
|
||||
'pt-[3px]': styleType === 'chrome',
|
||||
}"
|
||||
class="size-full flex-1 overflow-hidden"
|
||||
>
|
||||
<VbenScrollbar
|
||||
ref="scrollbarRef"
|
||||
class="h-full"
|
||||
horizontal
|
||||
scroll-bar-class="z-10 hidden"
|
||||
shadow
|
||||
shadow-left
|
||||
shadow-right
|
||||
@scroll-at="handleScrollAt"
|
||||
>
|
||||
<TabsChrome
|
||||
v-if="styleType === 'chrome'"
|
||||
v-bind="{ ...forward, ...$attrs, ...$props }"
|
||||
/>
|
||||
|
||||
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
|
||||
</VbenScrollbar>
|
||||
</div>
|
||||
|
||||
<!-- 左侧滚动按钮 -->
|
||||
<span
|
||||
v-show="showScrollButton"
|
||||
:class="{
|
||||
'hover:bg-muted text-muted-foreground cursor-pointer': !scrollIsAtRight,
|
||||
'pointer-events-none opacity-30': scrollIsAtRight,
|
||||
}"
|
||||
class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
|
||||
@click="scrollDirection('right')"
|
||||
>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import type { IContextMenuItem } from '@vben-core/shadcn-ui';
|
||||
import type { TabDefinition, TabsStyleType } from '@vben-core/typings';
|
||||
|
||||
interface TabsProps {
|
||||
export type TabsEmits = {
|
||||
close: [string];
|
||||
sortTabs: [number, number];
|
||||
unpin: [TabDefinition];
|
||||
};
|
||||
|
||||
export interface TabsProps {
|
||||
active?: string;
|
||||
/**
|
||||
* @zh_CN content class
|
||||
* @default tabs-chrome
|
||||
|
@ -48,12 +55,10 @@ interface TabsProps {
|
|||
tabs?: TabDefinition[];
|
||||
}
|
||||
|
||||
interface TabConfig extends TabDefinition {
|
||||
export interface TabConfig extends TabDefinition {
|
||||
affixTab: boolean;
|
||||
closable: boolean;
|
||||
icon: string;
|
||||
key: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type { TabConfig, TabsProps };
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import type { EmitType } from '@vben-core/typings';
|
||||
|
||||
import type { TabsProps } from './types';
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { type Sortable, useSortable } from '@vben-core/composables';
|
||||
|
||||
// 可能会找到拖拽的子元素,这里需要确保拖拽的dom时tab元素
|
||||
function findParentElement(element: HTMLElement) {
|
||||
const parentCls = 'group';
|
||||
return element.classList.contains(parentCls)
|
||||
? element
|
||||
: element.closest(`.${parentCls}`);
|
||||
}
|
||||
|
||||
export function useTabsDrag(props: TabsProps, emit: EmitType) {
|
||||
const sortableInstance = ref<null | Sortable>(null);
|
||||
|
||||
async function initTabsSortable() {
|
||||
await nextTick();
|
||||
|
||||
const el = document.querySelectorAll(
|
||||
`.${props.contentClass}`,
|
||||
)?.[0] as HTMLElement;
|
||||
|
||||
if (!el) {
|
||||
console.warn('Element not found for sortable initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
const resetElState = async () => {
|
||||
el.style.cursor = 'default';
|
||||
el.classList.remove('dragging');
|
||||
el.querySelector('.draggable')?.classList.remove('dragging');
|
||||
};
|
||||
|
||||
const { initializeSortable } = useSortable(el, {
|
||||
filter: (_evt, target: HTMLElement) => {
|
||||
const parent = findParentElement(target);
|
||||
const dragable = parent?.classList.contains('dragable');
|
||||
return !dragable || !props.dragable;
|
||||
},
|
||||
onEnd(evt) {
|
||||
const { newIndex, oldIndex } = evt;
|
||||
// const fromElement = evt.item;
|
||||
const { srcElement } = (evt as any).originalEvent;
|
||||
|
||||
if (!srcElement) {
|
||||
resetElState();
|
||||
return;
|
||||
}
|
||||
|
||||
const srcParent = findParentElement(srcElement);
|
||||
|
||||
if (!srcParent) {
|
||||
resetElState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!srcParent.classList.contains('dragable')) {
|
||||
resetElState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
oldIndex !== undefined &&
|
||||
newIndex !== undefined &&
|
||||
!Number.isNaN(oldIndex) &&
|
||||
!Number.isNaN(newIndex) &&
|
||||
oldIndex !== newIndex
|
||||
) {
|
||||
emit('sortTabs', oldIndex, newIndex);
|
||||
}
|
||||
resetElState();
|
||||
},
|
||||
onMove(evt) {
|
||||
const parent = findParentElement(evt.related);
|
||||
return parent?.classList.contains('dragable') && props.dragable;
|
||||
},
|
||||
onStart: () => {
|
||||
el.style.cursor = 'grabbing';
|
||||
el.querySelector('.draggable')?.classList.add('dragging');
|
||||
// el.classList.add('dragging');
|
||||
},
|
||||
});
|
||||
|
||||
sortableInstance.value = await initializeSortable();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await nextTick();
|
||||
initTabsSortable();
|
||||
}
|
||||
|
||||
onMounted(init);
|
||||
|
||||
watch(
|
||||
() => props.styleType,
|
||||
() => {
|
||||
sortableInstance.value?.destroy();
|
||||
init();
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
sortableInstance.value?.destroy();
|
||||
});
|
||||
}
|
|
@ -1,15 +1,28 @@
|
|||
import { nextTick, ref } from 'vue';
|
||||
import type { TabsProps } from './types';
|
||||
|
||||
type El = Element | null | undefined;
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
export function useTabsViewScroll(scrollDistance: number = 150) {
|
||||
const scrollbarEl = ref<El>(null);
|
||||
const scrollViewportEl = ref<El>(null);
|
||||
import { VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
type DomElement = Element | null | undefined;
|
||||
|
||||
export function useTabsViewScroll(props: TabsProps) {
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
let mutationObserver: MutationObserver | null = null;
|
||||
let tabItemCount = 0;
|
||||
const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null);
|
||||
const scrollViewportEl = ref<DomElement>(null);
|
||||
const showScrollButton = ref(false);
|
||||
const scrollIsAtLeft = ref(true);
|
||||
const scrollIsAtRight = ref(false);
|
||||
|
||||
function getScrollClientWidth() {
|
||||
if (!scrollbarEl.value || !scrollViewportEl.value) return {};
|
||||
const scrollbarEl = scrollbarRef.value?.$el;
|
||||
if (!scrollbarEl || !scrollViewportEl.value) return {};
|
||||
|
||||
const scrollbarWidth = scrollbarEl.value.clientWidth;
|
||||
const scrollbarWidth = scrollbarEl.clientWidth;
|
||||
const scrollViewWidth = scrollViewportEl.value.clientWidth;
|
||||
|
||||
return {
|
||||
|
@ -20,7 +33,7 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
|
|||
|
||||
function scrollDirection(
|
||||
direction: 'left' | 'right',
|
||||
distance: number = scrollDistance,
|
||||
distance: number = 150,
|
||||
) {
|
||||
const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
|
||||
|
||||
|
@ -39,21 +52,142 @@ export function useTabsViewScroll(scrollDistance: number = 150) {
|
|||
|
||||
async function initScrollbar() {
|
||||
await nextTick();
|
||||
const barEl = document.querySelector('#tabs-scrollbar');
|
||||
|
||||
const viewportEl = barEl?.querySelector(
|
||||
const scrollbarEl = scrollbarRef.value?.$el;
|
||||
if (!scrollbarEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportEl = scrollbarEl?.querySelector(
|
||||
'div[data-radix-scroll-area-viewport]',
|
||||
);
|
||||
|
||||
scrollbarEl.value = barEl;
|
||||
scrollViewportEl.value = viewportEl;
|
||||
calcShowScrollbarButton();
|
||||
|
||||
const activeItem = viewportEl?.querySelector('.is-active');
|
||||
activeItem?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
await nextTick();
|
||||
scrollToActiveIntoView();
|
||||
|
||||
// 监听大小变化
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = new ResizeObserver(
|
||||
useDebounceFn((_entries: ResizeObserverEntry[]) => {
|
||||
calcShowScrollbarButton();
|
||||
}, 100),
|
||||
);
|
||||
resizeObserver.observe(viewportEl);
|
||||
|
||||
tabItemCount = props.tabs?.length || 0;
|
||||
mutationObserver?.disconnect();
|
||||
// 使用 MutationObserver 仅监听子节点数量变化
|
||||
mutationObserver = new MutationObserver(() => {
|
||||
const count = viewportEl.querySelectorAll(
|
||||
`div[data-tab-item="true"]`,
|
||||
).length;
|
||||
|
||||
if (count > tabItemCount) {
|
||||
scrollToActiveIntoView();
|
||||
}
|
||||
|
||||
if (count !== tabItemCount) {
|
||||
calcShowScrollbarButton();
|
||||
tabItemCount = count;
|
||||
}
|
||||
});
|
||||
|
||||
// 配置为仅监听子节点的添加和移除
|
||||
mutationObserver.observe(viewportEl, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function scrollToActiveIntoView() {
|
||||
if (!scrollViewportEl.value) {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const viewportEl = scrollViewportEl.value;
|
||||
const { scrollbarWidth } = getScrollClientWidth();
|
||||
const { scrollWidth } = viewportEl;
|
||||
|
||||
if (scrollbarWidth >= scrollWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const activeItem = viewportEl?.querySelector('.is-active');
|
||||
activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算tabs 宽度,用于判断是否显示左右滚动按钮
|
||||
*/
|
||||
async function calcShowScrollbarButton() {
|
||||
if (!scrollViewportEl.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scrollbarWidth } = getScrollClientWidth();
|
||||
|
||||
showScrollButton.value =
|
||||
scrollViewportEl.value.scrollWidth > scrollbarWidth;
|
||||
}
|
||||
|
||||
const handleScrollAt = useDebounceFn(({ left, right }) => {
|
||||
scrollIsAtLeft.value = left;
|
||||
scrollIsAtRight.value = right;
|
||||
}, 100);
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
async () => {
|
||||
// 200为了等待 tab 切换动画完成
|
||||
// setTimeout(() => {
|
||||
scrollToActiveIntoView();
|
||||
// }, 300);
|
||||
},
|
||||
{
|
||||
flush: 'post',
|
||||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => props.tabs?.length,
|
||||
// async () => {
|
||||
// await nextTick();
|
||||
// calcShowScrollbarButton();
|
||||
// },
|
||||
// {
|
||||
// flush: 'post',
|
||||
// },
|
||||
// );
|
||||
|
||||
watch(
|
||||
() => props.styleType,
|
||||
() => {
|
||||
initScrollbar();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(initScrollbar);
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObserver?.disconnect();
|
||||
mutationObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
mutationObserver = null;
|
||||
});
|
||||
|
||||
return {
|
||||
handleScrollAt,
|
||||
initScrollbar,
|
||||
scrollbarRef,
|
||||
scrollDirection,
|
||||
scrollIsAtLeft,
|
||||
scrollIsAtRight,
|
||||
showScrollButton,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,6 @@
|
|||
"@vben/stores": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"echarts": "^5.5.1",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.4.37"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
"@vben/icons": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vueuse/integrations": "^10.11.1",
|
||||
"@vueuse/integrations": "^11.0.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"@vben/stores": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3",
|
||||
"watermark-js-plus": "^1.5.3"
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
"@vben/stores": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"vue": "^3.4.38",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "^9.13.1",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-i18n": "^9.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"@vben-core/typings": "workspace:*",
|
||||
"pinia": "2.2.2",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,10 +124,21 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||
} else {
|
||||
// 页面已经存在,不重复添加选项卡,只更新选项卡参数
|
||||
const currentTab = toRaw(this.tabs)[tabIndex];
|
||||
const mergedTab = { ...currentTab, ...tab };
|
||||
if (currentTab && Reflect.has(currentTab.meta, 'affixTab')) {
|
||||
mergedTab.meta.affixTab = currentTab.meta.affixTab;
|
||||
const mergedTab = {
|
||||
...currentTab,
|
||||
...tab,
|
||||
meta: { ...currentTab?.meta, ...tab.meta },
|
||||
};
|
||||
if (currentTab) {
|
||||
const curMeta = currentTab.meta;
|
||||
if (Reflect.has(curMeta, 'affixTab')) {
|
||||
mergedTab.meta.affixTab = curMeta.affixTab;
|
||||
}
|
||||
if (Reflect.has(curMeta, 'newTabTitle')) {
|
||||
mergedTab.meta.newTabTitle = curMeta.newTabTitle;
|
||||
}
|
||||
}
|
||||
|
||||
this.tabs.splice(tabIndex, 1, mergedTab);
|
||||
}
|
||||
this.updateCacheTab();
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,11 +40,11 @@
|
|||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.12",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ const routes: RouteRecordRaw[] = [
|
|||
import(
|
||||
'#/views/demos/features/hide-menu-children/children.vue'
|
||||
),
|
||||
meta: { title: 'HideChildrenInMenuChildrenDemo' },
|
||||
meta: { title: $t('page.demos.features.hideChildrenInMenu') },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
115
pnpm-lock.yaml
115
pnpm-lock.yaml
|
@ -165,8 +165,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../packages/utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
ant-design-vue:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -228,8 +228,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../packages/utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
dayjs:
|
||||
specifier: ^1.11.12
|
||||
version: 1.11.12
|
||||
|
@ -295,8 +295,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../packages/utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
naive-ui:
|
||||
specifier: ^2.39.0
|
||||
version: 2.39.0(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -660,8 +660,8 @@ importers:
|
|||
specifier: ^4.1.2
|
||||
version: 4.1.2(vue@3.4.38(typescript@5.5.4))
|
||||
lucide-vue-next:
|
||||
specifier: ^0.427.0
|
||||
version: 0.427.0(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^0.428.0
|
||||
version: 0.428.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -672,7 +672,7 @@ importers:
|
|||
specifier: 4.1.0
|
||||
version: 4.1.0
|
||||
'@vue/shared':
|
||||
specifier: ^3.4.38
|
||||
specifier: ^3.4.37
|
||||
version: 3.4.38
|
||||
clsx:
|
||||
specifier: 2.1.1
|
||||
|
@ -715,8 +715,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../base/shared
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
radix-vue:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -740,8 +740,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../base/typings
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -761,8 +761,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../base/typings
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -785,8 +785,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../base/typings
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -806,14 +806,14 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../base/typings
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
lucide-vue-next:
|
||||
specifier: ^0.427.0
|
||||
version: 0.427.0(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^0.428.0
|
||||
version: 0.428.0(vue@3.4.38(typescript@5.5.4))
|
||||
radix-vue:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -835,6 +835,9 @@ importers:
|
|||
'@vben-core/typings':
|
||||
specifier: workspace:*
|
||||
version: link:../../base/typings
|
||||
'@vueuse/core':
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -869,8 +872,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../preferences
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
echarts:
|
||||
specifier: ^5.5.1
|
||||
version: 5.5.1
|
||||
|
@ -899,8 +902,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../types
|
||||
'@vueuse/integrations':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))
|
||||
qrcode:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
|
@ -981,8 +984,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../../utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue:
|
||||
specifier: 3.4.38
|
||||
version: 3.4.38(typescript@5.5.4)
|
||||
|
@ -1129,8 +1132,8 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../packages/utils
|
||||
'@vueuse/core':
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
ant-design-vue:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -4179,6 +4182,9 @@ packages:
|
|||
'@vueuse/core@10.11.1':
|
||||
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
|
||||
|
||||
'@vueuse/core@11.0.0':
|
||||
resolution: {integrity: sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==}
|
||||
|
||||
'@vueuse/core@9.13.0':
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
|
||||
|
@ -4223,21 +4229,21 @@ packages:
|
|||
universal-cookie:
|
||||
optional: true
|
||||
|
||||
'@vueuse/integrations@10.11.1':
|
||||
resolution: {integrity: sha512-Y5hCGBguN+vuVYTZmdd/IMXLOdfS60zAmDmFYc4BKBcMUPZH1n4tdyDECCPjXm0bNT3ZRUy1xzTLGaUje8Xyaw==}
|
||||
'@vueuse/integrations@11.0.0':
|
||||
resolution: {integrity: sha512-B95nBX4B2q2ZETBDldrKARM/fYXBHfwdo44UbHBq4bUTi25lrlc8MwAZGqEoRvdV4ND9T6O1Rb9e4kaCJFXnqw==}
|
||||
peerDependencies:
|
||||
async-validator: ^4
|
||||
axios: ^1
|
||||
change-case: ^4
|
||||
drauu: ^0.3
|
||||
change-case: ^5
|
||||
drauu: ^0.4
|
||||
focus-trap: ^7
|
||||
fuse.js: ^6
|
||||
fuse.js: ^7
|
||||
idb-keyval: ^6
|
||||
jwt-decode: ^3
|
||||
jwt-decode: ^4
|
||||
nprogress: ^0.2
|
||||
qrcode: ^1.5
|
||||
sortablejs: ^1
|
||||
universal-cookie: ^6
|
||||
universal-cookie: ^7
|
||||
peerDependenciesMeta:
|
||||
async-validator:
|
||||
optional: true
|
||||
|
@ -4270,6 +4276,9 @@ packages:
|
|||
'@vueuse/metadata@10.11.1':
|
||||
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
|
||||
|
||||
'@vueuse/metadata@11.0.0':
|
||||
resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==}
|
||||
|
||||
'@vueuse/metadata@9.13.0':
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
|
||||
|
@ -4279,6 +4288,9 @@ packages:
|
|||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
'@vueuse/shared@11.0.0':
|
||||
resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==}
|
||||
|
||||
'@vueuse/shared@9.13.0':
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
|
||||
|
@ -6916,8 +6928,8 @@ packages:
|
|||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lucide-vue-next@0.427.0:
|
||||
resolution: {integrity: sha512-zI1FhbfQ3Wl0SgPKnOWhTDC6yAC5TTjSC9FSZ61ULg3U36e+GVK+RT1qfkU9Q5BjeBuwmsHWKsXKptKMjUAwFA==}
|
||||
lucide-vue-next@0.428.0:
|
||||
resolution: {integrity: sha512-of9GJGus9VKGIUOp3yQ0uQtNv+8MRLaso8H4OiDzI6+T7TeMRXTzqVOLhnyg9fdXUnYuwE9Xm1zD1nfQ7oFPmg==}
|
||||
peerDependencies:
|
||||
vue: 3.4.38
|
||||
|
||||
|
@ -13196,6 +13208,16 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/core@11.0.0(vue@3.4.38(typescript@5.5.4))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.20
|
||||
'@vueuse/metadata': 11.0.0
|
||||
'@vueuse/shared': 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/core@9.13.0(vue@3.4.38(typescript@5.5.4))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
|
@ -13222,10 +13244,10 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/integrations@10.11.1(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))':
|
||||
'@vueuse/integrations@11.0.0(async-validator@4.2.5)(axios@1.7.4)(focus-trap@7.5.4)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.2)(vue@3.4.38(typescript@5.5.4))':
|
||||
dependencies:
|
||||
'@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
'@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.5.4))
|
||||
'@vueuse/core': 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
'@vueuse/shared': 11.0.0(vue@3.4.38(typescript@5.5.4))
|
||||
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
|
||||
optionalDependencies:
|
||||
async-validator: 4.2.5
|
||||
|
@ -13242,6 +13264,8 @@ snapshots:
|
|||
|
||||
'@vueuse/metadata@10.11.1': {}
|
||||
|
||||
'@vueuse/metadata@11.0.0': {}
|
||||
|
||||
'@vueuse/metadata@9.13.0': {}
|
||||
|
||||
'@vueuse/shared@10.11.0(vue@3.4.38(typescript@5.5.4))':
|
||||
|
@ -13258,6 +13282,13 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@11.0.0(vue@3.4.38(typescript@5.5.4))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@9.13.0(vue@3.4.38(typescript@5.5.4))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.4.38(typescript@5.5.4))
|
||||
|
@ -16250,7 +16281,7 @@ snapshots:
|
|||
dependencies:
|
||||
yallist: 4.0.0
|
||||
|
||||
lucide-vue-next@0.427.0(vue@3.4.38(typescript@5.5.4)):
|
||||
lucide-vue-next@0.428.0(vue@3.4.38(typescript@5.5.4)):
|
||||
dependencies:
|
||||
vue: 3.4.38(typescript@5.5.4)
|
||||
|
||||
|
|
Loading…
Reference in New Issue