diff --git a/CHANGELOG.md b/CHANGELOG.md index 6948cbe1..ba28ed4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ We try to follow [http://keepachangelog.com/](http://keepachangelog.com/) recomm ## [Unreleased] _(add items here for easier creation of next log entry)_ +### Added +- `props.renderWrapper` which allows a custom rendering function for the wrapper element of input and menu ## [1.8.1] - 2018-02-11 ### Fixed diff --git a/examples/custom-menu/app.js b/examples/custom-menu/app.js index 242fc244..7146718b 100644 --- a/examples/custom-menu/app.js +++ b/examples/custom-menu/app.js @@ -60,6 +60,13 @@ class App extends React.Component { ) : items} )} + renderWrapper={(wrapperStyles, wrapperProps, renderedInput, renderedMenu, renderedDebugInfo) => ( + + Enter value here: {renderedInput} + {renderedMenu} + {renderedDebugInfo} + + )} isItemSelectable={(item) => !item.header} /> diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index 1658c8bb..7facd8c0 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -132,6 +132,21 @@ class Autocomplete extends React.Component { * will win if it contains a `style` entry. */ wrapperStyle: PropTypes.object, + /** + * Arguments: `wrapperStyles: Object, wrapperProps: Object, + * renderedInput: Any, renderedMenu: Any` + * + * Invoked to generate the wrapper element. The `wrapperStyles` and + * `wrapperProps` objects are the corresponding properties of the + * `` component itself. + * `renderedInput` and `renderedMenu` are the generated input and + * menu elements. + * The default implementation uses a div-element as the wrapper with input + * and menu as direct children. You can use this function to change the + * wrapper, e.g. by using an inline-element like span or omit it completely + * by using a fragment (supported from react version 16). + */ + renderWrapper: PropTypes.func, /** * Whether or not to automatically highlight the top match in the dropdown * menu. @@ -175,6 +190,13 @@ class Autocomplete extends React.Component { renderMenu(items, value, style) { return
}, + renderWrapper(wrapperStyle, wrapperProps, renderedInput, renderedMenu, renderedDebugInfo) { + return (
+ {renderedInput} + {renderedMenu} + {renderedDebugInfo} +
) + }, menuStyle: { borderRadius: '3px', boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)', @@ -570,29 +592,29 @@ class Autocomplete extends React.Component { const { inputProps } = this.props const open = this.isOpen() - return ( -
- {this.props.renderInput({ - ...inputProps, - role: 'combobox', - 'aria-autocomplete': 'list', - 'aria-expanded': open, - autoComplete: 'off', - ref: this.exposeAPI, - onFocus: this.handleInputFocus, - onBlur: this.handleInputBlur, - onChange: this.handleChange, - onKeyDown: this.composeEventHandlers(this.handleKeyDown, inputProps.onKeyDown), - onClick: this.composeEventHandlers(this.handleInputClick, inputProps.onClick), - value: this.props.value, - })} - {open && this.renderMenu()} - {this.props.debug && ( + return this.props.renderWrapper( + this.props.wrapperStyle, + this.props.wrapperProps, + this.props.renderInput({ + ...inputProps, + role: 'combobox', + 'aria-autocomplete': 'list', + 'aria-expanded': open, + autoComplete: 'off', + ref: this.exposeAPI, + onFocus: this.handleInputFocus, + onBlur: this.handleInputBlur, + onChange: this.handleChange, + onKeyDown: this.composeEventHandlers(this.handleKeyDown, inputProps.onKeyDown), + onClick: this.composeEventHandlers(this.handleInputClick, inputProps.onClick), + value: this.props.value, + }), + open && this.renderMenu(), + this.props.debug && (
             {JSON.stringify(this._debugStates.slice(Math.max(0, this._debugStates.length - 5), this._debugStates.length), null, 2)}
           
- )} -
+ ) ) } } diff --git a/lib/__tests__/Autocomplete-test.js b/lib/__tests__/Autocomplete-test.js index ed5b79fb..5c511fd4 100644 --- a/lib/__tests__/Autocomplete-test.js +++ b/lib/__tests__/Autocomplete-test.js @@ -651,6 +651,28 @@ describe('Autocomplete mouse event handlers', () => { }) }) +describe('Autocomplete.props.renderWrapper', () => { + it('should be invoked in `render` to render the root element', () => { + const renderWrapper = jest.fn((wrapperStyles, wrapperProps, renderedInput, renderedMenu, renderedDebugInfo) => { + expect(wrapperStyles).toMatchSnapshot() + expect(wrapperProps).toMatchSnapshot() + return ( + + {renderedInput} + {renderedMenu} + {renderedDebugInfo} + + ) + }) + const tree = shallow(AutocompleteComponentJSX({ + value: 'pants', + renderWrapper, + })) + expect(renderWrapper).toHaveBeenCalledTimes(1) + expect(tree).toMatchSnapshot() + }) +}) + describe('Autocomplete.props.renderInput', () => { it('should be invoked in `render` to create the element', () => { const renderInput = jest.fn(props => { diff --git a/lib/__tests__/__snapshots__/Autocomplete-test.js.snap b/lib/__tests__/__snapshots__/Autocomplete-test.js.snap index 0eeecd50..8b2f3f6e 100644 --- a/lib/__tests__/__snapshots__/Autocomplete-test.js.snap +++ b/lib/__tests__/__snapshots__/Autocomplete-test.js.snap @@ -42,3 +42,28 @@ exports[`Autocomplete.props.renderInput should be invoked in \`render\` to creat
`; + +exports[`Autocomplete.props.renderWrapper should be invoked in \`render\` to render the root element 1`] = ` +Object { + "display": "inline-block", +} +`; + +exports[`Autocomplete.props.renderWrapper should be invoked in \`render\` to render the root element 2`] = `Object {}`; + +exports[`Autocomplete.props.renderWrapper should be invoked in \`render\` to render the root element 3`] = ` + + + +`;