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

Issue:
Can't apply uiSchema to additionalProperties
### Prerequisites

- [x] I have read the [documentation](https://github.com/mozilla-services/react-jsonschema-form/blob/master/README.md#readme);
- [x] In the case of a bug report, I understand that providing a [SSCCE](http://sscce.org/) example is tremendously useful to the maintainers.

### Description

With arrays, you can apply a `uiSchema` to the items in the array using `items` and `additionalItems`. However, the same functionality does not seem to exist for objects, for example using `properties` and `additionalProperties`.

An example is, using the "Additional Properties" example on https://mozilla-services.github.io/react-jsonschema-form/, I can't find a way to set help text (or any other `uiSchema` properties) on all additional properties. You can set it only if you know the name of the property, so adding `"assKickCount": {"ui:help": "Help text"}` works in the example but the text disappears if you rename the key. There should be a way to pass the `uiSchema` down to all `additionalProperties`.

Pull request:
Fix uiSchema for additionalProperties
### Reasons for making this change

Fixes #1132:
- allows additionalProperties to be specified in uiSchema
- Document additionalProperties
- Add tests for additionalItems/additionalProperties uiSchema configuration
- Breaking change (but not sure if this is really a big deal @glasserc; the previous behavior might have been a bug) -- uiSchema for an additional property will now come from the additionalProperties attribute, not the attribute of the name of the additional property itself.
  - For example, if you previously had an additional property called "property1", you could style it using the "property1" attribute in the uiSchema. With this PR, the only way to style additional properties is through the "additionalProperties" attribute of the uiSchema, regardless of the value of "property1" in the uiSchema.
### Checklist

* [x] **I'm updating documentation**
  - [x] I've checked the rendering of the Markdown text I've added
  - [x] If I'm adding a new section, I've updated the Table of Content
* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [x] I've updated docs 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/form-customization.md b/docs/form-customization.md
index a6740a0df..07f77d938 100644
--- a/docs/form-customization.md
+++ b/docs/form-customization.md
@@ -248,11 +248,23 @@ const uiSchema = {
 };
 ```
 
-### Object item options
+### Object additional properties
 
-#### `expandable` option
+You can define `additionalProperties` by setting its value to a schema object, such as the following:
 
-If `additionalProperties` contains a schema object, an add button for new properties is shown by default. The UX for editing properties whose names are user-defined is still experimental.
+```js
+const schema = {
+  "type": "object",
+  "properties": {"type": "string"},
+  "additionalProperties": {"type": "number"}
+}
+```
+
+In this way, an add button for new properties is shown by default. The UX for editing properties whose names are user-defined is still experimental.
+
+You can also define `uiSchema` options for `additionalProperties` by setting the `additionalProperties` attribute in the `uiSchema`.
+
+#### `expandable` option
 
 You can turn support for `additionalProperties` off with the `expandable` option in `uiSchema`:
 
diff --git a/src/components/Form.js b/src/components/Form.js
index 6863d7437..87b96c0e0 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -250,11 +250,11 @@ export default class Form extends Component {
         {children ? (
           children
         ) : (
-          <p>
+          <div>
             <button type="submit" className="btn btn-info">
               Submit
             </button>
-          </p>
+          </div>
         )}
       </form>
     );
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js
index 9eb1732d8..b6514d82e 100644
--- a/src/components/fields/ObjectField.js
+++ b/src/components/fields/ObjectField.js
@@ -213,7 +213,6 @@ class ObjectField extends Component {
     }
 
     const Template = registry.ObjectFieldTemplate || DefaultObjectFieldTemplate;
-
     const templateProps = {
       title: uiSchema["ui:title"] || title,
       description,
@@ -230,7 +229,11 @@ class ObjectField extends Component {
               name={name}
               required={this.isRequired(name)}
               schema={schema.properties[name]}
-              uiSchema={uiSchema[name]}
+              uiSchema={
+                addedByAdditionalProperties
+                  ? uiSchema.additionalProperties
+                  : uiSchema[name]
+              }
               errorSchema={errorSchema[name]}
               idSchema={idSchema[name]}
               idPrefix={idPrefix}
diff --git a/src/validate.js b/src/validate.js
index 222b16328..80d599bd4 100644
--- a/src/validate.js
+++ b/src/validate.js
@@ -8,7 +8,7 @@ const ajv = new Ajv({
 // add custom formats
 ajv.addFormat(
   "data-url",
-  /^data:([a-z]+\/[a-z0-9-+.]+)?;name=(.*);base64,(.*)$/
+  /^data:([a-z]+\/[a-z0-9-+.]+)?;(?:name=(.*);)?base64,(.*)$/
 );
 ajv.addFormat(
   "color",


Test patch:
diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js
index d111af65a..da17bf630 100644
--- a/test/ArrayField_test.js
+++ b/test/ArrayField_test.js
@@ -1162,6 +1162,22 @@ describe("ArrayField", () => {
       expect(addInput.value).eql("bar");
     });
 
+    it("should apply uiSchema to additionalItems", () => {
+      const { node } = createFormComponent({
+        schema: schemaAdditional,
+        uiSchema: {
+          additionalItems: {
+            "ui:title": "Custom title",
+          },
+        },
+        formData: [1, 2, "bar"],
+      });
+      const label = node.querySelector(
+        "fieldset .field-string label.control-label"
+      );
+      expect(label.textContent).eql("Custom title*");
+    });
+
     it("should have an add button if additionalItems is an object", () => {
       const { node } = createFormComponent({ schema: schemaAdditional });
       expect(node.querySelector(".array-item-add button")).not.to.be.null;
diff --git a/test/ObjectField_test.js b/test/ObjectField_test.js
index c9e83611b..afd1fb04c 100644
--- a/test/ObjectField_test.js
+++ b/test/ObjectField_test.js
@@ -412,6 +412,23 @@ describe("ObjectField", () => {
       expect(node.querySelectorAll(".field-string")).to.have.length.of(1);
     });
 
+    it("should apply uiSchema to additionalProperties", () => {
+      const { node } = createFormComponent({
+        schema,
+        uiSchema: {
+          additionalProperties: {
+            "ui:title": "CustomName",
+          },
+        },
+        formData: {
+          property1: "test",
+        },
+      });
+      const labels = node.querySelectorAll("label.control-label");
+      expect(labels[0].textContent).eql("CustomName Key");
+      expect(labels[1].textContent).eql("CustomName");
+    });
+
     it("should pass through non-schema properties and not throw validation errors if additionalProperties is undefined", () => {
       const undefinedAPSchema = {
         ...schema,
diff --git a/test/validate_test.js b/test/validate_test.js
index cb437e894..072ed61ec 100644
--- a/test/validate_test.js
+++ b/test/validate_test.js
@@ -134,6 +134,32 @@ describe("Validation", () => {
       });
     });
 
+    describe("Data-Url validation", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          dataUrlWithName: { type: "string", format: "data-url" },
+          dataUrlWithoutName: { type: "string", format: "data-url" },
+        },
+      };
+
+      it("Data-Url with name is accepted", () => {
+        const formData = {
+          dataUrlWithName: "data:text/plain;name=file1.txt;base64,x=",
+        };
+        const result = validateFormData(formData, schema);
+        expect(result.errors).to.have.length.of(0);
+      });
+
+      it("Data-Url without name is accepted", () => {
+        const formData = {
+          dataUrlWithoutName: "data:text/plain;base64,x=",
+        };
+        const result = validateFormData(formData, schema);
+        expect(result.errors).to.have.length.of(0);
+      });
+    });
+
     describe("toErrorList()", () => {
       it("should convert an errorSchema into a flat list", () => {
         expect(