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 */
|
/** 内容区域的组件ID */
|
||||||
export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;
|
export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;
|
||||||
|
/** layout 滚动容器ID */
|
||||||
|
export const ELEMENT_ID_LAYOUT_SCROLL = `__vben_layout_scroll`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 默认命名空间
|
* @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', () => {
|
describe('getElementVisibleRect', () => {
|
||||||
// 设置浏览器视口尺寸的 mock
|
// 设置浏览器视口尺寸的 mock
|
||||||
@@ -15,6 +20,10 @@ describe('getElementVisibleRect', () => {
|
|||||||
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1000);
|
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('should return default rect if element is undefined', () => {
|
it('should return default rect if element is undefined', () => {
|
||||||
expect(getElementVisibleRect()).toEqual({
|
expect(getElementVisibleRect()).toEqual({
|
||||||
bottom: 0,
|
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 {
|
export interface VisibleDomRect {
|
||||||
bottom: number;
|
bottom: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -82,7 +84,24 @@ export function getScrollbarWidth() {
|
|||||||
return scrollbarWidth;
|
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 doc = document.documentElement;
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user