Skip to content

Commit

Permalink
fix: improve the scroll bar flashing when the modal box is opened (#4438
Browse files Browse the repository at this point in the history
)
  • Loading branch information
anncwb authored Sep 19, 2024
1 parent 56bdb8f commit 161820d
Show file tree
Hide file tree
Showing 18 changed files with 530 additions and 42 deletions.
16 changes: 16 additions & 0 deletions packages/@core/base/shared/src/utils/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,19 @@ export function getScrollbarWidth() {
scrollDiv.remove();
return scrollbarWidth;
}

export function needsScrollbar() {
const doc = document.documentElement;
const body = document.body;

// 检查 body 的 overflow-y 样式
const overflowY = window.getComputedStyle(body).overflowY;

// 如果明确设置了需要滚动条的样式
if (overflowY === 'scroll' || overflowY === 'auto') {
return doc.scrollHeight > window.innerHeight;
}

// 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
return doc.scrollHeight > window.innerHeight;
}
8 changes: 7 additions & 1 deletion packages/@core/composables/src/use-scroll-lock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getScrollbarWidth } from '@vben-core/shared/utils';
import { getScrollbarWidth, needsScrollbar } from '@vben-core/shared/utils';

import {
useScrollLock as _useScrollLock,
Expand All @@ -13,6 +13,9 @@ export function useScrollLock() {
const scrollbarWidth = getScrollbarWidth();

tryOnBeforeMount(() => {
if (!needsScrollbar()) {
return;
}
document.body.style.paddingRight = `${scrollbarWidth}px`;

const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
Expand All @@ -30,6 +33,9 @@ export function useScrollLock() {
});

tryOnBeforeUnmount(() => {
if (!needsScrollbar()) {
return;
}
isLocked.value = false;
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
`.${SCROLL_FIXED_CLASS}`,
Expand Down
114 changes: 114 additions & 0 deletions packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`defaultPreferences immutability test > should not modify the config object 1`] = `
{
"app": {
"accessMode": "frontend",
"authPageLayout": "panel-right",
"checkUpdatesInterval": 1,
"colorGrayMode": false,
"colorWeakMode": false,
"compact": false,
"contentCompact": "wide",
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/avatar-v1.webp",
"dynamicTitle": true,
"enableCheckUpdates": true,
"enablePreferences": true,
"enableRefreshToken": false,
"isMobile": false,
"layout": "sidebar-nav",
"locale": "zh-CN",
"loginExpiredMode": "page",
"name": "Vben Admin",
"preferencesButtonPosition": "auto",
"watermark": false,
},
"breadcrumb": {
"enable": true,
"hideOnlyOne": false,
"showHome": false,
"showIcon": true,
"styleType": "normal",
},
"copyright": {
"companyName": "Vben",
"companySiteLink": "https://www.vben.pro",
"date": "2024",
"enable": true,
"icp": "",
"icpLink": "",
},
"footer": {
"enable": true,
"fixed": false,
},
"header": {
"enable": true,
"hidden": false,
"mode": "fixed",
},
"logo": {
"enable": true,
"source": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp",
},
"navigation": {
"accordion": true,
"split": true,
"styleType": "rounded",
},
"shortcutKeys": {
"enable": true,
"globalLockScreen": true,
"globalLogout": true,
"globalPreferences": true,
"globalSearch": true,
},
"sidebar": {
"collapsed": false,
"collapsedShowTitle": false,
"enable": true,
"expandOnHover": true,
"extraCollapse": true,
"hidden": false,
"width": 224,
},
"tabbar": {
"dragable": true,
"enable": true,
"height": 38,
"keepAlive": true,
"persist": true,
"showIcon": true,
"showMaximize": true,
"showMore": true,
"showRefresh": true,
"styleType": "chrome",
},
"theme": {
"builtinType": "default",
"colorDestructive": "hsl(348 100% 61%)",
"colorPrimary": "hsl(212 100% 45%)",
"colorSuccess": "hsl(144 57% 58%)",
"colorWarning": "hsl(42 84% 61%)",
"mode": "dark",
"radius": "0.5",
"semiDarkHeader": false,
"semiDarkSidebar": true,
},
"transition": {
"enable": true,
"loading": true,
"name": "fade-slide",
"progress": true,
},
"widget": {
"fullscreen": true,
"globalSearch": true,
"languageToggle": true,
"lockScreen": true,
"notification": true,
"sidebarToggle": true,
"themeToggle": true,
},
}
`;
10 changes: 10 additions & 0 deletions packages/@core/preferences/__tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest';

import { defaultPreferences } from '../src/config';

describe('defaultPreferences immutability test', () => {
// 创建快照,确保默认配置对象不被修改
it('should not modify the config object', () => {
expect(defaultPreferences).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { defaultPreferences } from './config';
import { PreferenceManager } from './preferences';
import { isDarkTheme } from './update-css-variables';
import { defaultPreferences } from '../src/config';
import { PreferenceManager } from '../src/preferences';
import { isDarkTheme } from '../src/update-css-variables';

describe('preferences', () => {
let preferenceManager: PreferenceManager;
Expand Down
2 changes: 1 addition & 1 deletion packages/@core/preferences/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"include": ["src", "__tests__"],
"exclude": ["node_modules"]
}
146 changes: 146 additions & 0 deletions packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 假设这个文件为 FormApi.ts
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { FormApi } from '../src/form-api';

vi.mock('@vben-core/shared/utils', () => ({
bindMethods: vi.fn(),
createMerge: vi.fn((mergeFn) => {
return (stateOrFn, prev) => {
mergeFn(prev, 'key', stateOrFn);
return { ...prev, ...stateOrFn };
};
}),
isFunction: (fn: any) => typeof fn === 'function',
StateHandler: vi.fn().mockImplementation(() => ({
reset: vi.fn(),
setConditionTrue: vi.fn(),
waitForCondition: vi.fn().mockResolvedValue(true),
})),
}));

describe('formApi', () => {
let formApi: FormApi;

beforeEach(() => {
formApi = new FormApi();
});

it('should initialize with default state', () => {
expect(formApi.state).toEqual(
expect.objectContaining({
actionWrapperClass: '',
collapsed: false,
collapsedRows: 1,
commonConfig: {},
handleReset: undefined,
handleSubmit: undefined,
layout: 'horizontal',
resetButtonOptions: {},
schema: [],
showCollapseButton: false,
showDefaultActions: true,
submitButtonOptions: {},
wrapperClass: 'grid-cols-1',
}),
);
expect(formApi.isMounted).toBe(false);
});

it('should mount form actions', async () => {
const formActions: any = {
meta: {},
resetForm: vi.fn(),
setFieldValue: vi.fn(),
setValues: vi.fn(),
submitForm: vi.fn(),
validate: vi.fn(),
values: { name: 'test' },
};

await formApi.mount(formActions);
expect(formApi.isMounted).toBe(true);
expect(formApi.form).toEqual(formActions);
});

it('should get values from form', async () => {
const formActions: any = {
meta: {},
values: { name: 'test' },
};

await formApi.mount(formActions);
const values = await formApi.getValues();
expect(values).toEqual({ name: 'test' });
});

it('should set field value', async () => {
const setFieldValueMock = vi.fn();
const formActions: any = {
meta: {},
setFieldValue: setFieldValueMock,
values: { name: 'test' },
};

await formApi.mount(formActions);
await formApi.setFieldValue('name', 'new value');
expect(setFieldValueMock).toHaveBeenCalledWith(
'name',
'new value',
undefined,
);
});

it('should reset form', async () => {
const resetFormMock = vi.fn();
const formActions: any = {
meta: {},
resetForm: resetFormMock,
values: { name: 'test' },
};

await formApi.mount(formActions);
await formApi.resetForm();
expect(resetFormMock).toHaveBeenCalled();
});

it('should call handleSubmit on submit', async () => {
const handleSubmitMock = vi.fn();
const formActions: any = {
meta: {},
submitForm: vi.fn().mockResolvedValue(true),
values: { name: 'test' },
};

const state = {
handleSubmit: handleSubmitMock,
};

formApi.setState(state);
await formApi.mount(formActions);

const result = await formApi.submitForm();
expect(formActions.submitForm).toHaveBeenCalled();
expect(handleSubmitMock).toHaveBeenCalledWith({ name: 'test' });
expect(result).toEqual({ name: 'test' });
});

it('should unmount form and reset state', () => {
formApi.unmounted();
expect(formApi.isMounted).toBe(false);
expect(formApi.stateHandler.reset).toHaveBeenCalled();
});

it('should validate form', async () => {
const validateMock = vi.fn().mockResolvedValue(true);
const formActions: any = {
meta: {},
validate: validateMock,
};

await formApi.mount(formActions);
const isValid = await formApi.validate();
expect(validateMock).toHaveBeenCalled();
expect(isValid).toBe(true);
});
});
10 changes: 7 additions & 3 deletions packages/@core/ui-kit/form-ui/src/form-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ function getDefaultState(): VbenFormProps {
}

export class FormApi {
// private prevState!: ModalState;
private state: null | VbenFormProps = null;
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
public form = {} as FormActions;

isMounted = false;

// private prevState!: ModalState;
public state: null | VbenFormProps = null;

stateHandler: StateHandler;

public store: Store<VbenFormProps>;
Expand Down Expand Up @@ -92,6 +92,10 @@ export class FormApi {
this.store.batch(cb);
}

getState() {
return this.state;
}

async getValues() {
const form = await this.getForm();
return form.values;
Expand Down
2 changes: 1 addition & 1 deletion packages/@core/ui-kit/form-ui/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"include": ["src", "__tests__"],
"exclude": ["node_modules"]
}
3 changes: 2 additions & 1 deletion playground/src/locales/langs/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
"query": "Query Form",
"rules": "Form Rules",
"dynamic": "Dynamic Form",
"custom": "Custom Component"
"custom": "Custom Component",
"api": "Api"
},
"captcha": {
"title": "Captcha",
Expand Down
3 changes: 2 additions & 1 deletion playground/src/locales/langs/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
"query": "查询表单",
"rules": "表单校验",
"dynamic": "动态表单",
"custom": "自定义组件"
"custom": "自定义组件",
"api": "Api"
},
"captcha": {
"title": "验证码",
Expand Down
Loading

0 comments on commit 161820d

Please sign in to comment.