Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown

@github-actions github-actions Bot May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ•΅πŸΎβ€β™€οΈ visual changes to review in the Visual Change Report

vr-tests-react-components/Avatar Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Avatar Converged.badgeMask.normal.chromium.png 5 Changed
vr-tests-react-components/Charts-DonutChart 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 7530 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 605 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 404 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 849 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 65 Changed
vr-tests-react-components/ProgressBar converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 66 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 41 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - RTL.chromium.png 635 Changed

There were 1 duplicate changes discarded. Check the build logs for more information.

"type": "minor",
"comment": "feat: export contexts for headless",
"packageName": "@fluentui/react-tags",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { JSXElement } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';
import type { TabsterDOMAttribute } from '@fluentui/react-tabster';

// @public
export const InteractionTag: ForwardRefComponent<InteractionTagProps>;
Expand All @@ -29,6 +30,18 @@ export type InteractionTagBaseState<Value = TagValue> = Omit<InteractionTagState
// @public (undocumented)
export const interactionTagClassNames: SlotClassNames<InteractionTagSlots>;

// @public (undocumented)
export const InteractionTagContextProvider: React_2.Provider<InteractionTagContextValue<string> | undefined>;

// @public
export type InteractionTagContextValue<Value = string> = Required<Pick<InteractionTagState, 'appearance' | 'disabled' | 'selected' | 'selectedValues' | 'shape' | 'size'> & {
handleTagDismiss: TagDismissHandler<Value>;
interactionTagPrimaryId: string;
value?: Value;
}> & {
handleTagSelect?: TagSelectHandler<Value>;
};

// @public
export const InteractionTagPrimary: ForwardRefComponent<InteractionTagPrimaryProps>;

Expand Down Expand Up @@ -137,6 +150,9 @@ export type TagBaseState = DistributiveOmit<TagState, 'appearance' | 'size' | 's
// @public (undocumented)
export const tagClassNames: SlotClassNames<TagSlots>;

// @public (undocumented)
export type TagContextValues = TagAvatarContextValues;

