From 257b3bc528f1711d4087f57ea299dcfec6fee10c Mon Sep 17 00:00:00 2001 From: mnajdova Date: Fri, 26 Jul 2024 12:27:39 +0200 Subject: [PATCH 1/5] remove files --- packages/mui-material/src/Badge/useBadge.ts | 48 -- .../mui-material/src/Badge/useBadge.types.ts | 40 - .../ClickAwayListener.test.js | 441 ----------- .../ClickAwayListener/ClickAwayListener.tsx | 262 ------ .../src/Modal/ModalManager.test.ts | 435 ---------- .../mui-material/src/Modal/ModalManager.ts | 314 -------- packages/mui-material/src/Modal/useModal.ts | 243 ------ .../mui-material/src/Modal/useModal.types.ts | 123 --- .../mui-material/src/NoSsr/NoSsr.test.tsx | 56 -- .../mui-material/src/NoSsr/NoSsr.types.ts | 19 - .../mui-material/src/Popper/BasePopper.tsx | 539 ------------- .../src/Popper/BasePopper.types.ts | 155 ---- .../mui-material/src/Portal/Portal.test.tsx | 220 ----- packages/mui-material/src/Portal/Portal.tsx | 108 --- .../mui-material/src/Portal/Portal.types.ts | 24 - packages/mui-material/src/Slider/useSlider.ts | 749 ------------------ .../src/Slider/useSlider.types.ts | 259 ------ .../src/Snackbar/useSnackbar.test.tsx | 54 -- .../mui-material/src/Snackbar/useSnackbar.ts | 166 ---- .../src/Snackbar/useSnackbar.types.ts | 66 -- .../TextareaAutosize.test.tsx | 448 ----------- .../src/TextareaAutosize/TextareaAutosize.tsx | 264 ------ .../TextareaAutosize.types.ts | 15 - .../src/Unstable_TrapFocus/FocusTrap.test.tsx | 410 ---------- .../src/Unstable_TrapFocus/FocusTrap.tsx | 426 ---------- .../src/Unstable_TrapFocus/FocusTrap.types.ts | 52 -- .../useAutocomplete/useAutocomplete.spec.ts | 184 ----- .../useAutocomplete/useAutocomplete.test.js | 380 --------- 28 files changed, 6500 deletions(-) delete mode 100644 packages/mui-material/src/Badge/useBadge.ts delete mode 100644 packages/mui-material/src/Badge/useBadge.types.ts delete mode 100644 packages/mui-material/src/ClickAwayListener/ClickAwayListener.test.js delete mode 100644 packages/mui-material/src/ClickAwayListener/ClickAwayListener.tsx delete mode 100644 packages/mui-material/src/Modal/ModalManager.test.ts delete mode 100644 packages/mui-material/src/Modal/ModalManager.ts delete mode 100644 packages/mui-material/src/Modal/useModal.ts delete mode 100644 packages/mui-material/src/Modal/useModal.types.ts delete mode 100644 packages/mui-material/src/NoSsr/NoSsr.test.tsx delete mode 100644 packages/mui-material/src/NoSsr/NoSsr.types.ts delete mode 100644 packages/mui-material/src/Popper/BasePopper.tsx delete mode 100644 packages/mui-material/src/Popper/BasePopper.types.ts delete mode 100644 packages/mui-material/src/Portal/Portal.test.tsx delete mode 100644 packages/mui-material/src/Portal/Portal.tsx delete mode 100644 packages/mui-material/src/Portal/Portal.types.ts delete mode 100644 packages/mui-material/src/Slider/useSlider.ts delete mode 100644 packages/mui-material/src/Slider/useSlider.types.ts delete mode 100644 packages/mui-material/src/Snackbar/useSnackbar.test.tsx delete mode 100644 packages/mui-material/src/Snackbar/useSnackbar.ts delete mode 100644 packages/mui-material/src/Snackbar/useSnackbar.types.ts delete mode 100644 packages/mui-material/src/TextareaAutosize/TextareaAutosize.test.tsx delete mode 100644 packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx delete mode 100644 packages/mui-material/src/TextareaAutosize/TextareaAutosize.types.ts delete mode 100644 packages/mui-material/src/Unstable_TrapFocus/FocusTrap.test.tsx delete mode 100644 packages/mui-material/src/Unstable_TrapFocus/FocusTrap.tsx delete mode 100644 packages/mui-material/src/Unstable_TrapFocus/FocusTrap.types.ts delete mode 100644 packages/mui-material/src/useAutocomplete/useAutocomplete.spec.ts delete mode 100644 packages/mui-material/src/useAutocomplete/useAutocomplete.test.js diff --git a/packages/mui-material/src/Badge/useBadge.ts b/packages/mui-material/src/Badge/useBadge.ts deleted file mode 100644 index 214f281d084c35..00000000000000 --- a/packages/mui-material/src/Badge/useBadge.ts +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; -import * as React from 'react'; -import { usePreviousProps } from '@mui/utils'; -import { UseBadgeParameters, UseBadgeReturnValue } from './useBadge.types'; - -/** - * - * Demos: - * - * - [Badge](https://next.mui.com/base-ui/react-badge/#hook) - * - * API: - * - * - [useBadge API](https://next.mui.com/base-ui/react-badge/hooks-api/#use-badge) - */ -function useBadge(parameters: UseBadgeParameters): UseBadgeReturnValue { - const { - badgeContent: badgeContentProp, - invisible: invisibleProp = false, - max: maxProp = 99, - showZero = false, - } = parameters; - - const prevProps = usePreviousProps({ - badgeContent: badgeContentProp, - max: maxProp, - }); - - let invisible = invisibleProp; - - if (invisibleProp === false && badgeContentProp === 0 && !showZero) { - invisible = true; - } - - const { badgeContent, max = maxProp } = invisible ? prevProps : parameters; - - const displayValue: React.ReactNode = - badgeContent && Number(badgeContent) > max ? `${max}+` : badgeContent; - - return { - badgeContent, - invisible, - max, - displayValue, - }; -} - -export default useBadge; diff --git a/packages/mui-material/src/Badge/useBadge.types.ts b/packages/mui-material/src/Badge/useBadge.types.ts deleted file mode 100644 index be6ef34830c5f8..00000000000000 --- a/packages/mui-material/src/Badge/useBadge.types.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface UseBadgeParameters { - /** - * The content rendered within the badge. - */ - badgeContent?: React.ReactNode; - /** - * If `true`, the badge is invisible. - * @default false - */ - invisible?: boolean; - /** - * Max count to show. - * @default 99 - */ - max?: number; - /** - * Controls whether the badge is hidden when `badgeContent` is zero. - * @default false - */ - showZero?: boolean; -} - -export interface UseBadgeReturnValue { - /** - * Defines the content that's displayed inside the badge. - */ - badgeContent: React.ReactNode; - /** - * If `true`, the component will not be visible. - */ - invisible: boolean; - /** - * Maximum number to be displayed in the badge. - */ - max: number; - /** - * Value to be displayed in the badge. If `badgeContent` is greater than `max`, it will return `max+`. - */ - displayValue: React.ReactNode; -} diff --git a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.test.js b/packages/mui-material/src/ClickAwayListener/ClickAwayListener.test.js deleted file mode 100644 index e0c4c3730a4a82..00000000000000 --- a/packages/mui-material/src/ClickAwayListener/ClickAwayListener.test.js +++ /dev/null @@ -1,441 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { expect } from 'chai'; -import { spy } from 'sinon'; -import { - act, - createRenderer, - fireEvent, - fireDiscreteEvent, - screen, -} from '@mui/internal-test-utils'; -import Portal from '@mui/material/Portal'; -import ClickAwayListener from '@mui/material/ClickAwayListener'; - -describe('', () => { - const { render: clientRender, clock } = createRenderer({ clock: 'fake' }); - /** - * @type {typeof plainRender extends (...args: infer T) => any ? T : never} args - * - * @remarks - * This is for all intents and purposes the same as our client render method. - * `plainRender` is already wrapped in act(). - * However, React has a bug that flushes effects in a portal synchronously. - * We have to defer the effect manually like `useEffect` would so we have to flush the effect manually instead of relying on `act()`. - * React bug: https://github.com/facebook/react/issues/20074 - */ - function render(...args) { - const result = clientRender(...args); - clock.tick(0); - return result; - } - - it('should render the children', () => { - const children = ; - const { container } = render( - {}}>{children}, - ); - expect(container.querySelectorAll('span').length).to.equal(1); - }); - - describe('prop: onClickAway', () => { - it('should be called when clicking away', () => { - const handleClickAway = spy(); - render( - - - , - ); - - fireEvent.click(document.body); - expect(handleClickAway.callCount).to.equal(1); - expect(handleClickAway.args[0].length).to.equal(1); - }); - - it('should not be called when clicking inside', () => { - const handleClickAway = spy(); - const { container } = render( - - - , - ); - - fireEvent.click(container.querySelector('span')); - expect(handleClickAway.callCount).to.equal(0); - }); - - it('should be called when preventDefault is `true`', () => { - const handleClickAway = spy(); - render( - - - , - ); - const preventDefault = (event) => event.preventDefault(); - document.body.addEventListener('click', preventDefault); - - fireEvent.click(document.body); - expect(handleClickAway.callCount).to.equal(1); - - document.body.removeEventListener('click', preventDefault); - }); - - it('should not be called when clicking inside a portaled element', () => { - const handleClickAway = spy(); - const { getByText } = render( - -
- - Inside a portal - -
-
, - ); - - fireEvent.click(getByText('Inside a portal')); - expect(handleClickAway.callCount).to.equal(0); - }); - - it('should be called when clicking inside a portaled element and `disableReactTree` is `true`', () => { - const handleClickAway = spy(); - const { getByText } = render( - -
- - Inside a portal - -
-
, - ); - - fireEvent.click(getByText('Inside a portal')); - expect(handleClickAway.callCount).to.equal(1); - }); - - it('should not be called even if the event propagation is stopped', () => { - const handleClickAway = spy(); - const { getByText } = render( - -
-
{ - event.stopPropagation(); - }} - > - Outside a portal -
- - { - event.stopPropagation(); - }} - > - Stop inside a portal - - - - { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - }} - > - Stop all inside a portal - - -
-
, - ); - - fireEvent.click(getByText('Outside a portal')); - expect(handleClickAway.callCount).to.equal(0); - - fireEvent.click(getByText('Stop all inside a portal')); - expect(handleClickAway.callCount).to.equal(0); - - fireEvent.click(getByText('Stop inside a portal')); - // undesired behavior in React 16 - expect(handleClickAway.callCount).to.equal(React.version.startsWith('16') ? 1 : 0); - }); - - ['onClick', 'onClickCapture'].forEach((eventListenerName) => { - it(`should not be called when ${eventListenerName} mounted the listener`, () => { - function Test() { - const [open, setOpen] = React.useState(false); - - return ( - - - {toggle ? : } - - ); - } - - render(); - const button = screen.getByRole('button'); - fireEvent.click(button); - await waitFor(() => { - expect(screen.queryByText('LazyRoute')).not.to.equal(null); - }); - }); - - // For https://github.com/mui/material-ui/pull/33253 - it('should update height without an infinite rendering loop', async () => { - function App() { - const [value, setValue] = React.useState('Controlled'); - - const handleChange = (event: React.ChangeEvent) => { - setValue(event.target.value); - }; - - return ; - } - const { container } = render(); - const input = container.querySelector('textarea')!; - act(() => { - input.focus(); - }); - const activeElement = document.activeElement!; - // set the value of the input to be 1 larger than its content width - fireEvent.change(activeElement, { - target: { value: 'Controlled\n' }, - }); - await sleep(0); - fireEvent.change(activeElement, { - target: { value: 'Controlled\n\n' }, - }); - }); - - // For https://github.com/mui/material-ui/pull/37135 - it('should update height without delay', async function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // It depends on ResizeObserver - this.skip(); - } - - function App() { - const ref = React.useRef(null); - return ( -
- -
- -
-
- ); - } - const { container } = render(); - const input = container.querySelector('textarea')!; - const button = screen.getByRole('button'); - expect(parseInt(input.style.height, 10)).to.be.within(30, 32); - fireEvent.click(button); - await raf(); - await raf(); - expect(parseInt(input.style.height, 10)).to.be.within(15, 17); - }); - - describe('layout', () => { - const getComputedStyleStub = new Map>(); - function setLayout( - input: HTMLTextAreaElement, - shadow: Element, - { - getComputedStyle, - scrollHeight: scrollHeightArg, - lineHeight: lineHeightArg, - }: { - getComputedStyle: Partial; - scrollHeight?: number | (() => number); - lineHeight?: number | (() => number); - }, - ) { - const lineHeight = typeof lineHeightArg === 'function' ? lineHeightArg : () => lineHeightArg; - const scrollHeight = - typeof scrollHeightArg === 'function' ? scrollHeightArg : () => scrollHeightArg; - - getComputedStyleStub.set(input, getComputedStyle); - - let index = 0; - stub(shadow, 'scrollHeight').get(() => { - index += 1; - return index % 2 === 1 ? scrollHeight() : lineHeight(); - }); - } - - before(function beforeHook() { - // Only run the test on node. - if (!/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - - stub(window, 'getComputedStyle').value( - (node: Element) => getComputedStyleStub.get(node) || {}, - ); - }); - - after(() => { - sinon.restore(); - }); - - describe('resize', () => { - clock.withFakeTimers(); - - it('should handle the resize event', () => { - const { container } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - - expect(input.style).to.have.property('height', '0px'); - expect(input.style).to.have.property('overflow', 'hidden'); - - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 30, - lineHeight: 15, - }); - window.dispatchEvent(new window.Event('resize', {})); - - clock.tick(166); - - expect(input.style).to.have.property('height', '30px'); - expect(input.style).to.have.property('overflow', 'hidden'); - }); - }); - - it('should update when uncontrolled', () => { - const handleChange = spy(); - const { container } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - expect(input.style).to.have.property('height', '0px'); - expect(input.style).to.have.property('overflow', 'hidden'); - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 30, - lineHeight: 15, - }); - act(() => { - input.focus(); - }); - const activeElement = document.activeElement!; - fireEvent.change(activeElement, { target: { value: 'a' } }); - expect(input.style).to.have.property('height', '30px'); - expect(input.style).to.have.property('overflow', 'hidden'); - expect(handleChange.callCount).to.equal(1); - }); - - it('should take the border into account with border-box', () => { - const border = 5; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - expect(input.style).to.have.property('height', '0px'); - expect(input.style).to.have.property('overflow', 'hidden'); - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - borderBottomWidth: `${border}px`, - }, - scrollHeight: 30, - lineHeight: 15, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${30 + border}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - }); - - it('should take the padding into account with content-box', () => { - const padding = 5; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - paddingTop: `${padding}px`, - }, - scrollHeight: 30, - lineHeight: 15, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${30 + padding}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - }); - - it('should have at least height of "minRows"', () => { - const minRows = 3; - const lineHeight = 15; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 30, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * minRows}px`); - expect(input.style).to.have.property('overflow', ''); - }); - - it('should have at max "maxRows" rows', () => { - const maxRows = 3; - const lineHeight = 15; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 100, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * maxRows}px`); - expect(input.style).to.have.property('overflow', ''); - }); - - it('should show scrollbar when having more rows than "maxRows"', () => { - const maxRows = 3; - const lineHeight = 15; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - }, - scrollHeight: lineHeight * 2, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * 2}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - }, - scrollHeight: lineHeight * 3, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * 3}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - }, - scrollHeight: lineHeight * 4, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * 3}px`); - expect(input.style).to.have.property('overflow', ''); - }); - - it('should update its height when the "maxRows" prop changes', () => { - const lineHeight = 15; - const { container, forceUpdate, setProps } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 100, - lineHeight, - }); - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * 3}px`); - expect(input.style).to.have.property('overflow', ''); - setProps({ maxRows: 2 }); - expect(input.style).to.have.property('height', `${lineHeight * 2}px`); - expect(input.style).to.have.property('overflow', ''); - }); - - it('should not sync height if container width is 0px', () => { - const lineHeight = 15; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: lineHeight * 2, - lineHeight, - }); - forceUpdate(); - - expect(input.style).to.have.property('height', `${lineHeight * 2}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - width: '0px', - }, - scrollHeight: lineHeight * 3, - lineHeight, - }); - - forceUpdate(); - expect(input.style).to.have.property('height', `${lineHeight * 2}px`); - expect(input.style).to.have.property('overflow', 'hidden'); - }); - - it('should compute the correct height if padding-right is greater than 0px', () => { - const paddingRight = 50; - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')! as HTMLTextAreaElement; - const contentWidth = 100; - const lineHeight = 15; - const width = contentWidth + paddingRight; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'border-box', - width: `${width}px`, - }, - scrollHeight: () => { - // assuming that the width of the word is 1px, and substract the width of the paddingRight - const lineNum = Math.ceil( - input.value.length / (width - getStyleValue(shadow.style.paddingRight)), - ); - return lineNum * lineHeight; - }, - lineHeight, - }); - - act(() => { - input.focus(); - }); - const activeElement = document.activeElement!; - // set the value of the input to be 1 larger than its content width - fireEvent.change(activeElement, { - target: { value: new Array(contentWidth + 1).fill('a').join('') }, - }); - forceUpdate(); - - // the input should be 2 lines - expect(input.style).to.have.property('height', `${lineHeight * 2}px`); - }); - }); - - it('should apply the inline styles using the "style" prop', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - - const { container } = render(); - const input = container.querySelector('textarea')!; - - expect(input).toHaveComputedStyle({ - backgroundColor: 'rgb(255, 255, 0)', - }); - }); -}); diff --git a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx b/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx deleted file mode 100644 index c284416fd79f87..00000000000000 --- a/packages/mui-material/src/TextareaAutosize/TextareaAutosize.tsx +++ /dev/null @@ -1,264 +0,0 @@ -'use client'; -import * as React from 'react'; -import PropTypes from 'prop-types'; -import { - unstable_debounce as debounce, - unstable_useForkRef as useForkRef, - unstable_useEnhancedEffect as useEnhancedEffect, - unstable_ownerWindow as ownerWindow, -} from '@mui/utils'; -import { TextareaAutosizeProps } from './TextareaAutosize.types'; - -function getStyleValue(value: string) { - return parseInt(value, 10) || 0; -} - -const styles: { - shadow: React.CSSProperties; -} = { - shadow: { - // Visibility needed to hide the extra text area on iPads - visibility: 'hidden', - // Remove from the content flow - position: 'absolute', - // Ignore the scrollbar width - overflow: 'hidden', - height: 0, - top: 0, - left: 0, - // Create a new layer, increase the isolation of the computed values - transform: 'translateZ(0)', - }, -}; - -type TextareaStyles = { - outerHeightStyle: number; - overflowing: boolean; -}; - -function isEmpty(obj: TextareaStyles) { - return ( - obj === undefined || - obj === null || - Object.keys(obj).length === 0 || - (obj.outerHeightStyle === 0 && !obj.overflowing) - ); -} - -/** - * - * Demos: - * - * - [Textarea Autosize](https://next.mui.com/material-ui/react-textarea-autosize/) - * - * API: - * - * - [TextareaAutosize API](https://next.mui.com/material-ui/api/textarea-autosize/) - */ -const TextareaAutosize = React.forwardRef(function TextareaAutosize( - props: TextareaAutosizeProps, - forwardedRef: React.ForwardedRef, -) { - const { onChange, maxRows, minRows = 1, style, value, ...other } = props; - - const { current: isControlled } = React.useRef(value != null); - const inputRef = React.useRef(null); - const handleRef = useForkRef(forwardedRef, inputRef); - const heightRef = React.useRef(null); - const shadowRef = React.useRef(null); - - const calculateTextareaStyles = React.useCallback(() => { - const input = inputRef.current!; - - const containerWindow = ownerWindow(input); - const computedStyle = containerWindow.getComputedStyle(input); - - // If input's width is shrunk and it's not visible, don't sync height. - if (computedStyle.width === '0px') { - return { - outerHeightStyle: 0, - overflowing: false, - }; - } - - const inputShallow = shadowRef.current!; - - inputShallow.style.width = computedStyle.width; - inputShallow.value = input.value || props.placeholder || 'x'; - if (inputShallow.value.slice(-1) === '\n') { - // Certain fonts which overflow the line height will cause the textarea - // to report a different scrollHeight depending on whether the last line - // is empty. Make it non-empty to avoid this issue. - inputShallow.value += ' '; - } - - const boxSizing = computedStyle.boxSizing; - const padding = - getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop); - const border = - getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth); - - // The height of the inner content - const innerHeight = inputShallow.scrollHeight; - - // Measure height of a textarea with a single row - inputShallow.value = 'x'; - const singleRowHeight = inputShallow.scrollHeight; - - // The height of the outer content - let outerHeight = innerHeight; - - if (minRows) { - outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight); - } - if (maxRows) { - outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight); - } - outerHeight = Math.max(outerHeight, singleRowHeight); - - // Take the box sizing into account for applying this value as a style. - const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0); - const overflowing = Math.abs(outerHeight - innerHeight) <= 1; - - return { outerHeightStyle, overflowing }; - }, [maxRows, minRows, props.placeholder]); - - const syncHeight = React.useCallback(() => { - const textareaStyles = calculateTextareaStyles(); - - if (isEmpty(textareaStyles)) { - return; - } - - const outerHeightStyle = textareaStyles.outerHeightStyle; - const input = inputRef.current!; - if (heightRef.current !== outerHeightStyle) { - heightRef.current = outerHeightStyle; - input.style.height = `${outerHeightStyle}px`; - } - input.style.overflow = textareaStyles.overflowing ? 'hidden' : ''; - }, [calculateTextareaStyles]); - - useEnhancedEffect(() => { - const handleResize = () => { - syncHeight(); - }; - // Workaround a "ResizeObserver loop completed with undelivered notifications" error - // in test. - // Note that we might need to use this logic in production per https://github.com/WICG/resize-observer/issues/38 - // Also see https://github.com/mui/mui-x/issues/8733 - let rAF: any; - const rAFHandleResize = () => { - cancelAnimationFrame(rAF); - rAF = requestAnimationFrame(() => { - handleResize(); - }); - }; - const debounceHandleResize = debounce(handleResize); - const input = inputRef.current!; - const containerWindow = ownerWindow(input); - - containerWindow.addEventListener('resize', debounceHandleResize); - - let resizeObserver: ResizeObserver; - - if (typeof ResizeObserver !== 'undefined') { - resizeObserver = new ResizeObserver( - process.env.NODE_ENV === 'test' ? rAFHandleResize : handleResize, - ); - resizeObserver.observe(input); - } - - return () => { - debounceHandleResize.clear(); - cancelAnimationFrame(rAF); - containerWindow.removeEventListener('resize', debounceHandleResize); - if (resizeObserver) { - resizeObserver.disconnect(); - } - }; - }, [calculateTextareaStyles, syncHeight]); - - useEnhancedEffect(() => { - syncHeight(); - }); - - const handleChange = (event: React.ChangeEvent) => { - if (!isControlled) { - syncHeight(); - } - - if (onChange) { - onChange(event); - } - }; - - return ( - -