Skip to content

Commit fa12080

Browse files
ciampotyxlaDaniGuardiola
authored
Tabs: update indicator more reactively (#66207)
Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: tyxla <tyxla@git.wordpress.org> Co-authored-by: DaniGuardiola <daniguardiola@git.wordpress.org>
1 parent 240180a commit fa12080

File tree

3 files changed

+37
-13
lines changed

3 files changed

+37
-13
lines changed

packages/components/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)).
1111
- `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)).
1212
- `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)).
13+
- `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)).
1314

1415
### Enhancements
1516

packages/components/src/tabs/tablist.tsx

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* External dependencies
33
*/
4-
import { useStoreState } from '@ariakit/react';
4+
import * as Ariakit from '@ariakit/react';
5+
import clsx from 'clsx';
56

67
/**
78
* WordPress dependencies
@@ -14,11 +15,10 @@ import { useMergeRefs } from '@wordpress/compose';
1415
* Internal dependencies
1516
*/
1617
import type { TabListProps } from './types';
17-
import { useTabsContext } from './context';
18-
import { StyledTabList } from './styles';
1918
import type { WordPressComponentProps } from '../context';
20-
import clsx from 'clsx';
2119
import type { ElementOffsetRect } from '../utils/element-rect';
20+
import { useTabsContext } from './context';
21+
import { StyledTabList } from './styles';
2222
import { useTrackElementOffsetRect } from '../utils/element-rect';
2323
import { useTrackOverflow } from './use-track-overflow';
2424
import { useAnimatedOffsetRect } from '../utils/hooks/use-animated-offset-rect';
@@ -62,15 +62,25 @@ export const TabList = forwardRef<
6262
>( function TabList( { children, ...otherProps }, ref ) {
6363
const { store } = useTabsContext() ?? {};
6464

65-
const selectedId = useStoreState( store, 'selectedId' );
66-
const activeId = useStoreState( store, 'activeId' );
67-
const selectOnMove = useStoreState( store, 'selectOnMove' );
68-
const items = useStoreState( store, 'items' );
65+
const selectedId = Ariakit.useStoreState( store, 'selectedId' );
66+
const activeId = Ariakit.useStoreState( store, 'activeId' );
67+
const selectOnMove = Ariakit.useStoreState( store, 'selectOnMove' );
68+
const items = Ariakit.useStoreState( store, 'items' );
6969
const [ parent, setParent ] = useState< HTMLElement >();
7070
const refs = useMergeRefs( [ ref, setParent ] );
71-
const selectedRect = useTrackElementOffsetRect(
72-
store?.item( selectedId )?.element
73-
);
71+
72+
const selectedItem = store?.item( selectedId );
73+
const renderedItems = Ariakit.useStoreState( store, 'renderedItems' );
74+
75+
const selectedItemIndex =
76+
renderedItems && selectedItem
77+
? renderedItems.indexOf( selectedItem )
78+
: -1;
79+
// Use selectedItemIndex as a dependency to force recalculation when the
80+
// selected item index changes (elements are swapped / added / removed).
81+
const selectedRect = useTrackElementOffsetRect( selectedItem?.element, [
82+
selectedItemIndex,
83+
] );
7484

7585
// Track overflow to show scroll hints.
7686
const overflow = useTrackOverflow( parent, {

packages/components/src/utils/element-rect.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,17 @@ const POLL_RATE = 100;
134134
* milliseconds until it succeeds.
135135
*/
136136
export function useTrackElementOffsetRect(
137-
targetElement: HTMLElement | undefined | null
137+
targetElement: HTMLElement | undefined | null,
138+
deps: unknown[] = []
138139
) {
139140
const [ indicatorPosition, setIndicatorPosition ] =
140141
useState< ElementOffsetRect >( NULL_ELEMENT_OFFSET_RECT );
141142
const intervalRef = useRef< ReturnType< typeof setInterval > >();
142143

143144
const measure = useEvent( () => {
144-
if ( targetElement ) {
145+
// Check that the targetElement is still attached to the DOM, in case
146+
// it was removed since the last `measure` call.
147+
if ( targetElement && targetElement.isConnected ) {
145148
const elementOffsetRect = getElementOffsetRect( targetElement );
146149
if ( elementOffsetRect ) {
147150
setIndicatorPosition( elementOffsetRect );
@@ -171,6 +174,16 @@ export function useTrackElementOffsetRect(
171174
}
172175
}, [ setElement, targetElement ] );
173176

177+
// Escape hatch to force a remeasurement when something else changes rather
178+
// than the target elements' ref or size (for example, the target element
179+
// can change its position within the tablist).
180+
useLayoutEffect( () => {
181+
measure();
182+
// `measure` is a stable function, so it's safe to omit it from the deps array.
183+
// deps can't be statically analyzed by ESLint
184+
// eslint-disable-next-line react-hooks/exhaustive-deps
185+
}, deps );
186+
174187
return indicatorPosition;
175188
}
176189

0 commit comments

Comments
 (0)