chore: Replace scroll lock composable usage
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
import { onMounted, computed, ref, toRefs, useTemplateRef } from 'vue';
|
||||
import {
|
||||
onMounted,
|
||||
computed,
|
||||
ref,
|
||||
toRefs,
|
||||
useTemplateRef,
|
||||
onUnmounted,
|
||||
} from 'vue';
|
||||
import { useTimeoutFn } from '@vueuse/core';
|
||||
import { provideMessageContext } from './provider.js';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
@@ -19,7 +26,7 @@ import {
|
||||
MESSAGE_STATUS,
|
||||
CONTENT_TYPES,
|
||||
} from './constants';
|
||||
import { useTargetScrollLock } from 'dashboard/composables/useTargetScrollLock';
|
||||
import { useScrollLock } from '@vueuse/core';
|
||||
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
|
||||
@@ -131,7 +138,8 @@ const props = defineProps({
|
||||
sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties
|
||||
});
|
||||
|
||||
const conversationPanelScrollLock = useTargetScrollLock();
|
||||
const conversationPanelElement = document.querySelector('.conversation-panel');
|
||||
const conversationPanelScrollLock = useScrollLock(conversationPanelElement);
|
||||
const contextMenuPosition = ref({});
|
||||
const showBackgroundHighlight = ref(false);
|
||||
const showContextMenu = ref(false);
|
||||
@@ -371,7 +379,7 @@ const shouldRenderMessage = computed(() => {
|
||||
|
||||
function openContextMenu(e) {
|
||||
// Lock scroll to prevent scrolling when context menu is open
|
||||
conversationPanelScrollLock.lockScroll('.conversation-panel');
|
||||
conversationPanelScrollLock.value = true;
|
||||
|
||||
// Close forward modal, when opening context menu
|
||||
emailBubbleRef.value?.closeForwardModal();
|
||||
@@ -396,7 +404,7 @@ function openContextMenu(e) {
|
||||
|
||||
function closeContextMenu() {
|
||||
// Unlock scroll when context menu is closed
|
||||
conversationPanelScrollLock.unlockScroll();
|
||||
conversationPanelScrollLock.value = false;
|
||||
showContextMenu.value = false;
|
||||
contextMenuPosition.value = { x: null, y: null };
|
||||
}
|
||||
@@ -459,6 +467,9 @@ const openForwardModal = ({ x, y }) => {
|
||||
};
|
||||
|
||||
onMounted(setupHighlightTimer);
|
||||
onUnmounted(() => {
|
||||
conversationPanelScrollLock.value = false;
|
||||
});
|
||||
|
||||
provideMessageContext({
|
||||
...toRefs(props),
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<script setup>
|
||||
import { computed, useTemplateRef, ref, onMounted, reactive } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
useTemplateRef,
|
||||
onUnmounted,
|
||||
ref,
|
||||
onMounted,
|
||||
reactive,
|
||||
} from 'vue';
|
||||
import { Letter } from 'vue-letter';
|
||||
import { allowedCssProperties } from 'lettersanitizer';
|
||||
import { useWindowSize, useToggle } from '@vueuse/core';
|
||||
import { useTargetScrollLock } from 'dashboard/composables/useTargetScrollLock';
|
||||
import { useScrollLock } from '@vueuse/core';
|
||||
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { EmailQuoteExtractor } from './removeReply.js';
|
||||
@@ -35,7 +42,9 @@ 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 = useTargetScrollLock();
|
||||
const conversationPanelElement = document.querySelector('.conversation-panel');
|
||||
const conversationPanelScrollLock = useScrollLock(conversationPanelElement);
|
||||
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
|
||||
onMounted(() => {
|
||||
@@ -111,12 +120,12 @@ const handleSeeOriginal = () => {
|
||||
|
||||
const closeForwardModal = () => {
|
||||
toggleForwardModal(false);
|
||||
conversationPanelScrollLock.unlockScroll();
|
||||
conversationPanelScrollLock.value = false;
|
||||
};
|
||||
|
||||
const openForwardModal = ({ x, y }) => {
|
||||
// Lock conversation panel scroll
|
||||
conversationPanelScrollLock.lockScroll('.conversation-panel');
|
||||
conversationPanelScrollLock.value = true;
|
||||
|
||||
// Form dimensions
|
||||
const [formWidth, formHeight] = [672, 500];
|
||||
@@ -146,6 +155,10 @@ const openForwardModal = ({ x, y }) => {
|
||||
toggleForwardModal(true);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
conversationPanelScrollLock.value = false;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
openForwardModal,
|
||||
closeForwardModal,
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const getTargetElement = target => {
|
||||
if (!target) return null;
|
||||
if (typeof target === 'string') return document.querySelector(target);
|
||||
if (target instanceof HTMLElement) return target;
|
||||
return unref(target);
|
||||
};
|
||||
|
||||
export const useTargetScrollLock = defaultTarget => {
|
||||
const scrollLockInstance = shallowRef(null);
|
||||
const isLocked = ref(false);
|
||||
|
||||
// Simplified unlock function
|
||||
const unlockScroll = () => {
|
||||
if (!scrollLockInstance.value) return false;
|
||||
|
||||
scrollLockInstance.value.value = false;
|
||||
isLocked.value = false;
|
||||
scrollLockInstance.value = null;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Simplified lock function
|
||||
const lockScroll = (target = defaultTarget) => {
|
||||
unlockScroll();
|
||||
|
||||
const el = getTargetElement(target);
|
||||
if (!el) return false;
|
||||
|
||||
scrollLockInstance.value = useScrollLock(el, true);
|
||||
isLocked.value = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Auto-cleanup
|
||||
onUnmounted(unlockScroll);
|
||||
|
||||
return { lockScroll, unlockScroll, isLocked };
|
||||
};
|
||||
Reference in New Issue
Block a user