Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Offset logic with decimal #676

Merged
merged 3 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 62 additions & 33 deletions src/TabNavList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import useEvent from 'rc-util/lib/hooks/useEvent';
import { useComposeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import type { GetIndicatorSize } from '../hooks/useIndicator';
import useIndicator from '../hooks/useIndicator';
import useOffsets from '../hooks/useOffsets';
import useSyncState from '../hooks/useSyncState';
import useTouchMove from '../hooks/useTouchMove';
Expand All @@ -26,8 +28,6 @@ import AddButton from './AddButton';
import ExtraContent from './ExtraContent';
import OperationNode from './OperationNode';
import TabNode from './TabNode';
import useIndicator from '../hooks/useIndicator';
import type { GetIndicatorSize } from '../hooks/useIndicator';

export interface TabNavListProps {
id: string;
Expand All @@ -53,8 +53,31 @@ export interface TabNavListProps {
indicatorSize?: GetIndicatorSize;
}

const getTabSize = (tab: HTMLElement, containerRect: { x: number; y: number }) => {
// tabListRef
const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = tab;
const { width, height, x, y } = tab.getBoundingClientRect();

// Use getBoundingClientRect to avoid decimal inaccuracy
if (Math.abs(width - offsetWidth) < 1) {
return [width, height, x - containerRect.x, y - containerRect.y];
}

return [offsetWidth, offsetHeight, offsetLeft, offsetTop];
};

const getSize = (refObj: React.RefObject<HTMLElement>): SizeInfo => {
const { offsetWidth = 0, offsetHeight = 0 } = refObj.current || {};

// Use getBoundingClientRect to avoid decimal inaccuracy
if (refObj.current) {
const { width, height } = refObj.current.getBoundingClientRect();

if (Math.abs(width - offsetWidth) < 1) {
return [width, height];
}
}

return [offsetWidth, offsetHeight];
};

Expand Down Expand Up @@ -313,14 +336,20 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
const updateTabSizes = () =>
setTabSizes(() => {
const newSizes: TabSizeMap = new Map();
const listRect = tabListRef.current?.getBoundingClientRect();

tabs.forEach(({ key }) => {
const btnNode = tabListRef.current?.querySelector<HTMLElement>(`[data-node-key="${genDataNodeKey(key)}"]`);
const btnNode = tabListRef.current?.querySelector<HTMLElement>(
`[data-node-key="${genDataNodeKey(key)}"]`,
);
if (btnNode) {
const [width, height, left, top] = getTabSize(btnNode, listRect);

newSizes.set(key, {
width: btnNode.offsetWidth,
height: btnNode.offsetHeight,
left: btnNode.offsetLeft,
top: btnNode.offsetTop,
width,
height,
left,
top,
});
}
});
Expand Down Expand Up @@ -370,7 +399,7 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
horizontal: tabPositionTopOrBottom,
rtl,
indicatorSize,
})
});

