From 02616e5d038874fdf19ada6b40fc53ecac663b7b Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Thu, 7 Nov 2024 20:49:12 +0800 Subject: [PATCH] Make hiddenUntilFound override keepMounted --- docs/data/components/accordion/accordion.mdx | 6 ++-- .../components/collapsible/collapsible.mdx | 4 +-- .../accordion-panel/accordion-panel.json | 2 +- .../accordion-root/accordion-root.json | 2 +- .../collapsible-panel/collapsible-panel.json | 4 +-- .../collapsible-hidden-until-found.tsx | 2 -- .../src/Accordion/Panel/AccordionPanel.tsx | 17 ++++++++-- .../src/Accordion/Root/AccordionRoot.tsx | 29 +++++++++++----- .../Panel/CollapsiblePanel.test.tsx | 22 +++++++++++-- .../Collapsible/Panel/CollapsiblePanel.tsx | 33 ++++++++++++++++--- .../Collapsible/Panel/useCollapsiblePanel.ts | 6 ++-- .../src/Dialog/Root/DialogRoot.test.tsx | 2 +- 12 files changed, 93 insertions(+), 36 deletions(-) diff --git a/docs/data/components/accordion/accordion.mdx b/docs/data/components/accordion/accordion.mdx index 64647277d..40a887a4d 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 2f2a83076..20900249c 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/accordion-panel/accordion-panel.json b/docs/data/translations/api-docs/accordion-panel/accordion-panel.json index c2dbc14bd..857c3434f 100644 --- a/docs/data/translations/api-docs/accordion-panel/accordion-panel.json +++ b/docs/data/translations/api-docs/accordion-panel/accordion-panel.json @@ -5,7 +5,7 @@ "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 hidden="until-found" when closed. Accordion panels will remain mounted in the DOM when closed and overrides keepMounted. If false, sets hidden when closed." }, "keepMounted": { "description": "If true, accordion panels remains mounted when closed and is instead hidden using the hidden attribute. If false, accordion panels are unmounted when closed." diff --git a/docs/data/translations/api-docs/accordion-root/accordion-root.json b/docs/data/translations/api-docs/accordion-root/accordion-root.json index 7131e9ecb..e638988a0 100644 --- a/docs/data/translations/api-docs/accordion-root/accordion-root.json +++ b/docs/data/translations/api-docs/accordion-root/accordion-root.json @@ -12,7 +12,7 @@ }, "disabled": { "description": "If true, the component is disabled." }, "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 hidden="until-found" when closed. Accordion panels will remain mounted in the DOM when closed and overrides keepMounted. If false, sets hidden when closed." }, "keepMounted": { "description": "If true, accordion panels remains mounted when closed and is instead hidden using the hidden attribute. If false, accordion panels are unmounted when closed." 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 57a591d86..04904011a 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 19faad4ca..e9390ec12 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 be82a8296..817e2c2d5 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; } @@ -101,8 +112,8 @@ AccordionPanel.propTypes /* remove-proptypes */ = { */ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), /** - * If `true`, sets `hidden="until-found"` when closed. - * Requires setting `keepMounted` to `true`. + * If `true`, sets `hidden="until-found"` when closed. Accordion panels + * will remain mounted in the DOM when closed and overrides `keepMounted`. * If `false`, sets `hidden` when closed. * @default false */ diff --git a/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx b/packages/mui-base/src/Accordion/Root/AccordionRoot.tsx index 7aeb096cd..79bfa71ce 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({ @@ -111,8 +122,8 @@ export namespace AccordionRoot { extends useAccordionRoot.Parameters, Omit, 'defaultValue'> { /** - * If `true`, sets `hidden="until-found"` when closed. - * Requires setting `keepMounted` to `true`. + * If `true`, sets `hidden="until-found"` when closed. Accordion panels + * will remain mounted in the DOM when closed and overrides `keepMounted`. * If `false`, sets `hidden` when closed. * @default false */ @@ -165,8 +176,8 @@ AccordionRoot.propTypes /* remove-proptypes */ = { */ disabled: PropTypes.bool, /** - * If `true`, sets `hidden="until-found"` when closed. - * Requires setting `keepMounted` to `true`. + * If `true`, sets `hidden="until-found"` when closed. Accordion panels + * will remain mounted in the DOM when closed and overrides `keepMounted`. * If `false`, sets `hidden` when closed. * @default false */ diff --git a/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx b/packages/mui-base/src/Collapsible/Panel/CollapsiblePanel.test.tsx index f549dd4bb..3c7e670df 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 475406846..c5a571593 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 b46e029c6..20a43a461 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 c4b9d8854..1ef9005ae 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 () => {