diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 47159ec62d079d..23edf2b0ff5b7c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -49,6 +49,7 @@ - `Popover`: make sure offset middleware always applies the latest frame offset values ([#43329](https://github.com/WordPress/gutenberg/pull/43329/)). - `Dropdown`: anchor popover to the dropdown wrapper (instead of the toggle) ([#43377](https://github.com/WordPress/gutenberg/pull/43377/)). - `Guide`: Fix error when rendering with no pages ([#43380](https://github.com/WordPress/gutenberg/pull/43380/)). +- `Disabled`: preserve input values when toggling the `isDisabled` prop ([#43508](https://github.com/WordPress/gutenberg/pull/43508/)) ### Enhancements diff --git a/packages/components/src/disabled/index.tsx b/packages/components/src/disabled/index.tsx index 9b8fdd230fb206..3cda56a27b5b23 100644 --- a/packages/components/src/disabled/index.tsx +++ b/packages/components/src/disabled/index.tsx @@ -1,24 +1,34 @@ /** * External dependencies */ -import classnames from 'classnames'; +import type { HTMLProps } from 'react'; /** * WordPress dependencies */ import { useDisabled } from '@wordpress/compose'; -import { createContext } from '@wordpress/element'; +import { createContext, forwardRef } from '@wordpress/element'; /** * Internal dependencies */ -import { StyledWrapper } from './styles/disabled-styles'; +import { disabledStyles } from './styles/disabled-styles'; import type { DisabledProps } from './types'; import type { WordPressComponentProps } from '../ui/context'; +import { useCx } from '../utils'; const Context = createContext< boolean >( false ); const { Consumer, Provider } = Context; +// Extracting this ContentWrapper component in order to make it more explicit +// the same 'ContentWrapper' component is needed so that React can reconcile +// the dom correctly when switching between disabled/non-disabled (instead +// of thrashing the previous DOM and therefore losing the form fields values). +const ContentWrapper = forwardRef< + HTMLDivElement, + HTMLProps< HTMLDivElement > +>( ( props, ref ) =>
); + /** * `Disabled` is a component which disables descendant tabbable elements and prevents pointer interaction. * @@ -56,20 +66,28 @@ function Disabled( { ...props }: WordPressComponentProps< DisabledProps, 'div' > ) { const ref = useDisabled(); - + const cx = useCx(); if ( ! isDisabled ) { - return { children }; + return ( + + { children } + + ); } return ( - { children } - + ); } diff --git a/packages/components/src/disabled/styles/disabled-styles.tsx b/packages/components/src/disabled/styles/disabled-styles.tsx index b0ebeef8873972..28e617efcbe0d8 100644 --- a/packages/components/src/disabled/styles/disabled-styles.tsx +++ b/packages/components/src/disabled/styles/disabled-styles.tsx @@ -1,9 +1,9 @@ /** * External dependencies */ -import styled from '@emotion/styled'; +import { css } from '@emotion/react'; -export const StyledWrapper = styled.div` +export const disabledStyles = css` position: relative; pointer-events: none; diff --git a/packages/components/src/disabled/test/index.tsx b/packages/components/src/disabled/test/index.tsx index 2284fc3ee9e8c7..7b55b0f79c3832 100644 --- a/packages/components/src/disabled/test/index.tsx +++ b/packages/components/src/disabled/test/index.tsx @@ -7,6 +7,7 @@ import { render, screen, waitFor } from '@testing-library/react'; * Internal dependencies */ import Disabled from '../'; +import userEvent from '@testing-library/user-event'; jest.mock( '@wordpress/dom', () => { const focus = jest.requireActual( '../../../../dom/src' ).focus; @@ -134,6 +135,43 @@ describe( 'Disabled', () => { ); } ); + it( 'should preserve input values when toggling the isDisabled prop', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + const MaybeDisable = ( { isDisabled = true } ) => ( + +
+ + ); + + const getInput = () => screen.getByRole( 'textbox' ); + const getContentEditable = () => screen.getByTitle( 'edit my content' ); + + const { rerender } = render( ); + + await user.type( getInput(), 'This is input.' ); + expect( getInput() ).toHaveValue( 'This is input.' ); + + await user.type( getContentEditable(), 'This is contentEditable.' ); + expect( getContentEditable() ).toHaveTextContent( + 'This is contentEditable.' + ); + + rerender( ); + expect( getInput() ).toHaveValue( 'This is input.' ); + expect( getContentEditable() ).toHaveTextContent( + 'This is contentEditable.' + ); + + rerender( ); + expect( getInput() ).toHaveValue( 'This is input.' ); + expect( getContentEditable() ).toHaveTextContent( + 'This is contentEditable.' + ); + } ); + describe( 'Consumer', () => { function DisabledStatus() { return (