Skip to content

Commit da3d43c

Browse files
authored
fix: Offset logic with decimal (#676)
* fix: blink of decimal * test: add test case * chore: all width
1 parent ad50b0a commit da3d43c

File tree

3 files changed

+147
-37
lines changed

3 files changed

+147
-37
lines changed

src/TabNavList/index.tsx

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import useEvent from 'rc-util/lib/hooks/useEvent';
44
import { useComposeRef } from 'rc-util/lib/ref';
55
import * as React from 'react';
66
import { useEffect, useRef, useState } from 'react';
7+
import type { GetIndicatorSize } from '../hooks/useIndicator';
8+
import useIndicator from '../hooks/useIndicator';
79
import useOffsets from '../hooks/useOffsets';
810
import useSyncState from '../hooks/useSyncState';
911
import useTouchMove from '../hooks/useTouchMove';
@@ -26,8 +28,6 @@ import AddButton from './AddButton';
2628
import ExtraContent from './ExtraContent';
2729
import OperationNode from './OperationNode';
2830
import TabNode from './TabNode';
29-
import useIndicator from '../hooks/useIndicator';
30-
import type { GetIndicatorSize } from '../hooks/useIndicator';
3131

3232
export interface TabNavListProps {
3333
id: string;
@@ -53,8 +53,31 @@ export interface TabNavListProps {
5353
indicatorSize?: GetIndicatorSize;
5454
}
5555

56+
const getTabSize = (tab: HTMLElement, containerRect: { x: number; y: number }) => {
57+
// tabListRef
58+
const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = tab;
59+
const { width, height, x, y } = tab.getBoundingClientRect();
60+
61+
// Use getBoundingClientRect to avoid decimal inaccuracy
62+
if (Math.abs(width - offsetWidth) < 1) {
63+
return [width, height, x - containerRect.x, y - containerRect.y];
64+
}
65+
66+
return [offsetWidth, offsetHeight, offsetLeft, offsetTop];
67+
};
68+
5669
const getSize = (refObj: React.RefObject<HTMLElement>): SizeInfo => {
5770
const { offsetWidth = 0, offsetHeight = 0 } = refObj.current || {};
71+
72+
// Use getBoundingClientRect to avoid decimal inaccuracy
73+
if (refObj.current) {
74+
const { width, height } = refObj.current.getBoundingClientRect();
75+
76+
if (Math.abs(width - offsetWidth) < 1) {
77+
return [width, height];
78+
}
79+
}
80+
5881
return [offsetWidth, offsetHeight];
5982
};
6083

@@ -313,14 +336,20 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
313336
const updateTabSizes = () =>
314337
setTabSizes(() => {
315338
const newSizes: TabSizeMap = new Map();
339+
const listRect = tabListRef.current?.getBoundingClientRect();
340+
316341
tabs.forEach(({ key }) => {
317-
const btnNode = tabListRef.current?.querySelector<HTMLElement>(`[data-node-key="${genDataNodeKey(key)}"]`);
342+
const btnNode = tabListRef.current?.querySelector<HTMLElement>(
343+
`[data-node-key="${genDataNodeKey(key)}"]`,
344+
);
318345
if (btnNode) {
346+
const [width, height, left, top] = getTabSize(btnNode, listRect);
347+
319348
newSizes.set(key, {
320-
width: btnNode.offsetWidth,
321-
height: btnNode.offsetHeight,
322-
left: btnNode.offsetLeft,
323-
top: btnNode.offsetTop,
349+
width,
350+
height,
351+
left,
352+
top,
324353
});
325354
}
326355
});
@@ -370,7 +399,7 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
370399
horizontal: tabPositionTopOrBottom,
371400
rtl,
372401
indicatorSize,
373-
})
402+
});
374403

