From 10de3fbb81d07bae6bc5ab5db2c51aee6696662f Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 27 Jun 2019 17:00:27 +0200 Subject: [PATCH 01/12] [docs] Explain `inputRef` implementation concept --- .../components/text-fields/text-fields.md | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/src/pages/components/text-fields/text-fields.md b/docs/src/pages/components/text-fields/text-fields.md index 816f4b5aa85acc..17206917c09c90 100644 --- a/docs/src/pages/components/text-fields/text-fields.md +++ b/docs/src/pages/components/text-fields/text-fields.md @@ -109,14 +109,45 @@ or Count ``` -## Formatted inputs +## Integration with 3rd party input libraries You can use third-party libraries to format an input. You have to provide a custom implementation of the `` element with the `inputComponent` property. The provided input component should handle the `inputRef` property. -The property should be called with a value implementing the [`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) interface. +The property should be called with a value that implements the following interface: -The following demo uses the [react-text-mask](https://github.com/text-mask/text-mask) and [react-number-format](https://github.com/s-yadav/react-number-format) libraries. +```ts +interface InputElement { + focus(): void; +} +``` + +```jsx +function MyInputComponent(props) { + const { component: Component, inputRef, ...other } = props; + + // implement `InputElement` interface + React.useImperativeHandle(inputRef, () => ({ + focus: () => { + // logic to focus the rendered component from 3rd party belongs here + }, + })); + + // `Component` will be your `SomeThirdPartyComponent` from below + return ; +} + +// usage +; +``` + +The following demo uses the [react-text-mask](https://github.com/text-mask/text-mask) and [react-number-format](https://github.com/s-yadav/react-number-format) libraries. The same concept could +be applied to e.g. `react-stripe-element`. {{"demo": "pages/components/text-fields/FormattedInputs.js"}} From 0292d08192012acb0a897bc6c3f97d4939ffe9cf Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 27 Jun 2019 17:28:43 +0200 Subject: [PATCH 02/12] [InputBase] Improve error message for custom inputComponent --- docs/static/_redirects | 1 + packages/material-ui/src/InputBase/InputBase.js | 11 ++++++++++- .../material-ui/src/InputBase/InputBase.test.js | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/static/_redirects b/docs/static/_redirects index d8ab1b305e2e90..6484101efb3b04 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -41,6 +41,7 @@ https://material-ui.dev/* https://material-ui.com/:splat 301! /r/styles-instance-warning /getting-started/faq/#i-have-several-instances-of-styles-on-the-page 302 /r/caveat-with-refs-guide /guides/composition/#caveat-with-refs 302 /r/pseudo-classes-guide /customization/components/#pseudo-classes 302 +/r/inputComponent-ref-interface /components/text-fields/#formatted-inputs 302 # Legacy /v0.20.0 https://v0.material-ui.com/v0.20.0 diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js index 593c50acccc539..fc428b7681a883 100644 --- a/packages/material-ui/src/InputBase/InputBase.js +++ b/packages/material-ui/src/InputBase/InputBase.js @@ -277,8 +277,17 @@ const InputBase = React.forwardRef(function InputBase(props, ref) { const handleChange = (event, ...args) => { if (!isControlled) { + const element = event.target || inputRef.current; + if (element == null) { + throw new TypeError( + 'Material-UI: Expected valid input target. ' + + 'Did you use a custom `inputComponent` and forget to forward refs? ' + + 'See https://material-ui.com/r/inputComponent-ref-interface for more info.', + ); + } + checkDirty({ - value: (event.target || inputRef.current).value, + value: element.value, }); } diff --git a/packages/material-ui/src/InputBase/InputBase.test.js b/packages/material-ui/src/InputBase/InputBase.test.js index ff5b890f7916b0..4584a0bc9c03ce 100644 --- a/packages/material-ui/src/InputBase/InputBase.test.js +++ b/packages/material-ui/src/InputBase/InputBase.test.js @@ -182,6 +182,21 @@ describe('', () => { expect(typeof injectedProps.onBlur).to.equal('function'); expect(typeof injectedProps.onFocus).to.equal('function'); }); + + it('throws on change if `inputRef` isnt forwarded', () => { + // eslint-disable-next-line react/prop-types + const BadInputComponent = React.forwardRef(({ inputRef, ...props }, ref) => { + // `inputRef` belongs into `ref` + return ; + }); + + const wrapper = mount(); + assert.throws( + // simulating e.g. react-stripe-elements that hides the event target + () => wrapper.find('input').simulate('change', { target: null }), + 'Material-UI: Expected valid input target', + ); + }); }); describe('with FormControl', () => { From 6d059d88463081a1100ea7879135b42049192043 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 28 Jun 2019 10:26:24 +0200 Subject: [PATCH 03/12] Add value to interface --- docs/src/pages/components/text-fields/text-fields.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/pages/components/text-fields/text-fields.md b/docs/src/pages/components/text-fields/text-fields.md index 17206917c09c90..8e2ee7413d3b51 100644 --- a/docs/src/pages/components/text-fields/text-fields.md +++ b/docs/src/pages/components/text-fields/text-fields.md @@ -119,6 +119,7 @@ The property should be called with a value that implements the following interfa ```ts interface InputElement { focus(): void; + value?: string; } ``` @@ -131,6 +132,7 @@ function MyInputComponent(props) { focus: () => { // logic to focus the rendered component from 3rd party belongs here }, + // hiding the value e.g. react-stripe-elements })); // `Component` will be your `SomeThirdPartyComponent` from below From 732f154a4c15f4ed3339bbb038d9608725ae16b9 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 28 Jun 2019 10:26:40 +0200 Subject: [PATCH 04/12] inputComponet-ref-interface -> input-component-ref-interface --- docs/static/_redirects | 2 +- packages/material-ui/src/InputBase/InputBase.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/static/_redirects b/docs/static/_redirects index 6484101efb3b04..e78c0541d6a617 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -41,7 +41,7 @@ https://material-ui.dev/* https://material-ui.com/:splat 301! /r/styles-instance-warning /getting-started/faq/#i-have-several-instances-of-styles-on-the-page 302 /r/caveat-with-refs-guide /guides/composition/#caveat-with-refs 302 /r/pseudo-classes-guide /customization/components/#pseudo-classes 302 -/r/inputComponent-ref-interface /components/text-fields/#formatted-inputs 302 +/r/input-component-ref-interface /components/text-fields/#formatted-inputs 302 # Legacy /v0.20.0 https://v0.material-ui.com/v0.20.0 diff --git a/packages/material-ui/src/InputBase/InputBase.js b/packages/material-ui/src/InputBase/InputBase.js index fc428b7681a883..8cf1fc7ea1192d 100644 --- a/packages/material-ui/src/InputBase/InputBase.js +++ b/packages/material-ui/src/InputBase/InputBase.js @@ -282,7 +282,7 @@ const InputBase = React.forwardRef(function InputBase(props, ref) { throw new TypeError( 'Material-UI: Expected valid input target. ' + 'Did you use a custom `inputComponent` and forget to forward refs? ' + - 'See https://material-ui.com/r/inputComponent-ref-interface for more info.', + 'See https://material-ui.com/r/input-component-ref-interface for more info.', ); } From f17dedc5a4a8edeb236d98adeef47d3d217ca1f4 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 28 Jun 2019 10:27:20 +0200 Subject: [PATCH 05/12] Adjust redirect for new heading --- docs/static/_redirects | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/_redirects b/docs/static/_redirects index e78c0541d6a617..f1fa1913f49b09 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -41,7 +41,7 @@ https://material-ui.dev/* https://material-ui.com/:splat 301! /r/styles-instance-warning /getting-started/faq/#i-have-several-instances-of-styles-on-the-page 302 /r/caveat-with-refs-guide /guides/composition/#caveat-with-refs 302 /r/pseudo-classes-guide /customization/components/#pseudo-classes 302 -/r/input-component-ref-interface /components/text-fields/#formatted-inputs 302 +/r/input-component-ref-interface /components/text-fields/#integration-with-3rd-party-input-libraries 302 # Legacy /v0.20.0 https://v0.material-ui.com/v0.20.0 From 84a8a6c6354e268a9bdb84828026914c231abc68 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Fri, 28 Jun 2019 10:55:47 +0200 Subject: [PATCH 06/12] Add failing test for code being considered possible demo marker --- docs/src/modules/utils/parseMarkdown.test.js | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/src/modules/utils/parseMarkdown.test.js diff --git a/docs/src/modules/utils/parseMarkdown.test.js b/docs/src/modules/utils/parseMarkdown.test.js new file mode 100644 index 00000000000000..e801baa9a0a421 --- /dev/null +++ b/docs/src/modules/utils/parseMarkdown.test.js @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import { getContents } from './parseMarkdown'; + +describe('parseMarkdown', () => { + describe('getContents', () => { + describe('Split markdown into an array, separating demos', () => { + it('returns a single entry without a demo', () => { + expect(getContents('# SomeGuide\nwhich has no demo')).to.deep.equal([ + '# SomeGuide\nwhich has no demo', + ]); + }); + + it('uses a `{{"demo"` marker to split', () => { + expect( + getContents('# SomeGuide\n{{"demo": "GuideDemo.js" }}\n## NextHeading'), + ).to.deep.equal(['# SomeGuide\n', '"demo": "GuideDemo.js" ', '\n## NextHeading']); + }); + + it('ignores possible code', () => { + expect(getContents('# SomeGuide\n```jsx\n