// @public (undocumented)
export type TagDismissData<Value = TagValue> = {
value: Value;
Expand All @@ -160,6 +176,12 @@ export type TagGroupBaseState<Value = TagValue> = Omit<TagGroupState<Value>, 'ap
// @public (undocumented)
export const tagGroupClassNames: SlotClassNames<TagGroupSlots>;

// @public (undocumented)
export const TagGroupContextProvider: React_2.Provider<TagGroupContextValue | undefined>;

// @public
export type TagGroupContextValue = Required<Pick<TagGroupState, 'handleTagDismiss' | 'size'>> & Partial<Pick<TagGroupState, 'disabled' | 'appearance' | 'dismissible' | 'handleTagSelect' | 'role' | 'selectedValues'>>;

// @public (undocumented)
export type TagGroupContextValues = {
tagGroup: TagGroupContextValue;
Expand Down Expand Up @@ -228,6 +250,9 @@ export const useInteractionTag_unstable: (props: InteractionTagProps, ref: React
// @public
export const useInteractionTagBase_unstable: (props: InteractionTagBaseProps, ref: React_2.Ref<HTMLDivElement>) => InteractionTagBaseState;

// @public (undocumented)
export const useInteractionTagContext_unstable: () => InteractionTagContextValue;

// @public (undocumented)
export function useInteractionTagContextValues_unstable(state: InteractionTagState): InteractionTagContextValues;

Expand Down Expand Up @@ -265,7 +290,10 @@ export const useTagBase_unstable: (props: TagBaseProps, ref: React_2.Ref<HTMLSpa
export const useTagGroup_unstable: (props: TagGroupProps, ref: React_2.Ref<HTMLDivElement>) => TagGroupState;

// @public
export const useTagGroupBase_unstable: (props: TagGroupBaseProps, ref: React_2.Ref<HTMLDivElement>) => TagGroupBaseState;
export const useTagGroupBase_unstable: (props: TagGroupBaseProps, ref: React_2.Ref<HTMLDivElement>, options?: UseTagGroupBaseOptions) => TagGroupBaseState;

// @public (undocumented)
export const useTagGroupContext_unstable: () => TagGroupContextValue;

// @public (undocumented)
export function useTagGroupContextValues_unstable(state: TagGroupState): TagGroupContextValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import { useArrowNavigationGroup, useFocusFinders } from '@fluentui/react-tabste
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { interactionTagSecondaryClassNames } from '../InteractionTagSecondary/useInteractionTagSecondaryStyles.styles';
import type { TagValue } from '../../utils/types';
import type { TabsterDOMAttribute } from '@fluentui/react-tabster';

type UseTagGroupBaseOptions = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why cant we extend BaseProps type with this and have it all passed through props without need to change hook api pattern (which we probably should not do) ?

arrowNavigationProps?: TabsterDOMAttribute;
onAfterTagDismiss?: (container: HTMLElement | null) => void;
};

/**
* Create the base state required to render TagGroup, without design-only props.
Expand All @@ -24,6 +30,7 @@ import type { TagValue } from '../../utils/types';
export const useTagGroupBase_unstable = (
props: TagGroupBaseProps,
ref: React.Ref<HTMLDivElement>,
options?: UseTagGroupBaseOptions,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding third argument is an architecture change. I would like to avoid it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good callout , do we have this api change in any of our existing base hooks ? if not this should be avoided

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, but it leaked from the similar PR #36087 and should be undone

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR with a fix for useMenuTrigger #36237

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will ship lint rule for this to prevent these regressions

): TagGroupBaseState => {
const {
onDismiss,
Expand All @@ -37,8 +44,6 @@ export const useTagGroupBase_unstable = (
} = props;

const innerRef = React.useRef<HTMLElement>(undefined);
const { targetDocument } = useFluent();
const { findNextFocusable, findPrevFocusable } = useFocusFinders();

const [items, setItems] = useControllableState<Array<TagValue>>({
defaultState: defaultSelectedValues,
Expand All @@ -48,26 +53,7 @@ export const useTagGroupBase_unstable = (

const handleTagDismiss: TagGroupBaseState['handleTagDismiss'] = useEventCallback((e, data) => {
onDismiss?.(e, data);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useTagGroup_unstable can just enhance onDismiss callback?


// set focus after tag dismiss
const activeElement = targetDocument?.activeElement;
if (innerRef.current?.contains(activeElement as HTMLElement)) {
// focus on next tag only if the active element is within the current tag group
const next = findNextFocusable(activeElement as HTMLElement, { container: innerRef.current });
if (next) {
next.focus();
return;
}

// if there is no next focusable, focus on the previous focusable
if (activeElement?.className.includes(interactionTagSecondaryClassNames.root)) {
const prev = findPrevFocusable(activeElement.parentElement as HTMLElement, { container: innerRef.current });
prev?.focus();
} else {
const prev = findPrevFocusable(activeElement as HTMLElement, { container: innerRef.current });
prev?.focus();
}
}
options?.onAfterTagDismiss?.(innerRef.current ?? null);
});

const handleTagSelect: TagGroupBaseState['handleTagSelect'] = useEventCallback(
Expand All @@ -80,12 +66,6 @@ export const useTagGroupBase_unstable = (
}),
);

const arrowNavigationProps = useArrowNavigationGroup({
circular: true,
axis: 'both',
memorizeCurrent: true,
});

return {
handleTagDismiss,
handleTagSelect: onTagSelect ? handleTagSelect : undefined,
Expand All @@ -105,7 +85,7 @@ export const useTagGroupBase_unstable = (
ref: useMergedRefs(ref, innerRef) as React.Ref<HTMLDivElement>,
role,
'aria-disabled': disabled,
...arrowNavigationProps,
...options?.arrowNavigationProps,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be just spreaded to props in useTagGroup_unstable, no?

...rest,
}),
{ elementType: 'div' },
Expand All @@ -124,8 +104,39 @@ export const useTagGroupBase_unstable = (
*/
export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDivElement>): TagGroupState => {
const { size = 'medium', appearance = 'filled' } = props;

const { targetDocument } = useFluent();
const { findNextFocusable, findPrevFocusable } = useFocusFinders();

const arrowNavigationProps = useArrowNavigationGroup({
circular: true,
axis: 'both',
memorizeCurrent: true,
});

const onAfterTagDismiss = useEventCallback((container: HTMLElement | null) => {
const activeElement = targetDocument?.activeElement;
if (container?.contains(activeElement as HTMLElement)) {
// focus on next tag only if the active element is within the current tag group
const next = findNextFocusable(activeElement as HTMLElement, { container });
if (next) {
next.focus();
return;
}

// if there is no next focusable, focus on the previous focusable
if (activeElement?.className.includes(interactionTagSecondaryClassNames.root)) {
const prev = findPrevFocusable(activeElement.parentElement as HTMLElement, { container });
prev?.focus();
} else {
const prev = findPrevFocusable(activeElement as HTMLElement, { container });
prev?.focus();
}
}
});

return {
...useTagGroupBase_unstable(props, ref),
...useTagGroupBase_unstable(props, ref, { arrowNavigationProps, onAfterTagDismiss }),
size,
appearance,
};
Expand Down
8 changes: 7 additions & 1 deletion packages/react-components/react-tags/library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export {
useTagBase_unstable,
useTag_unstable,
} from './Tag';
export type { TagBaseProps, TagBaseState, TagProps, TagSlots, TagState } from './Tag';
export type { TagBaseProps, TagBaseState, TagContextValues, TagProps, TagSlots, TagState } from './Tag';

export {
InteractionTag,
Expand Down Expand Up @@ -74,6 +74,12 @@ export type {
TagGroupContextValues,
} from './TagGroup';

export { TagGroupContextProvider, useTagGroupContext_unstable } from './contexts/tagGroupContext';
export type { TagGroupContextValue } from './contexts/tagGroupContext';

export { InteractionTagContextProvider, useInteractionTagContext_unstable } from './contexts/interactionTagContext';
export type { InteractionTagContextValue } from './contexts/interactionTagContext';

export { useTagAvatarContextValues_unstable } from './utils';
export type {
TagAppearance,
Expand Down
Loading