chore: remove tab drag sortable runtime

This commit is contained in:
2026-06-11 09:45:24 +08:00
parent a4f40b1125
commit d652fc2bbd
8 changed files with 2 additions and 231 deletions
-1
View File
@@ -62,7 +62,6 @@
"@vue-flow/minimap": "^1.5.4", "@vue-flow/minimap": "^1.5.4",
"dompurify": "^3.3.1", "dompurify": "^3.3.1",
"marked": "^17.0.1", "marked": "^17.0.1",
"sortablejs": "catalog:",
"tippy.js": "catalog:", "tippy.js": "catalog:",
"turndown": "^7.2.2" "turndown": "^7.2.2"
}, },
@@ -37,10 +37,6 @@
"dependencies": { "dependencies": {
"@vben-core/shared": "workspace:*", "@vben-core/shared": "workspace:*",
"@vueuse/core": "catalog:", "@vueuse/core": "catalog:",
"sortablejs": "catalog:",
"vue": "catalog:" "vue": "catalog:"
},
"devDependencies": {
"@types/sortablejs": "catalog:"
} }
} }
@@ -1,47 +0,0 @@
import type { SortableOptions } from 'sortablejs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useSortable } from '../use-sortable';
describe('useSortable', () => {
beforeEach(() => {
vi.mock('sortablejs/modular/sortable.complete.esm.js', () => ({
default: {
create: vi.fn(),
},
}));
});
it('should call Sortable.create with the correct options', async () => {
// Create a mock element
const mockElement = document.createElement('div') as HTMLDivElement;
// Define custom options
const customOptions: SortableOptions = {
group: 'test-group',
sort: false,
};
// Use the useSortable function
const { initializeSortable } = useSortable(mockElement, customOptions);
// Initialize sortable
await initializeSortable();
// Import sortablejs to access the mocked create function
const Sortable =
await import('sortablejs/modular/sortable.complete.esm.js');
// Verify that Sortable.create was called with the correct parameters
expect(Sortable.default.create).toHaveBeenCalledTimes(1);
expect(Sortable.default.create).toHaveBeenCalledWith(
mockElement,
expect.objectContaining({
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...customOptions,
}),
);
});
});
@@ -5,4 +5,3 @@ export * from './use-namespace';
export * from './use-priority-value'; export * from './use-priority-value';
export * from './use-scroll-lock'; export * from './use-scroll-lock';
export * from './use-simple-locale'; export * from './use-simple-locale';
export * from './use-sortable';
@@ -1,29 +0,0 @@
import type { SortableOptions } from 'sortablejs';
import type Sortable from 'sortablejs';
function useSortable<T extends HTMLElement>(
sortableContainer: T,
options: SortableOptions = {},
) {
const initializeSortable = async () => {
const Sortable = await import(
// @ts-expect-error - This is a dynamic import
'sortablejs/modular/sortable.complete.esm.js'
);
const sortable = Sortable?.default?.create?.(sortableContainer, {
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...options,
});
return sortable as Sortable;
};
return {
initializeSortable,
};
}
export { useSortable };
export type { Sortable };
@@ -6,7 +6,6 @@ import { ChevronLeft, ChevronRight } from '@vben-core/icons';
import { VbenScrollbar } from '@vben-core/shadcn-ui'; import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { Tabs, TabsChrome } from './components'; import { Tabs, TabsChrome } from './components';
import { useTabsDrag } from './use-tabs-drag';
import { useTabsViewScroll } from './use-tabs-view-scroll'; import { useTabsViewScroll } from './use-tabs-view-scroll';
interface Props extends TabsProps {} interface Props extends TabsProps {}
@@ -43,8 +42,6 @@ function onWheel(e: WheelEvent) {
e.preventDefault(); e.preventDefault();
} }
} }
useTabsDrag(props, emit);
</script> </script>
<template> <template>
@@ -1,124 +0,0 @@
import type { Sortable } from '@vben-core/composables';
import type { EmitType } from '@vben-core/typings';
import type { TabsProps } from './types';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { useIsMobile, 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 draggable = parent?.classList.contains('draggable');
return !draggable || !props.draggable;
},
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('draggable')) {
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);
if (parent?.classList.contains('draggable') && props.draggable) {
const isCurrentAffix = evt.dragged.classList.contains('affix-tab');
const isRelatedAffix = evt.related.classList.contains('affix-tab');
// 不允许在固定的tab和非固定的tab之间互相拖拽
return isCurrentAffix === isRelatedAffix;
} else {
return false;
}
},
onStart: () => {
el.style.cursor = 'grabbing';
el.querySelector('.draggable')?.classList.add('dragging');
// el.classList.add('dragging');
},
});
sortableInstance.value = await initializeSortable();
}
async function init() {
const { isMobile } = useIsMobile();
// 移动端下tab不需要拖拽
if (isMobile.value) {
return;
}
await nextTick();
initTabsSortable();
}
onMounted(init);
watch(
() => props.styleType,
() => {
sortableInstance.value?.destroy();
init();
},
);
onUnmounted(() => {
sortableInstance.value?.destroy();
});
}
+2 -22
View File
@@ -105,9 +105,6 @@ catalogs:
'@types/qs': '@types/qs':
specifier: ^6.14.0 specifier: ^6.14.0
version: 6.14.0 version: 6.14.0
'@types/sortablejs':
specifier: ^1.15.8
version: 1.15.9
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^8.35.1 specifier: ^8.35.1
version: 8.48.0 version: 8.48.0
@@ -375,9 +372,6 @@ catalogs:
sass: sass:
specifier: ^1.89.2 specifier: ^1.89.2
version: 1.94.2 version: 1.94.2
sortablejs:
specifier: 1.15.6
version: 1.15.6
stylelint: stylelint:
specifier: ^16.21.0 specifier: ^16.21.0
version: 16.26.1 version: 16.26.1
@@ -532,9 +526,6 @@ importers:
marked: marked:
specifier: ^17.0.1 specifier: ^17.0.1
version: 17.0.1 version: 17.0.1
sortablejs:
specifier: 'catalog:'
version: 1.15.6
tippy.js: tippy.js:
specifier: 'catalog:' specifier: 'catalog:'
version: 6.3.7 version: 6.3.7
@@ -1196,16 +1187,9 @@ importers:
'@vueuse/core': '@vueuse/core':
specifier: 'catalog:' specifier: 'catalog:'
version: 13.4.0(vue@3.5.25(typescript@5.9.3)) version: 13.4.0(vue@3.5.25(typescript@5.9.3))
sortablejs:
specifier: 'catalog:'
version: 1.15.6
vue: vue:
specifier: ^3.5.17 specifier: ^3.5.17
version: 3.5.25(typescript@5.9.3) version: 3.5.25(typescript@5.9.3)
devDependencies:
'@types/sortablejs':
specifier: 'catalog:'
version: 1.15.9
packages/@core/preferences: packages/@core/preferences:
dependencies: dependencies:
@@ -4334,9 +4318,6 @@ packages:
'@types/resolve@1.20.2': '@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/sortablejs@1.15.9':
resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==}
'@types/trusted-types@2.0.7': '@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
@@ -13775,8 +13756,6 @@ snapshots:
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
'@types/sortablejs@1.15.9': {}
'@types/trusted-types@2.0.7': {} '@types/trusted-types@2.0.7': {}
'@types/turndown@5.0.6': {} '@types/turndown@5.0.6': {}
@@ -19318,7 +19297,8 @@ snapshots:
smob@1.5.0: {} smob@1.5.0: {}
sortablejs@1.15.6: {} sortablejs@1.15.6:
optional: true
source-map-js@1.2.1: {} source-map-js@1.2.1: {}