chore: Minor fix

This commit is contained in:
iamsivin
2025-05-09 17:31:20 +05:30
parent e722bebed8
commit be10e450e3
5 changed files with 169 additions and 5 deletions
@@ -19,7 +19,7 @@ import {
MESSAGE_STATUS,
CONTENT_TYPES,
} from './constants';
import { useGlobalScrollLock } from 'dashboard/composables/useGlobalScrollLock';
import { useTargetScrollLock } from 'dashboard/composables/useTargetScrollLock';
import Avatar from 'next/avatar/Avatar.vue';
@@ -131,7 +131,7 @@ const props = defineProps({
sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties
});
const conversationPanelScrollLock = useGlobalScrollLock();
const conversationPanelScrollLock = useTargetScrollLock();
const contextMenuPosition = ref({});
const showBackgroundHighlight = ref(false);
const showContextMenu = ref(false);
@@ -3,7 +3,7 @@ import { computed, useTemplateRef, ref, onMounted, reactive } from 'vue';
import { Letter } from 'vue-letter';
import { allowedCssProperties } from 'lettersanitizer';
import { useWindowSize, useToggle } from '@vueuse/core';
import { useGlobalScrollLock } from 'dashboard/composables/useGlobalScrollLock';
import { useTargetScrollLock } from 'dashboard/composables/useTargetScrollLock';
import Icon from 'next/icon/Icon.vue';
import { EmailQuoteExtractor } from './removeReply.js';
@@ -34,7 +34,7 @@ const contentContainer = useTemplateRef('contentContainer');
// Forward form - managed locally but can be triggered by parent
const [showForwardMessageModal, toggleForwardModal] = useToggle();
const forwardFormPosition = reactive({ top: 0, right: 0 });
const conversationPanelScrollLock = useGlobalScrollLock();
const conversationPanelScrollLock = useTargetScrollLock();
const { width: windowWidth, height: windowHeight } = useWindowSize();
onMounted(() => {
@@ -0,0 +1,116 @@
import { ref } from 'vue';
import { useTargetScrollLock } from '../useTargetScrollLock';
import { useScrollLock } from '@vueuse/core';
import { describe, beforeEach, test, expect, vi } from 'vitest';
vi.mock('@vueuse/core', () => ({
useScrollLock: vi.fn(),
}));
vi.mock('vue', async () => {
const actual = await vi.importActual('vue');
return {
...actual,
onUnmounted: vi.fn(fn => {
vi.fn.unmountCallback = fn;
}),
};
});
describe('useTargetScrollLock', () => {
let mockLockRef;
beforeEach(() => {
document.body.innerHTML = `
<div class="conversation-panel"></div>
<div id="modal"></div>
`;
mockLockRef = ref(true);
useScrollLock.mockReturnValue(mockLockRef);
vi.clearAllMocks();
});
test('provides the expected API', () => {
const api = useTargetScrollLock();
expect(api).toHaveProperty('lockScroll');
expect(api).toHaveProperty('unlockScroll');
expect(api).toHaveProperty('isLocked');
expect(api.isLocked.value).toBe(false);
});
test('handles various target types', () => {
const { lockScroll } = useTargetScrollLock();
lockScroll('.conversation-panel');
expect(useScrollLock).toHaveBeenLastCalledWith(
document.querySelector('.conversation-panel'),
true
);
const element = document.querySelector('#modal');
lockScroll(element);
expect(useScrollLock).toHaveBeenLastCalledWith(element, true);
const elementRef = ref(element);
lockScroll(elementRef);
expect(useScrollLock).toHaveBeenLastCalledWith(element, true);
});
test('handles default target parameter', () => {
const defaultEl = document.querySelector('.conversation-panel');
const { lockScroll } = useTargetScrollLock(defaultEl);
lockScroll();
expect(useScrollLock).toHaveBeenCalledWith(defaultEl, true);
});
test('fails gracefully with invalid targets', () => {
const { lockScroll, isLocked } = useTargetScrollLock();
expect(lockScroll('.doesnt-exist')).toBe(false);
expect(isLocked.value).toBe(false);
expect(lockScroll(null)).toBe(false);
expect(isLocked.value).toBe(false);
expect(useScrollLock).not.toHaveBeenCalled();
});
test('manages lock state correctly', () => {
const { lockScroll, unlockScroll, isLocked } = useTargetScrollLock();
expect(isLocked.value).toBe(false);
lockScroll('.conversation-panel');
expect(isLocked.value).toBe(true);
unlockScroll();
expect(isLocked.value).toBe(false);
expect(mockLockRef.value).toBe(false);
});
test('handles lock switching correctly', () => {
const { lockScroll } = useTargetScrollLock();
lockScroll('.conversation-panel');
lockScroll('#modal');
expect(mockLockRef.value).toBe(false);
expect(useScrollLock).toHaveBeenCalledTimes(2);
});
test('cleans up on component unmount', () => {
const { lockScroll, isLocked } = useTargetScrollLock();
lockScroll('.conversation-panel');
expect(isLocked.value).toBe(true);
if (vi.fn.unmountCallback) {
vi.fn.unmountCallback();
}
expect(isLocked.value).toBe(false);
});
});
@@ -0,0 +1,48 @@
/**
* useTargetScrollLock composable
*
* Usage:
* const { lockScroll, unlockScroll, isLocked } = useTargetScrollLock(target);
* target can be a ref, DOM element, or selector string (class, id, etc)
* eg: lockScroll('.conversation-panel'); unlockScroll();
*/
import { ref, unref, shallowRef, onUnmounted } from 'vue';
import { useScrollLock } from '@vueuse/core';
export function useTargetScrollLock(defaultTarget) {
const scrollLockInstance = shallowRef(null);
const isLocked = ref(false);
function resolveTarget(target) {
if (!target) return null;
if (typeof target === 'string') return document.querySelector(target); // class, id, etc
if (target instanceof HTMLElement) return target; // DOM element
return unref(target); // ref
}
function unlockScroll() {
if (!scrollLockInstance.value) return false;
scrollLockInstance.value.value = false;
isLocked.value = false;
scrollLockInstance.value = null;
return true;
}
function lockScroll(target = defaultTarget) {
unlockScroll(); // Always unlock first
const el = resolveTarget(target);
if (!el) return false;
scrollLockInstance.value = useScrollLock(el, true);
isLocked.value = true;
return true;
}
onUnmounted(() => {
unlockScroll();
});
return { lockScroll, unlockScroll, isLocked };
}
@@ -116,11 +116,11 @@ export default {
this.$emit('close', e);
},
openForwardModal() {
this.handleClose();
this.$emit('forwardEmail', {
x: this.contextMenuPosition.x,
y: this.contextMenuPosition.y,
});
this.handleClose();
},
handleTranslate() {
const { locale } = this.getAccount(this.currentAccountId);