From 84108990e258bb315a2b471552c6358de688b472 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 5 Aug 2026 00:44:06 +0200 Subject: [PATCH] feat(react-tag-picker): add portal-free base render renderTagPicker_unstable statically imports Portal, so @fluentui/react-portal is bundled by any consumer of the render - including the headless TagPicker, which sets inline: true and never portals at runtime. Moves the shared JSX into renderTagPickerBase, which keeps the popover in DOM order, and leaves portaling in the styled render. Headless re-exports the base render. Keeping the base in its own module is what removes the static import; sharing a file would re-introduce it. --- ...-3e7d5c91-42b6-4f8a-9d0e-8c5a1b2f6e73.json | 7 ++++ ...-9c1f2a70-5b84-4d2e-8a3c-6d1e9f4b7c25.json | 7 ++++ .../library/etc/tag-picker.api.md | 2 +- .../components/TagPicker/renderTagPicker.tsx | 2 +- .../library/etc/react-tag-picker.api.md | 3 ++ .../react-tag-picker/library/src/TagPicker.ts | 1 + .../library/src/components/TagPicker/index.ts | 1 + .../components/TagPicker/renderTagPicker.tsx | 25 ++++------- .../TagPicker/renderTagPickerBase.tsx | 41 +++++++++++++++++++ .../react-tag-picker/library/src/index.ts | 1 + 10 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 change/@fluentui-react-headless-components-preview-3e7d5c91-42b6-4f8a-9d0e-8c5a1b2f6e73.json create mode 100644 change/@fluentui-react-tag-picker-9c1f2a70-5b84-4d2e-8a3c-6d1e9f4b7c25.json create mode 100644 packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPickerBase.tsx diff --git a/change/@fluentui-react-headless-components-preview-3e7d5c91-42b6-4f8a-9d0e-8c5a1b2f6e73.json b/change/@fluentui-react-headless-components-preview-3e7d5c91-42b6-4f8a-9d0e-8c5a1b2f6e73.json new file mode 100644 index 00000000000000..0fd1ff53e082c4 --- /dev/null +++ b/change/@fluentui-react-headless-components-preview-3e7d5c91-42b6-4f8a-9d0e-8c5a1b2f6e73.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: render TagPicker without a portal so react-portal is not bundled", + "packageName": "@fluentui/react-headless-components-preview", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tag-picker-9c1f2a70-5b84-4d2e-8a3c-6d1e9f4b7c25.json b/change/@fluentui-react-tag-picker-9c1f2a70-5b84-4d2e-8a3c-6d1e9f4b7c25.json new file mode 100644 index 00000000000000..360ed6d61148dc --- /dev/null +++ b/change/@fluentui-react-tag-picker-9c1f2a70-5b84-4d2e-8a3c-6d1e9f4b7c25.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add renderTagPickerBase_unstable, a portal-free render for headless consumers", + "packageName": "@fluentui/react-tag-picker", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-headless-components-preview/library/etc/tag-picker.api.md b/packages/react-components/react-headless-components-preview/library/etc/tag-picker.api.md index 82c0d2516706ca..9fdbce2fda77aa 100644 --- a/packages/react-components/react-headless-components-preview/library/etc/tag-picker.api.md +++ b/packages/react-components/react-headless-components-preview/library/etc/tag-picker.api.md @@ -16,7 +16,7 @@ import type { OptionSlots as OptionSlots_2 } from '@fluentui/react-combobox'; import type { OptionState as OptionState_2 } from '@fluentui/react-combobox'; import { PositioningShorthand } from '@fluentui/react-positioning'; import type * as React_2 from 'react'; -import { renderTagPicker_unstable as renderTagPicker } from '@fluentui/react-tag-picker'; +import { renderTagPickerBase_unstable as renderTagPicker } from '@fluentui/react-tag-picker'; import { renderTagPickerButton_unstable as renderTagPickerButton } from '@fluentui/react-tag-picker'; import { renderTagPickerControl_unstable as renderTagPickerControl } from '@fluentui/react-tag-picker'; import { renderTagPickerGroup_unstable as renderTagPickerGroup } from '@fluentui/react-tag-picker'; diff --git a/packages/react-components/react-headless-components-preview/library/src/components/TagPicker/renderTagPicker.tsx b/packages/react-components/react-headless-components-preview/library/src/components/TagPicker/renderTagPicker.tsx index 93bdfe09d6e749..96ce65f8451794 100644 --- a/packages/react-components/react-headless-components-preview/library/src/components/TagPicker/renderTagPicker.tsx +++ b/packages/react-components/react-headless-components-preview/library/src/components/TagPicker/renderTagPicker.tsx @@ -1 +1 @@ -export { renderTagPicker_unstable as renderTagPicker } from '@fluentui/react-tag-picker'; +export { renderTagPickerBase_unstable as renderTagPicker } from '@fluentui/react-tag-picker'; diff --git a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md index a7a0ce50f7b0b3..161332dfacb81b 100644 --- a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md +++ b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md @@ -37,6 +37,9 @@ import type { TagGroupState } from '@fluentui/react-tags'; // @public export const renderTagPicker_unstable: (state: TagPickerState, contexts: TagPickerContextValues) => JSXElement; +// @public +export const renderTagPickerBase_unstable: (state: TagPickerBaseState, contexts: TagPickerContextValues) => JSXElement; + // @public export const renderTagPickerButton_unstable: (state: TagPickerButtonBaseState) => JSXElement; diff --git a/packages/react-components/react-tag-picker/library/src/TagPicker.ts b/packages/react-components/react-tag-picker/library/src/TagPicker.ts index de35c946bc720c..4c804e5f664da1 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPicker.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPicker.ts @@ -12,6 +12,7 @@ export type { export { TagPicker, renderTagPicker_unstable, + renderTagPickerBase_unstable, useTagPicker_unstable, useTagPickerBase_unstable, useTagPickerContextValues, diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts index 919539cb076931..2086e38feea1c9 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts @@ -11,5 +11,6 @@ export type { TagPickerState, } from './TagPicker.types'; export { renderTagPicker_unstable } from './renderTagPicker'; +export { renderTagPickerBase_unstable } from './renderTagPickerBase'; export { useTagPicker_unstable, useTagPickerBase_unstable } from './useTagPicker'; export { useTagPickerContextValues } from './useTagPickerContextValues'; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPicker.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPicker.tsx index c31e64e07c69fb..2a3242a483dbb8 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPicker.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPicker.tsx @@ -1,27 +1,16 @@ import * as React from 'react'; -import type { TagPickerState, TagPickerContextValues, TagPickerSlots } from './TagPicker.types'; -import { ActiveDescendantContextProvider } from '@fluentui/react-aria'; -import { ListboxProvider } from '@fluentui/react-combobox'; +import type { TagPickerState, TagPickerContextValues } from './TagPicker.types'; import { Portal } from '@fluentui/react-portal'; -import { assertSlots } from '@fluentui/react-utilities'; import type { JSXElement } from '@fluentui/react-utilities'; -import { TagPickerContextProvider } from '../../contexts/TagPickerContext'; +import { renderTagPickerContent } from './renderTagPickerBase'; /** * Render the final JSX of Picker */ -export const renderTagPicker_unstable = (state: TagPickerState, contexts: TagPickerContextValues): JSXElement => { - assertSlots(state); - return ( - - - - {state.trigger} - {state.popover && - (state.inline ? state.popover : {state.popover})} - - - +export const renderTagPicker_unstable = (state: TagPickerState, contexts: TagPickerContextValues): JSXElement => + renderTagPickerContent( + state, + contexts, + state.popover && (state.inline ? state.popover : {state.popover}), ); -}; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPickerBase.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPickerBase.tsx new file mode 100644 index 00000000000000..35b14d6cdf6058 --- /dev/null +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/renderTagPickerBase.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { ActiveDescendantContextProvider } from '@fluentui/react-aria'; +import { ListboxProvider } from '@fluentui/react-combobox'; +import { assertSlots } from '@fluentui/react-utilities'; +import type { JSXElement } from '@fluentui/react-utilities'; + +import type { TagPickerBaseState, TagPickerContextValues, TagPickerSlots } from './TagPicker.types'; +import { TagPickerContextProvider } from '../../contexts/TagPickerContext'; + +/** + * Render the base JSX of Picker, keeping the popover in DOM order. + * + * Portaling is layered on by the styled `renderTagPicker_unstable`. Keeping it in a separate module + * is what lets consumers render a TagPicker without pulling `@fluentui/react-portal` - and with it + * Griffel - into their bundle. + */ +export const renderTagPickerBase_unstable = (state: TagPickerBaseState, contexts: TagPickerContextValues): JSXElement => + renderTagPickerContent(state, contexts, state.popover); + +/** + * Shared by the base and styled renders so that portaling stays out of the base module. + * + * @internal + */ +export const renderTagPickerContent = ( + state: TagPickerBaseState, + contexts: TagPickerContextValues, + popover: React.ReactNode, +): JSXElement => { + assertSlots(state); + return ( + + + + {state.trigger} + {popover} + + + + ); +}; diff --git a/packages/react-components/react-tag-picker/library/src/index.ts b/packages/react-components/react-tag-picker/library/src/index.ts index 1518360ea801f4..0921c3cd1d469a 100644 --- a/packages/react-components/react-tag-picker/library/src/index.ts +++ b/packages/react-components/react-tag-picker/library/src/index.ts @@ -1,6 +1,7 @@ export { TagPicker, renderTagPicker_unstable, + renderTagPickerBase_unstable, useTagPicker_unstable, useTagPickerBase_unstable, useTagPickerContextValues,