Skip to content

Commit 395f5c2

Browse files
bsunderhusmainframev
authored andcommitted
chore(react-utilities): updates useMergedRefs to accept LegacyRef
1 parent 55de57f commit 395f5c2

File tree

6 files changed

+47
-39
lines changed

6 files changed

+47
-39
lines changed

packages/react-components/react-menu/library/etc/react-menu.api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ARIAButtonType } from '@fluentui/react-aria';
1212
import type { ComponentProps } from '@fluentui/react-utilities';
1313
import type { ComponentState } from '@fluentui/react-utilities';
1414
import type { ContextSelector } from '@fluentui/react-context-selector';
15+
import type { ExtractSlotProps } from '@fluentui/react-utilities';
1516
import type { ForwardRefComponent } from '@fluentui/react-utilities';
1617
import type { PortalProps } from '@fluentui/react-portal';
1718
import type { PositioningShorthand } from '@fluentui/react-positioning';
@@ -157,7 +158,7 @@ export type MenuItemLinkSlots = {
157158
export type MenuItemLinkState = ComponentState<MenuItemLinkSlots>;
158159

159160
// @public (undocumented)
160-
export type MenuItemProps = Omit<ComponentProps<Partial<MenuItemSlots>>, 'content'> & Pick<Partial<MenuItemSlots>, 'content'> & {
161+
export type MenuItemProps = ComponentProps<Partial<MenuItemSlots>> & {
161162
hasSubmenu?: boolean;
162163
persistOnClick?: boolean;
163164
disabled?: boolean;
@@ -189,7 +190,7 @@ export type MenuItemSelectableState = MenuItemSelectableProps & {
189190

190191
// @public (undocumented)
191192
export type MenuItemSlots = {
192-
root: Slot<'div'>;
193+
root: Slot<Omit<ExtractSlotProps<Slot<'div'>>, 'content'>>;
193194
icon?: Slot<'span'>;
194195
checkmark?: Slot<'span'>;
195196
submenuIndicator?: Slot<'span'>;

packages/react-components/react-menu/library/src/components/MenuItem/MenuItem.types.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
1+
import type { ComponentProps, ComponentState, ExtractSlotProps, Slot } from '@fluentui/react-utilities';
22

33
export type MenuItemSlots = {
4-
root: Slot<'div'>;
4+
root: Slot<Omit<ExtractSlotProps<Slot<'div'>>, 'content'>>;
55

66
/**
77
* Icon slot rendered before children content
@@ -36,29 +36,28 @@ export type MenuItemSlots = {
3636
subText?: Slot<'span'>;
3737
};
3838

39-
export type MenuItemProps = Omit<ComponentProps<Partial<MenuItemSlots>>, 'content'> &
40-
Pick<Partial<MenuItemSlots>, 'content'> & {
41-
/**
42-
* If the menu item is a trigger for a submenu
43-
*
44-
* @default false
45-
*/
46-
hasSubmenu?: boolean;
39+
export type MenuItemProps = ComponentProps<Partial<MenuItemSlots>> & {
40+
/**
41+
* If the menu item is a trigger for a submenu
42+
*
43+
* @default false
44+
*/
45+
hasSubmenu?: boolean;
4746

48-
/**
49-
* Clicking on the menu item will not dismiss an open menu
50-
*
51-
* @default false
52-
*/
53-
persistOnClick?: boolean;
47+
/**
48+
* Clicking on the menu item will not dismiss an open menu
49+
*
50+
* @default false
51+
*/
52+
persistOnClick?: boolean;
5453

55-
disabled?: boolean;
56-
/**
57-
* @deprecated this property does nothing.
58-
* disabled focusable is by default by simply using `disabled` property
59-
*/
60-
disabledFocusable?: boolean;
61-
};
54+
disabled?: boolean;
55+
/**
56+
* @deprecated this property does nothing.
57+
* disabled focusable is by default by simply using `disabled` property
58+
*/
59+
disabledFocusable?: boolean;
60+
};
6261

6362
export type MenuItemState = ComponentState<MenuItemSlots> &
6463
Required<Pick<MenuItemProps, 'disabled' | 'hasSubmenu' | 'persistOnClick'>>;

packages/react-components/react-menu/library/src/components/MenuItem/useMenuItem.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ import {
1919
import { useMenuListContext_unstable } from '../../contexts/menuListContext';
2020
import { useMenuContext_unstable } from '../../contexts/menuContext';
2121
import type { MenuItemProps, MenuItemState } from './MenuItem.types';
22-
import {
23-
ARIAButtonElement,
24-
ARIAButtonElementIntersection,
25-
ARIAButtonProps,
26-
useARIAButtonProps,
27-
} from '@fluentui/react-aria';
22+
import { ARIAButtonElement, ARIAButtonElementIntersection, useARIAButtonProps } from '@fluentui/react-aria';
2823
import { Enter, Space } from '@fluentui/keyboard-keys';
2924
import { useIsInMenuSplitGroup, useMenuSplitGroupContext_unstable } from '../../contexts/menuSplitGroupContext';
3025

@@ -38,7 +33,6 @@ export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref<ARIABu
3833
const isSubmenuTrigger = useMenuTriggerContext_unstable();
3934
const persistOnClickContext = useMenuContext_unstable(context => context.persistOnItemClick);
4035
const {
41-
as = 'div',
4236
disabled = false,
4337
hasSubmenu = isSubmenuTrigger,
4438
persistOnClick = persistOnClickContext,
@@ -69,7 +63,7 @@ export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref<ARIABu
6963
root: slot.always(
7064
getIntrinsicElementProps<MenuItemProps>(
7165
'div',
72-
useARIAButtonProps<'div', ARIAButtonProps<'div'>>(as, {
66+
useARIAButtonProps('div', {
7367
role: 'menuitem',
7468
...rest,
7569
disabled: false,

packages/react-components/react-utilities/etc/react-utilities.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ export const useIsomorphicLayoutEffect: typeof React_2.useEffect;
359359
export function useIsSSR(): boolean;
360360

361361
// @public
362-
export function useMergedRefs<T>(...refs: (React_2.Ref<T> | undefined)[]): RefObjectFunction<T>;
362+
export function useMergedRefs<T>(...refs: (React_2.LegacyRef<T> | undefined)[]): RefObjectFunction<T>;
363363

364364
// @internal (undocumented)
365365
export type UseOnClickOrScrollOutsideOptions = {

packages/react-components/react-utilities/src/compose/deprecated/getSlots.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function getSlot<R extends SlotPropsRecord, K extends keyof R>(
110110
if (renderFunction || typeof children === 'function') {
111111
const render = (renderFunction || children) as SlotRenderFunction<R[K]>;
112112
return [
113-
React.Fragment,
113+
React.Fragment as React.ElementType<R[K]>,
114114
{
115115
children: render(slot, rest as Omit<R[K], 'as'>),
116116
} as unknown as R[K],

packages/react-components/react-utilities/src/hooks/useMergedRefs.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,35 @@ import * as React from 'react';
66
*/
77
export type RefObjectFunction<T> = React.RefObject<T> & ((value: T | null) => void);
88

9+
/** @internal */
10+
type MutableRefObjectFunction<T> = React.MutableRefObject<T | null> & ((value: T | null) => void);
11+
912
/**
1013
* React hook to merge multiple React refs (either MutableRefObjects or ref callbacks) into a single ref callback that
1114
* updates all provided refs
1215
* @param refs - Refs to collectively update with one ref value.
1316
* @returns A function with an attached "current" prop, so that it can be treated like a RefObject.
1417
*/
15-
export function useMergedRefs<T>(...refs: (React.Ref<T> | undefined)[]): RefObjectFunction<T> {
18+
// LegacyRef is actually not supported, but in React v18 types this is leaking directly from forwardRef component declaration
19+
export function useMergedRefs<T>(...refs: (React.LegacyRef<T> | undefined)[]): RefObjectFunction<T> {
1620
'use no memo';
1721

18-
const mergedCallback: RefObjectFunction<T> = React.useCallback(
22+
const mergedCallback = React.useCallback(
1923
(value: T | null) => {
2024
// Update the "current" prop hanging on the function.
21-
(mergedCallback as React.MutableRefObject<T | null>).current = value;
25+
mergedCallback.current = value;
2226

2327
for (const ref of refs) {
28+
if (typeof ref === 'string' && process.env.NODE_ENV !== 'production') {
29+
// eslint-disable-next-line no-console
30+
console.error(/** #__DE-INDENT__ */ `
31+
@fluentui/react-utilities [useMergedRefs]:
32+
This hook does not support the usage of string refs. Please use React.useRef instead.
33+
34+
For more info on 'React.useRef', see https://react.dev/reference/react/useRef.
35+
For more info on string refs, see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-string-refs.
36+
`);
37+
}
2438
if (typeof ref === 'function') {
2539
ref(value);
2640
} else if (ref) {
@@ -31,7 +45,7 @@ export function useMergedRefs<T>(...refs: (React.Ref<T> | undefined)[]): RefObje
3145
},
3246
// eslint-disable-next-line react-hooks/exhaustive-deps -- already exhaustive
3347
[...refs],
34-
) as RefObjectFunction<T>;
48+
) as MutableRefObjectFunction<T>;
3549

3650
return mergedCallback;
3751
}

0 commit comments

Comments
 (0)