feat(@vben-core/shared): add layout scroll helpers

This commit is contained in:
Dream
2026-07-24 16:55:36 +08:00
parent dcdbfe5dfc
commit baea741432
3 changed files with 105 additions and 3 deletions

View File

@@ -9,6 +9,8 @@ export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;
/** 内容区域的组件ID */
export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;
/** layout 滚动容器ID */
export const ELEMENT_ID_LAYOUT_SCROLL = `__vben_layout_scroll`;
/**
* @zh_CN 默认命名空间

View File

@@ -1,6 +1,11 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { getElementVisibleRect } from '../dom';
import { ELEMENT_ID_LAYOUT_SCROLL } from '../../constants';
import {
getElementVisibleRect,
getLayoutScrollElement,
needsScrollbar,
} from '../dom';
describe('getElementVisibleRect', () => {
// 设置浏览器视口尺寸的 mock
@@ -15,6 +20,10 @@ describe('getElementVisibleRect', () => {
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1000);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should return default rect if element is undefined', () => {
expect(getElementVisibleRect()).toEqual({
bottom: 0,
@@ -125,3 +134,75 @@ describe('getElementVisibleRect', () => {
});
});
});
describe('getLayoutScrollElement', () => {
afterEach(() => {
document.body.innerHTML = '';
});
it('should return the layout scroll element', () => {
const element = document.createElement('div');
element.id = ELEMENT_ID_LAYOUT_SCROLL;
document.body.append(element);
expect(getLayoutScrollElement()).toBe(element);
});
it('should return null when the layout scroll element is missing', () => {
expect(getLayoutScrollElement()).toBeNull();
});
});
describe('needsScrollbar', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should check scrollbar state from target element', () => {
const element = document.createElement('div');
vi.spyOn(element, 'clientHeight', 'get').mockReturnValue(100);
vi.spyOn(element, 'scrollHeight', 'get').mockReturnValue(120);
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
overflowY: 'auto',
} as CSSStyleDeclaration);
expect(needsScrollbar(element)).toBe(true);
});
it('should return false when target content does not overflow', () => {
const element = document.createElement('div');
vi.spyOn(element, 'clientHeight', 'get').mockReturnValue(100);
vi.spyOn(element, 'scrollHeight', 'get').mockReturnValue(100);
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
overflowY: 'auto',
} as CSSStyleDeclaration);
expect(needsScrollbar(element)).toBe(false);
});
it.each(['clip', 'hidden'])(
'should ignore %s overflow targets',
(overflowY) => {
const element = document.createElement('div');
vi.spyOn(element, 'clientHeight', 'get').mockReturnValue(100);
vi.spyOn(element, 'scrollHeight', 'get').mockReturnValue(120);
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
overflowY,
} as CSSStyleDeclaration);
expect(needsScrollbar(element)).toBe(false);
},
);
it('should fall back to document scrollbar state', () => {
vi.spyOn(document.documentElement, 'scrollHeight', 'get').mockReturnValue(
120,
);
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(100);
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
overflowY: 'auto',
} as CSSStyleDeclaration);
expect(needsScrollbar()).toBe(true);
});
});

View File

@@ -1,3 +1,5 @@
import { ELEMENT_ID_LAYOUT_SCROLL } from '../constants/globals';
export interface VisibleDomRect {
bottom: number;
height: number;
@@ -82,7 +84,24 @@ export function getScrollbarWidth() {
return scrollbarWidth;
}
export function needsScrollbar() {
export function getLayoutScrollElement() {
return document.querySelector<HTMLElement>(`#${ELEMENT_ID_LAYOUT_SCROLL}`);
}
function elementNeedsScrollbar(element: HTMLElement) {
const overflowY = window.getComputedStyle(element).overflowY;
if (overflowY === 'hidden' || overflowY === 'clip') {
return false;
}
return element.scrollHeight > element.clientHeight;
}
export function needsScrollbar(target?: HTMLElement | null) {
if (target) {
return elementNeedsScrollbar(target);
}
const doc = document.documentElement;
const body = document.body;