feat(@vben-core/shared): add layout scroll helpers
This commit is contained in:
@@ -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 默认命名空间
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user