375404
// ========================= Effect ========================
376405
useEffect(() => {
@@ -437,33 +466,33 @@ function TabNavList(props: TabNavListProps, ref: React.Ref<HTMLDivElement>) {
437466
ref={tabsWrapperRef}
438467
>
439468
<ResizeObserver onResize={onListHolderResize}>
440-
<div
441-
ref={tabListRef}
442-
className={`${prefixCls}-nav-list`}
443-
style={{
444-
transform: `translate(${transformLeft}px, ${transformTop}px)`,
445-
transition: lockAnimation ? 'none' : undefined,
446-
}}
447-
>
448-
{tabNodes}
449-
<AddButton
450-
ref={innerAddButtonRef}
451-
prefixCls={prefixCls}
452-
locale={locale}
453-
editable={editable}
469+
<div
470+
ref={tabListRef}
471+
className={`${prefixCls}-nav-list`}
454472
style={{
455-
...(tabNodes.length === 0 ? undefined : tabNodeStyle),
456-
visibility: hasDropdown ? 'hidden' : null,
473+
transform: `translate(${transformLeft}px, ${transformTop}px)`,
474+
transition: lockAnimation ? 'none' : undefined,
457475
}}
458-
/>
459-
460-
<div
461-
className={classNames(`${prefixCls}-ink-bar`, {
462-
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
463-
})}
464-
style={indicatorStyle}
465-
/>
466-
</div>
476+
>
477+
{tabNodes}
478+
<AddButton
479+
ref={innerAddButtonRef}
480+
prefixCls={prefixCls}
481+
locale={locale}
482+
editable={editable}
483+
style={{
484+
...(tabNodes.length === 0 ? undefined : tabNodeStyle),
485+
visibility: hasDropdown ? 'hidden' : null,
486+
}}
487+
/>
488+
489+
<div
490+
className={classNames(`${prefixCls}-ink-bar`, {
491+
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
492+
})}
493+
style={indicatorStyle}
494+
/>
495+
</div>
467496
</ResizeObserver>
468497
</div>
469498
</ResizeObserver>

tests/common/util.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-invalid-this */
22
import { act } from '@testing-library/react';
3-
import type { ReactWrapper } from 'enzyme';
43
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
54
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
65
import React from 'react';
@@ -17,6 +16,7 @@ import type { TabsProps } from '../../src/Tabs';
1716
export interface HackInfo {
1817
container?: number;
1918
tabNode?: number;
19+
tabNodeList?: number;
2020
add?: number;
2121
more?: number;
2222
extra?: number;
@@ -25,7 +25,15 @@ export interface HackInfo {
2525

2626
export function getOffsetSizeFunc(info: HackInfo = {}) {
2727
return function getOffsetSize() {
28-
const { container = 50, extra = 10, tabNode = 20, add = 10, more = 10, dropdown = 10 } = info;
28+
const {
29+
container = 50,
30+
extra = 10,
31+
tabNodeList,
32+
tabNode = 20,
33+
add = 10,
34+
more = 10,
35+
dropdown = 10,
36+
} = info;
2937

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

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

4250
if (this.classList.contains('rc-tabs-tab')) {

tests/overflow.test.tsx

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ import {
1111
getTabs,
1212
getTransformX,
1313
getTransformY,
14-
triggerResize, waitFakeTimer,
14+
triggerResize,
1515
} from './common/util';
1616

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

2020
const hackOffsetInfo: HackInfo = {};
2121

22+
let mockGetBoundingClientRect: (
23+
ele: HTMLElement,
24+
) => { x: number; y: number; width: number; height: number } | void = null;
25+
2226
beforeEach(() => {
27+
mockGetBoundingClientRect = null;
28+
2329
Object.keys(hackOffsetInfo).forEach(key => {
2430
delete hackOffsetInfo[key];
2531
});
@@ -40,6 +46,16 @@ describe('Tabs.Overflow', () => {
4046
offsetTop: {
4147
get: btnOffsetPosition,
4248
},
49+
getBoundingClientRect() {
50+
return (
51+
mockGetBoundingClientRect?.(this) || {
52+
x: 0,
53+
y: 0,
54+
width: 0,
55+
height: 0,
56+
}
57+
);
58+
},
4359
});
4460
});
4561

@@ -483,4 +499,61 @@ describe('Tabs.Overflow', () => {
483499
});
484500
expect(document.querySelector('.rc-tabs-dropdown')).toHaveClass('custom-popup');
485501
});
502+
503+
it('correct handle decimal', () => {
504+
hackOffsetInfo.container = 29;
505+
hackOffsetInfo.tabNodeList = 29;
506+
hackOffsetInfo.tabNode = 15;
507+
508+
mockGetBoundingClientRect = ele => {
509+
if (ele.classList.contains('rc-tabs-tab')) {
510+
const sharedRect = {
511+
x: 0,
512+
y: 0,
513+
width: 14.5,
514+
height: 14.5,
515+
};
516+
517+
return ele.getAttribute('data-node-key') === 'bamboo'
518+
? {
519+
...sharedRect,
520+
}
521+
: {
522+
...sharedRect,
523+
x: 14.5,
524+
};
525+
}
526+
// console.log('ele!!!', ele.className);
527+
};
528+
529+
jest.useFakeTimers();
530+
const { container } = render(
531+
getTabs({
532+
defaultActiveKey: 'little',
533+
items: [
534+
{
535+
label: 'bamboo',
536+
key: 'bamboo',
537+
children: 'Bamboo',
538+
},
539+
{
540+
label: 'little',
541+
key: 'little',
542+
children: 'Little',
543+
},
544+
],
545+
}),
546+
);
547+
548+
act(() => {
549+
jest.runAllTimers();
550+
});
551+
552+
expect(container.querySelector('.rc-tabs-nav-operations-hidden')).toBeTruthy();
553+
expect(container.querySelector('.rc-tabs-ink-bar')).toHaveStyle({
554+
left: '21.75px',
555+
});
556+
557+
jest.useRealTimers();
558+
});
486559
});

0 commit comments

Comments
 (0)