diff --git a/docs/data/components/accordion/accordion.mdx b/docs/data/components/accordion/accordion.mdx index 64647277de..40a887a4dd 100644 --- a/docs/data/components/accordion/accordion.mdx +++ b/docs/data/components/accordion/accordion.mdx @@ -205,12 +205,10 @@ Alternatively `keepMounted` can be passed to `Accordion.Panel`s directly to enab Content hidden by `Accordion.Panel` components can be made accessible only to a browser's find-in-page functionality with the `hiddenUntilFound` prop to improve searchability: ```jsx - - {/* accordion items */} - +{/* accordion items */} ``` -Be aware that this must be used together with `keepMounted`. +When `hiddenUntilFound` is used, `Accordion.Panel`s will remain mounted even when closed, overriding the `keepMounted` prop on the root or the panel. Alternatively `hiddenUntilFound` can be passed to `Accordion.Panel`s directly to enable this for only one `Item` instead of the whole accordion. diff --git a/docs/data/components/collapsible/collapsible.mdx b/docs/data/components/collapsible/collapsible.mdx index 2f2a830761..20900249c4 100644 --- a/docs/data/components/collapsible/collapsible.mdx +++ b/docs/data/components/collapsible/collapsible.mdx @@ -57,14 +57,14 @@ Content hidden in the `Collapsible.Panel` component can be made accessible only ```jsx Toggle - + When this component is closed, this sentence will only be accessible to the browser's native find-in-page functionality ``` -Be aware that this must be used together with `keepMounted`. +When `hiddenUntilFound` is used, the `Panel` remains mounted even when closed, overriding the `keepMounted` prop. We recommend using [CSS animations](#css-animations) for animated collapsibles that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a [CSS transition](#css-transitions) applied. diff --git a/docs/data/translations/api-docs/collapsible-panel/collapsible-panel.json b/docs/data/translations/api-docs/collapsible-panel/collapsible-panel.json index 57a591d862..04904011a6 100644 --- a/docs/data/translations/api-docs/collapsible-panel/collapsible-panel.json +++ b/docs/data/translations/api-docs/collapsible-panel/collapsible-panel.json @@ -5,10 +5,10 @@ "description": "Class names applied to the element or a function that returns them based on the component's state." }, "hiddenUntilFound": { - "description": "If true, sets hidden="until-found" when closed. Requires setting keepMounted to true. If false, sets hidden when closed." + "description": "If true, sets the hidden state using hidden="until-found". The panel remains mounted in the DOM when closed and overrides keepMounted. If false, sets the hidden state using hidden." }, "keepMounted": { - "description": "If true, the panel remains mounted when closed and is instead hidden using the hidden attribute If false, the panel is unmounted when closed." + "description": "If true, the panel remains mounted when closed and is instead hidden using the hidden attribute If false, the panel is unmounted when closed. If the hiddenUntilFound prop is used, the panel overrides this prop and is remains mounted when closed." }, "render": { "description": "A function to customize rendering of the component." } }, diff --git a/docs/src/app/experiments/collapsible-hidden-until-found.tsx b/docs/src/app/experiments/collapsible-hidden-until-found.tsx index 19faad4ca8..e9390ec12e 100644 --- a/docs/src/app/experiments/collapsible-hidden-until-found.tsx +++ b/docs/src/app/experiments/collapsible-hidden-until-found.tsx @@ -51,7 +51,6 @@ export default function CollapsibleHiddenUntilFound() {

This is the collapsed content

@@ -66,7 +65,6 @@ export default function CollapsibleHiddenUntilFound() {

This is the collapsed content

diff --git a/packages/mui-base/src/Accordion/Panel/AccordionPanel.tsx b/packages/mui-base/src/Accordion/Panel/AccordionPanel.tsx index be82a8296f..abc9599b19 100644 --- a/packages/mui-base/src/Accordion/Panel/AccordionPanel.tsx +++ b/packages/mui-base/src/Accordion/Panel/AccordionPanel.tsx @@ -40,6 +40,17 @@ const AccordionPanel = React.forwardRef(function AccordionPanel( const { hiddenUntilFound, keepMounted } = useAccordionRootContext(); + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + if (keepMountedProp === false && (hiddenUntilFoundProp ?? hiddenUntilFound)) { + console.warn( + 'Base UI: The `keepMounted={false}` prop on a Accordion.Panel will be ignored when using `hiddenUntilFound` on the Panel or the Root since it requires the panel to remain mounted when closed.', + ); + } + }, [hiddenUntilFoundProp, hiddenUntilFound, keepMountedProp]); + } + const { getRootProps, height, width, isOpen } = useCollapsiblePanel({ animated, hiddenUntilFound: hiddenUntilFoundProp || hiddenUntilFound, @@ -72,7 +83,7 @@ const AccordionPanel = React.forwardRef(function AccordionPanel( customStyleHookMapping: accordionStyleHookMapping, }); - if (!(keepMountedProp || keepMounted) && !isOpen) { + if (!(keepMountedProp ?? keepMounted) && !isOpen) { return null; } diff --git a/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx b/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx index 7aeb096cd1..ea039e594c 100644 --- a/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx +++ b/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx @@ -30,8 +30,8 @@ const AccordionRoot = React.forwardRef(function AccordionRoot( className, direction, disabled = false, - hiddenUntilFound = false, - keepMounted = false, + hiddenUntilFound: hiddenUntilFoundProp, + keepMounted: keepMountedProp, loop, onValueChange, openMultiple = true, @@ -42,6 +42,17 @@ const AccordionRoot = React.forwardRef(function AccordionRoot( ...otherProps } = props; + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + if (hiddenUntilFoundProp && keepMountedProp === false) { + console.warn( + 'Base UI: The `keepMounted={false}` prop on a Accordion.Root will be ignored when using `hiddenUntilFound` since it requires Panels to remain mounted when closed.', + ); + } + }, [hiddenUntilFoundProp, keepMountedProp]); + } + // memoized to allow omitting both defaultValue and value // which would otherwise trigger a warning in useControlled const defaultValue = React.useMemo(() => { @@ -76,11 +87,11 @@ const AccordionRoot = React.forwardRef(function AccordionRoot( const contextValue: AccordionRootContext = React.useMemo( () => ({ ...accordion, - hiddenUntilFound, - keepMounted, + hiddenUntilFound: hiddenUntilFoundProp ?? false, + keepMounted: keepMountedProp ?? false, ownerState, }), - [accordion, hiddenUntilFound, keepMounted, ownerState], + [accordion, hiddenUntilFoundProp, keepMountedProp, ownerState], ); const { renderElement } = useComponentRenderer({ diff --git a/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx b/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx index f549dd4bbc..3c7e670df0 100644 --- a/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx +++ b/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx @@ -95,9 +95,7 @@ describe('', () => { const { queryByText } = await render( - - This is panel content - + This is panel content , ); @@ -114,5 +112,23 @@ describe('', () => { expect(handleOpenChange.callCount).to.equal(1); expect(panel).to.have.attribute('data-open'); }); + + // toWarnDev doesn't work reliably with async rendering. To re-enable after it's fixed in the test-utils. + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('warns when setting both `hiddenUntilFound` and `keepMounted={false}`', async function test() { + await expect(() => + render( + + + + This is panel content + + , + ), + ).toWarnDev([ + 'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.', + 'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.', + ]); + }); }); }); diff --git a/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.tsx b/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.tsx index 4754068466..c5a571593a 100644 --- a/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.tsx +++ b/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.tsx @@ -22,7 +22,26 @@ const CollapsiblePanel = React.forwardRef(function CollapsiblePanel( props: CollapsiblePanel.Props, forwardedRef: React.ForwardedRef, ) { - const { className, hiddenUntilFound, keepMounted = false, render, ...otherProps } = props; + const { + className, + hiddenUntilFound, + keepMounted: keepMountedProp, + render, + ...otherProps + } = props; + + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + if (hiddenUntilFound && keepMountedProp === false) { + console.warn( + 'Base UI: The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.', + ); + } + }, [hiddenUntilFound, keepMountedProp]); + } + + const keepMounted = keepMountedProp ?? false; const { animated, mounted, open, panelId, setPanelId, setMounted, setOpen, ownerState } = useCollapsibleRootContext(); @@ -55,7 +74,7 @@ const CollapsiblePanel = React.forwardRef(function CollapsiblePanel( customStyleHookMapping: collapsibleStyleHookMapping, }); - if (!keepMounted && !isOpen) { + if (!keepMounted && !isOpen && !hiddenUntilFound) { return null; } @@ -72,6 +91,8 @@ namespace CollapsiblePanel { * If `true`, the panel remains mounted when closed and is instead * hidden using the `hidden` attribute * If `false`, the panel is unmounted when closed. + * If the `hiddenUntilFound` prop is used, the panel overrides this prop and + * is remains mounted when closed. * @default false */ keepMounted?: boolean; @@ -92,9 +113,9 @@ CollapsiblePanel.propTypes /* remove-proptypes */ = { */ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), /** - * If `true`, sets `hidden="until-found"` when closed. - * Requires setting `keepMounted` to `true`. - * If `false`, sets `hidden` when closed. + * If `true`, sets the hidden state using `hidden="until-found"`. The panel + * remains mounted in the DOM when closed and overrides `keepMounted`. + * If `false`, sets the hidden state using `hidden`. * @default false */ hiddenUntilFound: PropTypes.bool, @@ -102,6 +123,8 @@ CollapsiblePanel.propTypes /* remove-proptypes */ = { * If `true`, the panel remains mounted when closed and is instead * hidden using the `hidden` attribute * If `false`, the panel is unmounted when closed. + * If the `hiddenUntilFound` prop is used, the panel overrides this prop and + * is remains mounted when closed. * @default false */ keepMounted: PropTypes.bool, diff --git a/packages/mui-base/src/Collapsible/Panel/useCollapsiblePanel.ts b/packages/mui-base/src/Collapsible/Panel/useCollapsiblePanel.ts index b46e029c60..20a43a461c 100644 --- a/packages/mui-base/src/Collapsible/Panel/useCollapsiblePanel.ts +++ b/packages/mui-base/src/Collapsible/Panel/useCollapsiblePanel.ts @@ -300,9 +300,9 @@ export namespace useCollapsiblePanel { */ animated?: boolean; /** - * If `true`, sets `hidden="until-found"` when closed. - * Requires setting `keepMounted` to `true`. - * If `false`, sets `hidden` when closed. + * If `true`, sets the hidden state using `hidden="until-found"`. The panel + * remains mounted in the DOM when closed and overrides `keepMounted`. + * If `false`, sets the hidden state using `hidden`. * @default false */ hiddenUntilFound?: boolean; diff --git a/packages/mui-base/src/Dialog/Root/DialogRoot.test.tsx b/packages/mui-base/src/Dialog/Root/DialogRoot.test.tsx index c4b9d8854a..1ef9005ae6 100644 --- a/packages/mui-base/src/Dialog/Root/DialogRoot.test.tsx +++ b/packages/mui-base/src/Dialog/Root/DialogRoot.test.tsx @@ -46,7 +46,7 @@ describe('', () => { }); }); - // toWarnDev doesn't work reliably with async rendering. To re-eanble after it's fixed in the test-utils. + // toWarnDev doesn't work reliably with async rendering. To re-enable after it's fixed in the test-utils. // eslint-disable-next-line mocha/no-skipped-tests describe.skip('prop: modal', () => { it('warns when the dialog is modal but no backdrop is present', async () => {