// ========================= Effect ========================
useEffect(() => {
Expand Down Expand Up @@ -437,33 +466,33 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
ref={tabsWrapperRef}
>
<ResizeObserver onResize={onListHolderResize}>
<div
ref={tabListRef}
className={`${prefixCls}-nav-list`}
style={{
transform: `translate(${transformLeft}px, ${transformTop}px)`,
transition: lockAnimation ? 'none' : undefined,
}}
>
{tabNodes}
<AddButton
ref={innerAddButtonRef}
prefixCls={prefixCls}
locale={locale}
editable={editable}
<div
ref={tabListRef}
className={`${prefixCls}-nav-list`}
style={{
...(tabNodes.length === 0 ? undefined : tabNodeStyle),
visibility: hasDropdown ? 'hidden' : null,
transform: `translate(${transformLeft}px, ${transformTop}px)`,
transition: lockAnimation ? 'none' : undefined,
}}
/>

<div
className={classNames(`${prefixCls}-ink-bar`, {
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
})}
style={indicatorStyle}
/>
</div>
>
{tabNodes}
<AddButton
ref={innerAddButtonRef}
prefixCls={prefixCls}
locale={locale}
editable={editable}
style={{
...(tabNodes.length === 0 ? undefined : tabNodeStyle),
visibility: hasDropdown ? 'hidden' : null,
}}
/>

<div
className={classNames(`${prefixCls}-ink-bar`, {
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
})}
style={indicatorStyle}
/>
</div>
</ResizeObserver>
</div>
</ResizeObserver>
Expand Down
14 changes: 11 additions & 3 deletions tests/common/util.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-invalid-this */
import { act } from '@testing-library/react';
import type { ReactWrapper } from 'enzyme';
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
import React from 'react';
Expand All @@ -17,6 +16,7 @@ import type { TabsProps } from '../../src/Tabs';
export interface HackInfo {
container?: number;
tabNode?: number;
tabNodeList?: number;
add?: number;
more?: number;
extra?: number;
Expand All @@ -25,7 +25,15 @@ export interface HackInfo {

export function getOffsetSizeFunc(info: HackInfo = {}) {
return function getOffsetSize() {
const { container = 50, extra = 10, tabNode = 20, add = 10, more = 10, dropdown = 10 } = info;
const {
container = 50,
extra = 10,
tabNodeList,
tabNode = 20,
add = 10,
more = 10,
dropdown = 10,
} = info;

if (this.classList.contains('rc-tabs-nav')) {
return container;
Expand All @@ -36,7 +44,7 @@ export function getOffsetSizeFunc(info: HackInfo = {}) {
}

if (this.classList.contains('rc-tabs-nav-list')) {
return this.querySelectorAll('.rc-tabs-tab').length * tabNode + add;
return tabNodeList || this.querySelectorAll('.rc-tabs-tab').length * tabNode + add;
}

if (this.classList.contains('rc-tabs-tab')) {
Expand Down
75 changes: 74 additions & 1 deletion tests/overflow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,21 @@ import {
getTabs,
getTransformX,
getTransformY,
triggerResize, waitFakeTimer,
triggerResize,
} from './common/util';

describe('Tabs.Overflow', () => {
let domSpy: ReturnType<typeof spyElementPrototypes>;

const hackOffsetInfo: HackInfo = {};

let mockGetBoundingClientRect: (
ele: HTMLElement,
) => { x: number; y: number; width: number; height: number } | void = null;

beforeEach(() => {
mockGetBoundingClientRect = null;

Object.keys(hackOffsetInfo).forEach(key => {
delete hackOffsetInfo[key];
});
Expand All @@ -40,6 +46,16 @@ describe('Tabs.Overflow', () => {
offsetTop: {
get: btnOffsetPosition,
},
getBoundingClientRect() {
return (
mockGetBoundingClientRect?.(this) || {
x: 0,
y: 0,
width: 0,
height: 0,
}
);
},
});
});

Expand Down Expand Up @@ -483,4 +499,61 @@ describe('Tabs.Overflow', () => {
});
expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('custom-popup');
});

it('correct handle decimal', () => {
hackOffsetInfo.container = 29;
hackOffsetInfo.tabNodeList = 29;
hackOffsetInfo.tabNode = 15;

mockGetBoundingClientRect = ele => {
if (ele.classList.contains('rc-tabs-tab')) {
const sharedRect = {
x: 0,
y: 0,
width: 14.5,
height: 14.5,
};

return ele.getAttribute('data-node-key') === 'bamboo'
? {
...sharedRect,
}
: {
...sharedRect,
x: 14.5,
};
}
// console.log('ele!!!', ele.className);
};

jest.useFakeTimers();
const { container } = render(
getTabs({
defaultActiveKey: 'little',
items: [
{
label: 'bamboo',
key: 'bamboo',
children: 'Bamboo',
},
{
label: 'little',
key: 'little',
children: 'Little',
},
],
}),
);

act(() => {
jest.runAllTimers();
});

expect(container.querySelector('.rc-tabs-nav-operations-hidden')).toBeTruthy();
expect(container.querySelector('.rc-tabs-ink-bar')).toHaveStyle({
left: '21.75px',
});

jest.useRealTimers();
});
});
Loading