Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: decouple useTagGroupBase_unstable from Tabster; export contexts. This is technically a breaking change, but the prior Tabster coupling was a programmatic mistake and not intended public behavior. If you run into issues, please bump to the next version.",
"packageName": "@fluentui/react-tags",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ 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 Down Expand Up @@ -290,7 +289,7 @@ 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>, options?: UseTagGroupBaseOptions) => TagGroupBaseState;
export const useTagGroupBase_unstable: (props: TagGroupBaseProps, ref: React_2.Ref<HTMLDivElement>) => TagGroupBaseState;

// @public (undocumented)
export const useTagGroupContext_unstable: () => TagGroupContextValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import type { TabsterDOMAttribute } from '@fluentui/react-tabster';

import { useTagGroup_unstable, useTagGroupBase_unstable } from './useTagGroup';
import type { TagGroupBaseProps } from './TagGroup.types';

describe('useTagGroup_unstable', () => {
it('should default size to medium and appearance to filled', () => {
Expand All @@ -13,7 +14,7 @@ describe('useTagGroup_unstable', () => {
expect(result.current.appearance).toBe('filled');
});

it('should spread Tabster arrow-navigation attributes onto root (via UseTagGroupBaseOptions)', () => {
it('should spread Tabster arrow-navigation attributes onto root', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useTagGroup_unstable({}, ref));

Expand All @@ -29,31 +30,31 @@ describe('useTagGroup_unstable', () => {
});

describe('useTagGroupBase_unstable', () => {
it('should NOT include arrow-navigation props when options omitted (true headless mode)', () => {
it('should NOT include arrow-navigation props by default (true headless mode)', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useTagGroupBase_unstable({}, ref));
expect(result.current.root).not.toHaveProperty('data-tabster');
});

it('should spread arrowNavigationProps option onto root when supplied', () => {
it('should spread arrow-navigation attributes onto root when passed via props', () => {
const ref = React.createRef<HTMLDivElement>();
const arrowNavigationProps: TabsterDOMAttribute = { 'data-tabster': '{"mock":"value"}' };
const { result } = renderHook(() => useTagGroupBase_unstable({}, ref, { arrowNavigationProps }));
const { result } = renderHook(() =>
useTagGroupBase_unstable({ ...arrowNavigationProps } as TagGroupBaseProps, ref),
);

expect(result.current.root).toHaveProperty('data-tabster', '{"mock":"value"}');
});

it('should call onAfterTagDismiss with the group container after a tag is dismissed', () => {
const onAfterTagDismiss = jest.fn();
it('should call onDismiss when a tag is dismissed', () => {
const onDismiss = jest.fn();
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useTagGroupBase_unstable({ onDismiss }, ref, { onAfterTagDismiss }));
const { result } = renderHook(() => useTagGroupBase_unstable({ onDismiss }, ref));

const event = {} as React.MouseEvent;
result.current.handleTagDismiss(event, { value: 'v1' });

expect(onDismiss).toHaveBeenCalledWith(event, { value: 'v1' });
expect(onAfterTagDismiss).toHaveBeenCalledWith(null);
});

it('should set aria-disabled on the root when disabled', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ 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 = {
arrowNavigationProps?: TabsterDOMAttribute;
onAfterTagDismiss?: (container: HTMLElement | null) => void;
};

/**
* Create the base state required to render TagGroup, without design-only props.
Expand All @@ -30,7 +24,6 @@ type UseTagGroupBaseOptions = {
export const useTagGroupBase_unstable = (
props: TagGroupBaseProps,
ref: React.Ref<HTMLDivElement>,
options?: UseTagGroupBaseOptions,
Comment thread
Hotell marked this conversation as resolved.
): TagGroupBaseState => {
const {
onDismiss,
Expand All @@ -43,8 +36,6 @@ export const useTagGroupBase_unstable = (
...rest
} = props;

const innerRef = React.useRef<HTMLElement>(undefined);

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

const handleTagDismiss: TagGroupBaseState['handleTagDismiss'] = useEventCallback((e, data) => {
onDismiss?.(e, data);
Comment thread
mainframev marked this conversation as resolved.
options?.onAfterTagDismiss?.(innerRef.current ?? null);
});

const handleTagSelect: TagGroupBaseState['handleTagSelect'] = useEventCallback(
Expand All @@ -79,13 +69,9 @@ export const useTagGroupBase_unstable = (

root: slot.always(
getIntrinsicElementProps('div', {
// FIXME:
// `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
// but since it would be a breaking change to fix it, we are casting ref to it's proper type
ref: useMergedRefs(ref, innerRef) as React.Ref<HTMLDivElement>,
ref,
role,
'aria-disabled': disabled,
...options?.arrowNavigationProps,
...rest,
}),
{ elementType: 'div' },
Expand Down Expand Up @@ -114,8 +100,15 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDi
memorizeCurrent: true,
});

const onAfterTagDismiss = useEventCallback((container: HTMLElement | null) => {
const innerRef = React.useRef<HTMLDivElement>(null);
const mergedRef = useMergedRefs(ref, innerRef);

const enhancedOnDismiss: TagGroupProps['onDismiss'] = useEventCallback((e, data) => {
props.onDismiss?.(e, data);

const container = innerRef.current;
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 });
Expand All @@ -136,7 +129,7 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref<HTMLDi
});

return {
...useTagGroupBase_unstable(props, ref, { arrowNavigationProps, onAfterTagDismiss }),
...useTagGroupBase_unstable({ ...arrowNavigationProps, ...props, onDismiss: enhancedOnDismiss }, mergedRef),
size,
appearance,
};
Expand Down
Loading