Repository: mozilla-services/react-jsonschema-form (js)

Issue:
allow custom widget for oneOf / anyOf select
### Prerequisites

- [x] I have read the [documentation](https://react-jsonschema-form.readthedocs.io/);
- [ ] In the case of a bug report, I understand that providing a [SSCCE](http://sscce.org/) example is tremendously useful to the maintainers.
- [ ] Ideally, I'm providing a [sample JSFiddle](https://jsfiddle.net/n1k0/f2y3fq7L/6/) or a [shared playground link](https://mozilla-services.github.io/react-jsonschema-form/) demonstrating the issue.

### Description

When providing a custom select widget, I expected it to be used for the oneOf / anyOf select. The MultiSchemaField component just renders a `select` instead of looking up the select widget ([link](https://github.com/mozilla-services/react-jsonschema-form/blob/d9bcf1acfa3fd15814654129054b3c65501f5271/src/components/fields/MultiSchemaField.js#L160)). 

### Steps to Reproduce

1. Create a schema with a oneOf / anyOf property
2. Create a custom select widget and pass to the form: `<Form widgets={{ SelectWidget: MyCustomSelect }} />`
3. Observe that the select used for the oneOf / anyOf does not render the custom widget.

#### Expected behavior

I would expect that my custom widget would be rendered instead.

#### Actual behavior

A `select` element is used.

### Version

1.3.0


Pull request:
use select widget to render oneOf / anyOf control
### Reasons for making this change

Fixes #1219. oneOf / anyOf will now use the `SelectWidget` to render the top control. This can be customized by the user. 

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've [checked the rendering](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) of the Markdown text I've added
* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [ ] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) if needed
  - [x] I've run `npm run cs-format` on my branch to conform my code to [prettier](https://github.com/prettier/prettier) coding style
* [ ] **I'm adding a new feature**
  - [ ] I've updated the playground with an example use of the feature


Patch:
diff --git a/docs/advanced-customization.md b/docs/advanced-customization.md
index e74fb9eaa..69ec69fef 100644
--- a/docs/advanced-customization.md
+++ b/docs/advanced-customization.md
@@ -257,9 +257,11 @@ The following props are passed to custom widget components:
 - `id`: The generated id for this field;
 - `schema`: The JSONSchema subschema object for this field;
 - `value`: The current value for this field;
+- `placeholder`: the placeholder for the field, if any;
 - `required`: The required status of this field;
 - `disabled`: `true` if the widget is disabled;
 - `readonly`: `true` if the widget is read-only;
+- `autofocus`: `true` if the widget should autofocus;
 - `onChange`: The value change event handler; call it with the new value everytime it changes;
 - `onBlur`: The input blur event handler; call it with the the widget id and value;
 - `onFocus`: The input focus event handler; call it with the the widget id and value;
diff --git a/package.json b/package.json
index 8d7cff345..46349d113 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-jsonschema-form",
-  "version": "1.3.0",
+  "version": "1.4.0",
   "description": "A simple React component capable of building HTML forms out of a JSON schema.",
   "scripts": {
     "build:lib": "rimraf lib && cross-env NODE_ENV=production babel -d lib/ src/",
diff --git a/src/components/fields/MultiSchemaField.js b/src/components/fields/MultiSchemaField.js
index 325a1b985..6ecaa8276 100644
--- a/src/components/fields/MultiSchemaField.js
+++ b/src/components/fields/MultiSchemaField.js
@@ -1,7 +1,7 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import * as types from "../../types";
-import { guessType } from "../../utils";
+import { getUiOptions, getWidget, guessType } from "../../utils";
 import { isValid } from "../../validate";
 
 class AnyOfField extends Component {
@@ -85,8 +85,8 @@ class AnyOfField extends Component {
     return 0;
   }
 
-  onOptionChange = event => {
-    const selectedOption = parseInt(event.target.value, 10);
+  onOptionChange = option => {
+    const selectedOption = parseInt(option, 10);
     const { formData, onChange, options } = this.props;
 
     const newOption = options[selectedOption];
@@ -119,7 +119,7 @@ class AnyOfField extends Component {
     }
 
     this.setState({
-      selectedOption: parseInt(event.target.value, 10),
+      selectedOption: parseInt(option, 10),
     });
   };
 
@@ -141,7 +141,10 @@ class AnyOfField extends Component {
     } = this.props;
 
     const _SchemaField = registry.fields.SchemaField;
+    const { widgets } = registry;
     const { selectedOption } = this.state;
+    const { widget = "select", ...uiOptions } = getUiOptions(uiSchema);
+    const Widget = getWidget({ type: "number" }, widget, widgets);
 
     const option = options[selectedOption] || null;
     let optionSchema;
@@ -154,22 +157,24 @@ class AnyOfField extends Component {
         : Object.assign({}, option, { type: baseType });
     }
 
+    const enumOptions = options.map((option, index) => ({
+      label: option.title || `Option ${index + 1}`,
+      value: index,
+    }));
+
     return (
       <div className="panel panel-default panel-body">
         <div className="form-group">
-          <select
-            className="form-control"
+          <Widget
+            id={`${idSchema.$id}_anyof_select`}
+            schema={{ type: "number", default: 0 }}
             onChange={this.onOptionChange}
+            onBlur={onBlur}
+            onFocus={onFocus}
             value={selectedOption}
-            id={`${idSchema.$id}_anyof_select`}>
-            {options.map((option, index) => {
-              return (
-                <option key={index} value={index}>
-                  {option.title || `Option ${index + 1}`}
-                </option>
-              );
-            })}
-          </select>
+            options={{ enumOptions }}
+            {...uiOptions}
+          />
         </div>
 
         {option !== null && (


Test patch:
diff --git a/test/anyOf_test.js b/test/anyOf_test.js
index a2bd14b20..9f7cbf2b0 100644
--- a/test/anyOf_test.js
+++ b/test/anyOf_test.js
@@ -54,6 +54,36 @@ describe("anyOf", () => {
     expect(node.querySelectorAll("select")).to.have.length.of(1);
   });
 
+  it("should render a custom widget", () => {
+    const schema = {
+      type: "object",
+      anyOf: [
+        {
+          properties: {
+            foo: { type: "string" },
+          },
+        },
+        {
+          properties: {
+            bar: { type: "string" },
+          },
+        },
+      ],
+    };
+    const widgets = {
+      SelectWidget: () => {
+        return <section id="CustomSelect">Custom Widget</section>;
+      },
+    };
+
+    const { node } = createFormComponent({
+      schema,
+      widgets,
+    });
+
+    expect(node.querySelector("#CustomSelect")).to.exist;
+  });
+
   it("should change the rendered form when the select value is changed", () => {
     const schema = {
       type: "object",
diff --git a/test/oneOf_test.js b/test/oneOf_test.js
index a7589dae6..98ba91ed6 100644
--- a/test/oneOf_test.js
+++ b/test/oneOf_test.js
@@ -54,6 +54,36 @@ describe("oneOf", () => {
     expect(node.querySelectorAll("select")).to.have.length.of(1);
   });
 
+  it("should render a custom widget", () => {
+    const schema = {
+      type: "object",
+      oneOf: [
+        {
+          properties: {
+            foo: { type: "string" },
+          },
+        },
+        {
+          properties: {
+            bar: { type: "string" },
+          },
+        },
+      ],
+    };
+    const widgets = {
+      SelectWidget: () => {
+        return <section id="CustomSelect">Custom Widget</section>;
+      },
+    };
+
+    const { node } = createFormComponent({
+      schema,
+      widgets,
+    });
+
+    expect(node.querySelector("#CustomSelect")).to.exist;
+  });
+
   it("should change the rendered form when the select value is changed", () => {
     const schema = {
       type: "object",