diff --git a/.gitignore b/.gitignore index 03c1b53ecb..b5009d0cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,130 @@ -npm-debug.log -node_modules +# Created by https://www.gitignore.io/api/osx,node,linux,windows +# Edit at https://www.gitignore.io/?templates=osx,node,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,node,linux,windows + +# Package specific files + build +_build +site dist lib yarn.lock + +# IDE +.vscode + +# Code coverage +coverage +.nyc_output diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..498baa3fb0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,15 @@ +# Community Participation Guidelines + +This repository is governed by Mozilla's code of conduct and etiquette guidelines. +For more details, please read the +[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). + +## How to Report +For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. + + diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 91aa129f11..757feefc6b 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,7 +1,8 @@ ### Prerequisites -- [ ] I have read the [documentation](https://github.com/mozilla-services/react-jsonschema-form/blob/master/README.md#readme); +- [ ] 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 @@ -13,8 +14,6 @@ 2. [Second Step] 3. [and so on...] -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. - #### Expected behavior [What you expected to happen] diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index ff7da6a167..212fe4cd99 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -7,11 +7,10 @@ If this is related to existing tickets, include links to them as well. ### Checklist * [ ] **I'm updating documentation** - - [ ] I've checked the rendering of the Markdown text I've added - - [ ] If I'm adding a new section, I've updated the Table of Content + - [ ] I've [checked the rendering](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) of the Markdown text I've added * [ ] **I'm adding or updating code** - [ ] I've added and/or updated tests - - [ ] I've updated docs if needed + - [ ] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) if needed - [ ] 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 diff --git a/README.md b/README.md index ca4428cf0b..1e7c658aa0 100644 --- a/README.md +++ b/README.md @@ -1,1889 +1,24 @@ react-jsonschema-form ===================== -[![Build Status](https://travis-ci.org/mozilla-services/react-jsonschema-form.svg)](https://travis-ci.org/mozilla-services/react-jsonschema-form) +[![Build Status](https://travis-ci.org/mozilla-services/react-jsonschema-form.svg?branch=master)](https://travis-ci.org/mozilla-services/react-jsonschema-form) A simple [React](http://facebook.github.io/react/) component capable of building HTML forms out of a [JSON schema](http://json-schema.org/) and using [Bootstrap](http://getbootstrap.com/) semantics by default. -A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) is hosted on gh-pages. - ![](http://i.imgur.com/M8ZCES5.gif) -## Table of Contents - - - [Philosophy](#philosophy) - - [Installation](#installation) - - [As a npm-based project dependency](#as-a-npm-based-project-dependency) - - [As a script served from a CDN](#as-a-script-served-from-a-cdn) - - [Usage](#usage) - - [Form initialization](#form-initialization) - - [Form event handlers](#form-event-handlers) - - [Form submission](#form-submission) - - [Form error event handler](#form-error-event-handler) - - [Form data changes](#form-data-changes) - - [Form field blur events](#form-field-blur-events) - - [Form field focus events](#form-field-focus-events) - - [Submit form programmatically](#submit-form-programmatically) - - [Form customization](#form-customization) - - [The uiSchema object](#the-uischema-object) - - [Alternative widgets](#alternative-widgets) - - [For boolean fields](#for-boolean-fields) - - [For string fields](#for-string-fields) - - [String formats](#string-formats) - - [For number and integer fields](#for-number-and-integer-fields) - - [Disabled fields](#disabled-fields) - - [Read-only fields](#read-only-fields) - - [Hidden widgets](#hidden-widgets) - - [File widgets](#file-widgets) - - [Multiple files](#multiple-files) - - [File widget input ref](#file-widget-input-ref) - - [Object fields ordering](#object-fields-ordering) - - [Object item options](#object-item-options) - - [expandable option](#expandable-option) - - [Array item options](#array-item-options) - - [orderable option](#orderable-option) - - [addable option](#addable-option) - - [removable option](#removable-option) - - [Custom CSS class names](#custom-css-class-names) - - [Custom labels for enum fields](#custom-labels-for-enum-fields) - - [Alternative JSON-Schema compliant approach](#alternative-json-schema-compliant-approach) - - [Disabled attribute for enum fields](#disabled-attribute-for-enum-fields) - - [Multiple choices list](#multiple-choices-list) - - [Autogenerated widget ids](#autogenerated-widget-ids) - - [Form action buttons](#form-action-buttons) - - [Help texts](#help-texts) - - [Title texts](#title-texts) - - [Description texts](#description-texts) - - [Auto focus](#auto-focus) - - [Textarea rows option](#textarea-rows-option) - - [Placeholders](#placeholders) - - [Field labels](#field-labels) - - [HTML5 Input Types](#html5-input-types) - - [Form attributes](#form-attributes) - - [Form disable](#form-disable) - - [Advanced customization](#advanced-customization) - - [Field template](#field-template) - - [Array Field Template](#array-field-template) - - [Object Field Template](#object-field-template) - - [Error List template](#error-list-template) - - [Id prefix](#id-prefix) - - [Custom widgets and fields](#custom-widgets-and-fields) - - [Custom widget components](#custom-widget-components) - - [Custom component registration](#custom-component-registration) - - [Custom widget options](#custom-widget-options) - - [Customizing widgets text input](#customizing-widgets-text-input) - - [Custom field components](#custom-field-components) - - [Field props](#field-props) - - [The registry object](#the-registry-object) - - [The formContext object](#the-formcontext-object) - - [Custom array field buttons](#custom-array-field-buttons) - - [Custom SchemaField](#custom-schemafield) - - [Customizing the default fields and widgets](#customizing-the-default-fields-and-widgets) - - [Custom titles](#custom-titles) - - [Custom descriptions](#custom-descriptions) - - [Form data validation](#form-data-validation) - - [Live validation](#live-validation) - - [HTML5 Validation](#html5-validation) - - [Custom validation](#custom-validation) - - [Custom error messages](#custom-error-messages) - - [Error List Display](#error-list-display) - - [The case of empty strings](#the-case-of-empty-strings) - - [Styling your forms](#styling-your-forms) - - [Schema definitions and references](#schema-definitions-and-references) - - [Property dependencies](#property-dependencies) - - [Unidirectional](#unidirectional) - - [Bidirectional](#bidirectional) - - [Schema dependencies](#schema-dependencies) - - [Conditional](#conditional) - - [Dynamic](#dynamic) - - [JSON Schema supporting status](#json-schema-supporting-status) - - [Tips and tricks](#tips-and-tricks) - - [Contributing](#contributing) - - [Coding style](#coding-style) - - [Development server](#development-server) - - [Tests](#tests) - - [TDD](#tdd) - - [Releasing](#releasing) - - [FAQ](#faq) - - [Q: Does rjsf support oneOf, anyOf, multiple types in an array, etc.?](#q-does-rjsf-support-oneof-anyof-multiple-types-in-an-array-etc) - - [Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]?](#q-will-react-jsonschema-form-support-material-ant-design-foundation-or-some-other-specific-widget-library-or-frontend-style) - - [License](#license) - ---- - -## Philosophy - -react-jsonschema-form is meant to automatically generate a React form based on a [JSON Schema](http://json-schema.org/). It is a major component in the [kinto-admin](https://github.com/Kinto/kinto-admin/) project. If you want to generate a form for any data, sight unseen, simply given a JSON schema, react-jsonschema-form may be for you. If you have _a priori_ knowledge of your data and want a toolkit for generating forms for it, you might look elsewhere. - -react-jsonschema-form validates that the data conforms to the given schema, but doesn't prevent the user from inputing data that doesn't fit (for example, stripping non-numbers from a number field, or adding values to an array that is already "full"). - -## Installation - -Requires React 15.0.0+. - -> Note: The `master` branch of the repository reflects ongoing development. Releases are published as [tags](https://github.com/mozilla-services/react-jsonschema-form/releases). You should never blindly install from `master`, but rather check what the available stable releases are. - - -### As a npm-based project dependency - -``` -$ npm install react-jsonschema-form --save -``` - -> Note: While the library renders [Bootstrap](http://getbootstrap.com/) HTML semantics, you have to build and load the Bootstrap styles on your own. - -### As a script served from a CDN - -```html - -``` - -Source maps are available at [this url](https://unpkg.com/react-jsonschema-form/dist/react-jsonschema-form.js.map). - -> Note: The CDN version **does not** embed `react` or `react-dom`. - -You'll also need to alias the default export property to use the Form component: - -```jsx -const Form = JSONSchemaForm.default; -// or -const {default: Form} = JSONSchemaForm; -``` - -## Usage - -```jsx -import React, { Component } from "react"; -import { render } from "react-dom"; - -import Form from "react-jsonschema-form"; - -const schema = { - title: "Todo", - type: "object", - required: ["title"], - properties: { - title: {type: "string", title: "Title", default: "A new task"}, - done: {type: "boolean", title: "Done?", default: false} - } -}; - -const log = (type) => console.log.bind(console, type); - -render(( -
-), document.getElementById("app")); -``` - -This will generate a form like this (assuming you loaded the standard [Bootstrap](http://getbootstrap.com/) stylesheet): - -![](http://i.imgur.com/DZQYPyu.png) - -### Form initialization - -Often you'll want to prefill a form with existing data; this is done by passing a `formData` prop object matching the schema: - -```jsx -const formData = { - title: "First task", - done: true -}; - -render(( - -), document.getElementById("app")); -``` - -> Note: If your form has a single field, pass a single value to `formData`. ex: `formData='Charlie'` - -> WARNING: If you have situations where your parent component can re-render, make sure you listen to the `onChange` event and update the data you pass to the `formData` attribute. - -### Form event handlers - -#### Form submission - -You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form data you're usually after: - -```js -const onSubmit = ({formData}) => console.log("Data submitted: ", formData); - -render(( - -), document.getElementById("app")); -``` - -#### Form error event handler - -To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of encountered errors: - -```js -const onError = (errors) => console.log("I have", errors.length, "errors to fix"); - -render(( - -), document.getElementById("app")); -``` - -#### Form data changes - -If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same args as `onSubmit` any time a value is updated in the form. - -#### Form field blur events - -Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass an `onBlur` handler, which will receive the id of the input that was blurred and the field value. - -#### Form field focus events - -Sometimes you may want to trigger events or modify external state when a field has been focused, so you can pass an `onFocus` handler, which will receive the id of the input that is focused and the field value. - -### Submit form programmatically -You can use the reference to get your `Form` component and call the `submit` method to submit the form programmatically without a submit button. -This method will dispatch the `submit` event of the form, and the function, that is passed to `onSubmit` props, will be called. - -```js -const onSubmit = ({formData}) => console.log("Data submitted: ", formData); -let yourForm; - -render(( - {yourForm = form;}}/> -), document.getElementById("app")); - -yourForm.submit(); -``` - -## Form customization - -### The `uiSchema` object - -JSONSchema is limited for describing how a given data type should be rendered as a form input component. That's why this lib introduces the concept of *UI schema*. - -A UI schema is basically an object literal providing information on **how** the form should be rendered, while the JSON schema tells **what**. - -The uiSchema object follows the tree structure of the form field hierarchy, and defines how each property should be rendered: - -```js -const schema = { - type: "object", - properties: { - foo: { - type: "object", - properties: { - bar: {type: "string"} - } - }, - baz: { - type: "array", - items: { - type: "object", - properties: { - description: { - "type": "string" - } - } - } - } - } -} - -const uiSchema = { - foo: { - bar: { - "ui:widget": "textarea" - }, - }, - baz: { - // note the "items" for an array - items: { - description: { - "ui:widget": "textarea" - } - } - } -} - -render(( - -), document.getElementById("app")); -``` - -### Alternative widgets - -The uiSchema `ui:widget` property tells the form which UI widget should be used to render a field. - -Example: - -```jsx -const uiSchema =  { - done: { - "ui:widget": "radio" // could also be "select" - } -}; - -render(( - -), document.getElementById("app")); -``` - -Here's a list of supported alternative widgets for different JSONSchema data types: - -#### For `boolean` fields - - * `radio`: a radio button group with `true` and `false` as selectable values; - * `select`: a select box with `true` and `false` as options; - * by default, a checkbox is used - -> Note: To set the labels for a boolean field, instead of using `true` and `false` you can set `enumNames` in your schema. Note that `enumNames` belongs in your `schema`, not the `uiSchema`, and the order is always `[true, false]`. - -#### For `string` fields - - * `textarea`: a `textarea` element is used; - * `password`: an `input[type=password]` element is used; - * `color`: an `input[type=color]` element is used; - * by default, a regular `input[type=text]` element is used. - -##### String formats - -The built-in string field also supports the JSONSchema `format` property, and will render an appropriate widget by default for the following string formats: - -- `email`: An `input[type=email]` element is used; -- `uri`: An `input[type=url]` element is used; -- `data-url`: By default, an `input[type=file]` element is used; in case the string is part of an array, multiple files will be handled automatically (see [File widgets](#file-widgets)). -- `date`: By default, an `input[type=date]` element is used; -- `date-time`: By default, an `input[type=datetime-local]` element is used. - -![](http://i.imgur.com/xqu6Lcp.png) - -Please note that, even though they are standardized, `datetime-local` and `date` input elements are not yet supported by Firefox and IE. If you plan on targeting these platforms, two alternative widgets are available: - -- `alt-datetime`: Six `select` elements are used to select the year, the month, the day, the hour, the minute and the second; -- `alt-date`: Three `select` elements are used to select the year, month and the day. - -![](http://i.imgur.com/VF5tY60.png) - -You can customize the list of years displayed in the `year` dropdown by providing a ``yearsRange`` property to ``ui:options`` in your uiSchema. Its also possible to remove the `Now` and `Clear` buttons with the `hideNowButton` and `hideClearButton` options. - -```jsx -uiSchema: { - a_date: { - "alt-datetime": { - "ui:widget": "alt-datetime", - "ui:options": { - yearsRange: [1980, 2030], - hideNowButton: true, - hideClearButton: true, - }, - }, - }, -}, -``` - -#### For `number` and `integer` fields - - * `updown`: an `input[type=number]` updown selector; - * `range`: an `input[type=range]` slider; - * `radio`: a radio button group with enum values. This can only be used when `enum` values are specified for this input. - * By default, a regular `input[type=text]` element is used. - -> Note: If JSONSchema's `minimum`, `maximum` and `multipleOf` values are defined, the `min`, `max` and `step` input attributes values will take those values. - -#### Disabled fields - -The `ui:disabled` uiSchema directive will disable all child widgets from a given field. - -#### Read-only fields - -The `ui:readonly` uiSchema directive will mark all child widgets from a given field as read-only. - -> Note: If you're wondering about the difference between a `disabled` field and a `readonly` one: Marking a field as read-only will render it greyed out, but its text value will be selectable. Disabling it will prevent its value to be selected at all. - -#### Hidden widgets - -It's possible to use a hidden widget for a field by setting its `ui:widget` uiSchema directive to `hidden`: - -```js -const schema = { - type: "object", - properties: { - foo: {type: "boolean"} - } -}; - -const uiSchema = { - foo: {"ui:widget": "hidden"} -}; -``` - -Notes: - - - Hiding widgets is only supported for `boolean`, `string`, `number` and `integer` schema types; - - A hidden widget takes its value from the `formData` prop. - -#### File widgets - -This library supports a limited form of `input[type=file]` widgets, in the sense that it will propagate file contents to form data state as [data-url](http://dataurl.net/#about)s. - -There are two ways to use file widgets. - -1. By declaring a `string` json schema type along a `data-url` [format](#string-formats): -```js -const schema = { - type: "string", - format: "data-url", -}; -``` - -2. By specifying a `ui:widget` field uiSchema directive as `file`: -```js -const schema = { - type: "string", -}; - -const uiSchema = { - "ui:widget": "file", -}; -``` - -##### Multiple files - -Multiple files selectors are supported by defining an array of strings having `data-url` as a format: - -```js -const schema = { - type: "array", - items: { - type: "string", - format: "data-url", - } -}; -``` - -> Note that storing large dataURIs into form state might slow rendering. - -##### File widget input ref - -The included `FileWidget` exposes a reference to the `` element node as an `inputRef` component property. - -This allows you to programmatically trigger the browser's file selector, which can be used in a custom file widget. - -### Object fields ordering - -Since the order of object properties in Javascript and JSON is not guaranteed, the `uiSchema` object spec allows you to define the order in which properties are rendered using the `ui:order` property: - -```jsx -const schema = { - type: "object", - properties: { - foo: {type: "string"}, - bar: {type: "string"} - } -}; - -const uiSchema = { - "ui:order": ["bar", "foo"] -}; - -render(( - -), document.getElementById("app")); -``` - -If a guaranteed fixed order is only important for some fields, you can insert a wildcard `"*"` item in your `ui:order` definition. All fields that are not referenced explicitly anywhere in the list will be rendered at that point: - -```js -const uiSchema = { - "ui:order": ["bar", "*"] -}; -``` - -### Object item options - -#### `expandable` option - -If `additionalProperties` contains a schema object, an add button for new properies is shown by default. The UX for editing properties whose names are user-defined is still experimental. - -You can turn support for `additionalProperties` off with the `expandable` option in `uiSchema`: - -```jsx -const uiSchema = { - "ui:options": { - expandable: false - } -}; -``` - -### Array item options - -#### `orderable` option - -Array items are orderable by default, and react-jsonschema-form renders move up/down buttons alongside them. The `uiSchema` object spec allows you to disable ordering: - -```jsx -const schema = { - type: "array", - items: { - type: "string" - } -}; - -const uiSchema = { - "ui:options": { - orderable: false - } -}; -``` - -#### `addable` option - -If either `items` or `additionalItems` contains a schema object, an add button for new items is shown by default. You can turn this off with the `addable` option in `uiSchema`: - -```jsx -const uiSchema = { - "ui:options": { - addable: false - } -}; -``` - -#### `removable` option - -A remove button is shown by default for an item if `items` contains a schema object, or the item is an `additionalItems` instance. You can turn this off with the `removable` option in `uiSchema`: - -```jsx -const uiSchema = { - "ui:options": { - removable: false - } -}; -``` - -### Custom CSS class names - -The uiSchema object accepts a `classNames` property for each field of the schema: - -```jsx -const uiSchema = { - title: { - classNames: "task-title foo-bar" - } -}; -``` - -Will result in: - -```html -
- -
-``` - -### Custom labels for `enum` fields - -This library supports the [`enumNames`](https://github.com/json-schema/json-schema/wiki/enumNames-%28v5-proposal%29) property for `enum` fields, which allows defining custom labels for each option of an `enum`: - -```js -const schema = { - type: "number", - enum: [1, 2, 3], - enumNames: ["one", "two", "three"] -}; -``` - -This will be rendered using a select box like this: - -```html - -``` - -Note that string representations of numbers will be cast back and reflected as actual numbers into form state. - -#### Alternative JSON-Schema compliant approach - -JSON Schema has an alternative approach to enumerations; react-jsonschema-form supports it as well. - -```js -const schema = { - "type": "number", - "anyOf": [ - { - "type": "number", - "title": "one", - "enum": [ - 1 - ] - }, - { - "type": "number", - "title": "two", - "enum": [ - 2 - ] - }, - { - "type": "number", - "title": "three", - "enum": [ - 3 - ] - } - ] -}; -``` - -This will be rendered as follows: - -```html - -``` - -A live example of both approaches side-by-side can be found in the **Alternatives** tab of the [playground](https://mozilla-services.github.io/react-jsonschema-form/). - -### Disabled attribute for `enum` fields - -To disable an option, use the `enumDisabled` property in uiSchema. - -```js -const schema = { - type: "string", - enum: ["one", "two", "three"], -}; - -const uiSchema={ - "ui:enumDisabled": ['two'], -} -``` - -This will be rendered using a select box as follows: - -```html - -``` - -### Multiple-choice list - -The default behavior for array fields is a list of text inputs with add/remove buttons. There are two alternative widgets for picking multiple elements from a list of choices. Typically this applies when a schema has an `enum` list for the `items` property of an `array` field, and the `uniqueItems` property set to `true`. - -Example: - -```js -const schema = { - type: "array", - title: "A multiple-choice list", - items: { - type: "string", - enum: ["foo", "bar", "fuzz", "qux"], - }, - uniqueItems: true -}; -``` - -By default, this will render a multiple select box. If you prefer a list of checkboxes, just set the uiSchema `ui:widget` directive to `checkboxes` for that field: - -```js -const uiSchema = { - "ui:widget": "checkboxes" -}; -``` - -Note that when an array property is marked as `required`, an empty array is considered valid. If array needs to be populated, you can specify the minimum number of items using the `minItems` property. - -Example: - -```js -const schema = { - type: "array", - minItems: 2, - title: "A multiple-choice list", - items: { - type: "string", - enum: ["foo", "bar", "fuzz", "qux"], - }, - uniqueItems: true -}; -``` - -By default, checkboxes are stacked. If you prefer them inline, set the `inline` property to `true`: - -```js -const uiSchema = { - "ui:widget": "checkboxes", - "ui:options": { - inline: true - } -}; -``` - -See the "Arrays" section of the [playground](https://mozilla-services.github.io/react-jsonschema-form/) for cool demos. - -### Autogenerated widget ids - -By default, this library will generate ids unique to the form for all rendered widgets. If you plan on using multiple instances of the `Form` component in a same page, it's wise to declare a root prefix for these, using the `ui:rootFieldId` uiSchema directive: - -```js -const uiSchema = { - "ui:rootFieldId": "myform" -}; -``` - -So all widgets will have an id prefixed with `myform`. - -### Form action buttons - -You can provide custom buttons to your form via the `Form` component's `children`. Otherwise a default submit button will be rendered. - -```jsx -render(( - -
- - -
-
-), document.getElementById("app")); -``` - -> **Warning:** There needs to be a button or an input with `type="submit"` to trigger the form submission (and then the form validation). - -### Help text - -Sometimes it's convenient to add text next to a field to guide the end user filling it. This is the purpose of the `ui:help` uiSchema directive: - -```js -const schema = {type: "string"}; -const uiSchema = { - "ui:widget": "password", - "ui:help": "Hint: Make it strong!" -}; -``` - -![](http://i.imgur.com/scJUuZo.png) - -Help texts work for any kind of field at any level, and will always be rendered immediately below the field component widget(s) (after contextualized errors, if any). - -### Title texts - -Sometimes it's convenient to change a field's title. this is the purpose of the `ui:title` uiSchema directive: - -```js -const schema = {type: "string"}; -const uiSchema = { - "ui:widget": "password", - "ui:title": "Your password" -}; -``` - -### Description texts - -Sometimes it's convenient to change description a field. This is the purpose of the `ui:description` uiSchema directive: - -```js -const schema = {type: "string"}; -const uiSchema = { - "ui:widget": "password", - "ui:description": "The best password" -}; -``` - -### Auto focus - -If you want to automatically focus on a text input or textarea input, set the `ui:autofocus` uiSchema directive to `true`. - -```js -const schema = {type: "string"}; -const uiSchema = { - "ui:widget": "textarea", - "ui:autofocus": true -} -``` - -### Textarea `rows` option - -You can set the initial height of a textarea widget by specifying `rows` option. - -```js -const schema = {type: "string"}; -const uiSchema = { - "ui:widget": "textarea", - "ui:options": { - rows: 15 - } -} -``` - -### Placeholders - -You can add placeholder text to an input by using the `ui:placeholder` uiSchema directive: - -```jsx -const schema = {type: "string", format: "uri"}; -const uiSchema = { - "ui:placeholder": "http://" -}; -``` - -![](http://i.imgur.com/MbHypKg.png) - -Fields using `enum` can also use `ui:placeholder`. The value will be used as the text for the empty option in the select widget. - -```jsx -const schema = {type: "string", enum: ["First", "Second"]}; -const uiSchema = { - "ui:placeholder": "Choose an option" -}; -``` - -### Field labels - -Field labels are rendered by default. Labels may be omitted by setting the `label` option to `false` in the `ui:options` uiSchema directive. - -```jsx -const schema = {type: "string"}; -const uiSchema = { - "ui:options": { - label: false - } -}; -``` - -### HTML5 Input Types - -To change the input type (for example, `tel` or `email`) you can specify the `inputType` in the `ui:options` uiSchema directive. - -```jsx -const schema = {type: "string"}; -const uiSchema = { - "ui:options": { - inputType: 'tel' - } -}; -``` - -### Form attributes - -The `Form` component supports the following html attributes: - -```jsx -
-``` - -### Form disable - -Its possible to disable the whole form by setting the `disabled` prop. The `disabled` prop is then forwarded down thru each field of the form. - -```jsx - -``` - -If you just want to disable some of the fields see the `ui:disabled` parameter in the uiSchema directive. - -## Advanced customization - - -_ | Custom Field | Custom Template | Custom Widget ---|---------- | ------------- | ---- -What it does | Overrides all behaviour | Overrides just the layout | Overrides just the input box (not layout, labels, or help, or validation) -Usage | Global or per-field | Only global | Global or per-field -Global Example | `` | `` | `` -Per-Field Example | `"ui:field": MyField` | N/A | `"ui:widget":MyWidget` -Documentation | [Field](#field-props) | [Field Template](#field-template) - [Array Template](#array-field-template) - [Object Template](#object-field-template) - [Error List Template](#error-list-template) | [Custom Widgets](#custom-widget-components) - -### Field template - -To take control over the inner organization of each field (each form row), you can define a *field template* for your form. - -A field template is basically a React stateless component being passed field-related props, allowing you to structure your form row as you like. - -```jsx -function CustomFieldTemplate(props) { - const {id, classNames, label, help, required, description, errors, children} = props; - return ( -
- - {description} - {children} - {errors} - {help} -
- ); -} - -render(( - , -), document.getElementById("app")); -``` - -If you want to handle the rendering of each element yourself, you can use the props `rawHelp`, `rawDescription` and `rawErrors`. +Testing powered by BrowserStack
+ -The following props are passed to a custom field template component: -- `id`: The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget. -- `classNames`: A string containing the base Bootstrap CSS classes, merged with any [custom ones](#custom-css-class-names) defined in your uiSchema. -- `label`: The computed label for this field, as a string. -- `description`: A component instance rendering the field description, if one is defined (this will use any [custom `DescriptionField`](#custom-descriptions) defined). -- `rawDescription`: A string containing any `ui:description` uiSchema directive defined. -- `children`: The field or widget component instance for this field row. -- `errors`: A component instance listing any encountered errors for this field. -- `rawErrors`: An array of strings listing all generated error messages from encountered errors for this field. -- `help`: A component instance rendering any `ui:help` uiSchema directive defined. -- `rawHelp`: A string containing any `ui:help` uiSchema directive defined. **NOTE:** `rawHelp` will be `undefined` if passed `ui:help` is a React component instead of a string. -- `hidden`: A boolean value stating if the field should be hidden. -- `required`: A boolean value stating if the field is required. -- `readonly`: A boolean value stating if the field is read-only. -- `disabled`: A boolean value stating if the field is disabled. -- `displayLabel`: A boolean value stating if the label should be rendered or not. This is useful for nested fields in arrays where you don't want to clutter the UI. -- `fields`: An array containing all Form's fields including your [custom fields](#custom-field-components) and the built-in fields. -- `schema`: The schema object for this field. -- `uiSchema`: The uiSchema object for this field. -- `formContext`: The `formContext` object that you passed to Form. +## Documentation +Documentation is hosted on: https://react-jsonschema-form.readthedocs.io/ -> Note: you can only define a single field template for a form. If you need many, it's probably time to look at [custom fields](#custom-field-components) instead. - -### Array Field Template - -Similarly to the `FieldTemplate` you can use an `ArrayFieldTemplate` to customize how your -arrays are rendered. This allows you to customize your array, and each element in the array. - -```jsx -function ArrayFieldTemplate(props) { - return ( -
- {props.items.map(element => element.children)} - {props.canAdd && } -
- ); -} - -render(( - , -), document.getElementById("app")); -``` - -Please see [customArray.js](https://github.com/mozilla-services/react-jsonschema-form/blob/master/playground/samples/customArray.js) for a better example. - -The following props are passed to each `ArrayFieldTemplate`: - -- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) -- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). -- `canAdd`: A boolean value stating whether new elements can be added to the array. -- `className`: The className string. -- `disabled`: A boolean value stating if the array is disabled. -- `idSchema`: Object -- `items`: An array of objects representing the items in the array. Each of the items represent a child with properties described below. -- `onAddClick: (event) => void`: A function that adds a new item to the array. -- `readonly`: A boolean value stating if the array is read-only. -- `required`: A boolean value stating if the array is required. -- `schema`: The schema object for this array. -- `uiSchema`: The uiSchema object for this array field. -- `title`: A string value containing the title for the array. -- `formContext`: The `formContext` object that you passed to Form. -- `formData`: The formData for this array. - -The following props are part of each element in `items`: - -- `children`: The html for the item's content. -- `className`: The className string. -- `disabled`: A boolean value stating if the array item is disabled. -- `hasMoveDown`: A boolean value stating whether the array item can be moved down. -- `hasMoveUp`: A boolean value stating whether the array item can be moved up. -- `hasRemove`: A boolean value stating whether the array item can be removed. -- `hasToolbar`: A boolean value stating whether the array item has a toolbar. -- `index`: A number stating the index the array item occurs in `items`. -- `onDropIndexClick: (index) => (event) => void`: Returns a function that removes the item at `index`. -- `onReorderClick: (index, newIndex) => (event) => void`: Returns a function that swaps the items at `index` with `newIndex`. -- `readonly`: A boolean value stating if the array item is read-only. - -### Object Field Template - -Similarly to the `FieldTemplate` you can use an `ObjectFieldTemplate` to customize how your -objects are rendered. - -```jsx -function ObjectFieldTemplate(props) { - return ( -
- {props.title} - {props.description} - {props.properties.map(element =>
{element.content}
)} -
- ); -} - -render(( - , -), document.getElementById("app")); -``` - -Please see [customObject.js](https://github.com/mozilla-services/react-jsonschema-form/blob/master/playground/samples/customObject.js) for a better example. - -The following props are passed to each `ObjectFieldTemplate`: - -- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) -- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). -- `title`: A string value containing the title for the object. -- `description`: A string value containing the description for the object. -- `properties`: An array of object representing the properties in the array. Each of the properties represent a child with properties described below. -- `required`: A boolean value stating if the object is required. -- `schema`: The schema object for this object. -- `uiSchema`: The uiSchema object for this object field. -- `idSchema`: An object containing the id for this object & ids for it's properties. -- `formData`: The form data for the object. -- `formContext`: The `formContext` object that you passed to Form. - -The following props are part of each element in `properties`: - -- `content`: The html for the property's content. -- `name`: A string representing the property name. -- `disabled`: A boolean value stating if the object property is disabled. -- `readonly`: A boolean value stating if the property is read-only. - -### Error List template - -To take control over how the form errors are displayed, you can define an *error list template* for your form. This list is the form global error list that appears at the top of your forms. - -An error list template is basically a React stateless component being passed errors as props so you can render them as you like: - -```jsx -function ErrorListTemplate(props) { - const {errors} = props; - return ( -
- {errors.map((error, i) => { - return ( -
  • - {error.stack} -
  • - ); - })} -
    - ); -} - -render(( - , -), document.getElementById("app")); -``` - -> Note: Your custom `ErrorList` template will only render when `showErrorList` is `true`. - -The following props are passed to `ErrorList` - -- `errors`: An array of the errors. -- `errorSchema`: The errorSchema constructed by `Form`. -- `schema`: The schema that was passed to `Form`. -- `uiSchema`: The uiSchema that was passed to `Form`. -- `formContext`: The `formContext` object that you passed to Form. - -### Id prefix - -To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids (the default is `root`). - -```jsx -render(( - , -), document.getElementById("app")); -``` - -This will render `` instead of `` - -### Custom widgets and fields - -The API allows to specify your own custom *widget* and *field* components: - -- A *widget* represents a HTML tag for the user to enter data, eg. `input`, `select`, etc. -- A *field* usually wraps one or more widgets and most often handles internal field state; think of a field as a form row, including the labels. - -### Custom widget components - -You can provide your own custom widgets to a uiSchema for the following json data types: - -- `string` -- `number` -- `integer` -- `boolean` - -```jsx -const schema = { - type: "string" -}; - -const uiSchema = { - "ui:widget": (props) => { - return ( - props.onChange(event.target.value)} /> - ); - } -}; - -render(( - , -), document.getElementById("app")); -``` - -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; -- `required`: The required status of this field; -- `disabled`: `true` if the widget is disabled; -- `readonly`: `true` if the widget is read-only; -- `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; -- `options`: A map of options passed as a prop to the component (see [Custom widget options](#custom-widget-options)). -- `formContext`: The `formContext` object that you passed to Form. - -> Note: Prior to v0.35.0, the `options` prop contained the list of options (`label` and `value`) for `enum` fields. Since v0.35.0, it now exposes this list as the `enumOptions` property within the `options` object. - -#### Custom component registration - -Alternatively, you can register them all at once by passing the `widgets` prop to the `Form` component, and reference their identifier from the `uiSchema`: - -```jsx -const MyCustomWidget = (props) => { - return ( - props.onChange(event.target.value)} /> - ); -}; - -const widgets = { - myCustomWidget: MyCustomWidget -}; - -const uiSchema = { - "ui:widget": "myCustomWidget" -} - -render(( - -), document.getElementById("app")); -``` - -This is useful if you expose the `uiSchema` as pure JSON, which can't carry functions. - -> Note: Until 0.40.0 it was possible to register a widget as object with shape `{ component: MyCustomWidget, options: {...} }`. This undocumented API has been removed. Instead, you can register a custom widget with a React `defaultProps` property. `defaultProps.options` can be an object containing your custom options. - -#### Custom widget options - -If you need to pass options to your custom widget, you can add a `ui:options` object containing those properties. If the widget has `defaultProps`, the options will be merged with the (optional) options object from `defaultProps`: - -```jsx -const schema = { - type: "string" -}; - -function MyCustomWidget(props) { - const {options} = props; - const {color, backgroundColor} = options; - return ; -} - -MyCustomWidget.defaultProps = { - options: { - color: "red" - } -}; - -const uiSchema = { - "ui:widget": MyCustomWidget, - "ui:options": { - backgroundColor: "yellow" - } -}; - -// renders red on yellow input -render(( - -), document.getElementById("app")); -``` - -> Note: This also applies to [registered custom components](#custom-component-registration). - -> Note: Since v0.41.0, the `ui:widget` object API, where a widget and options were specified with `"ui:widget": {component, options}` shape, is deprecated. It will be removed in a future release. - -#### Customizing widgets text input - -All the widgets that render a text input use the `BaseInput` component internally. If you need to customize all text inputs without customizing all widgets individially, you can provide a `BaseInput` component in the `widgets` property of `Form` (see [Custom component registration](#custom-component-registration). - -### Custom field components - -You can provide your own field components to a uiSchema for basically any json schema data type, by specifying a `ui:field` property. - -For example, let's create and register a dumb `geo` component handling a *latitude* and a *longitude*: - -```jsx -const schema = { - type: "object", - required: ["lat", "lon"], - properties: { - lat: {type: "number"}, - lon: {type: "number"} - } -}; - -// Define a custom component for handling the root position object -class GeoPosition extends React.Component { - constructor(props) { - super(props); - this.state = {...props.formData}; - } - - onChange(name) { - return (event) => { - this.setState({ - [name]: parseFloat(event.target.value) - }, () => this.props.onChange(this.state)); - }; - } - - render() { - const {lat, lon} = this.state; - return ( -
    - - -
    - ); - } -} - -// Define the custom field component to use for the root object -const uiSchema = {"ui:field": "geo"}; - -// Define the custom field components to register; here our "geo" -// custom field component -const fields = {geo: GeoPosition}; - -// Render the form with all the properties we just defined passed -// as props -render(( - -), document.getElementById("app")); -``` - -> Note: Registered fields can be reused across the entire schema. - -#### Field props - -A field component will always be passed the following props: - - - `schema`: The JSON schema for this field; - - `uiSchema`: The [uiSchema](#the-uischema-object) for this field; - - `idSchema`: The tree of unique ids for every child field; - - `formData`: The data for this field; - - `errorSchema`: The tree of errors for this field and its children; - - `registry`: A [registry](#the-registry-object) object (read next). - - `formContext`: A [formContext](#the-formcontext-object) object (read next next). - -#### The `registry` object - -The `registry` is an object containing the registered custom fields and widgets as well as root schema definitions. - - - `fields`: The [custom registered fields](#custom-field-components). By default this object contains the standard `SchemaField`, `TitleField` and `DescriptionField` components; - - `widgets`: The [custom registered widgets](#custom-widget-components), if any; - - `definitions`: The root schema [definitions](#schema-definitions-and-references), if any. - - `formContext`: The [formContext](#the-formcontext-object) object. - -The registry is passed down the component tree, so you can access it from your custom field and `SchemaField` components. - -#### The `formContext` object - -You can provide a `formContext` object to the Form, which is passed down to all fields and widgets (including [TitleField](#custom-titles) and [DescriptionField](#custom-descriptions)). Useful for implementing context aware fields and widgets. - -### Custom array field buttons - -The `ArrayField` component provides a UI to add, remove and reorder array items, and these buttons use [Bootstrap glyphicons](http://getbootstrap.com/components/#glyphicons). If you don't use glyphicons but still want to provide your own icons or texts for these buttons, you can easily do so using CSS: - -```css -i.glyphicon { display: none; } -.btn-add::after { content: 'Add'; } -.array-item-move-up::after { content: 'Move Up'; } -.array-item-move-down::after { content: 'Move Down'; } -.array-item-remove::after { content: 'Remove'; } -``` - -### Custom SchemaField - -**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care. - -You can provide your own implementation of the `SchemaField` base React component for rendering any JSONSchema field type, including objects and arrays. This is useful when you want to augment a given field type with supplementary powers. - -To proceed so, pass a `fields` object having a `SchemaField` property to your `Form` component; here's a rather silly example wrapping the standard `SchemaField` lib component: - -```jsx -import SchemaField from "react-jsonschema-form/lib/components/fields/SchemaField"; - -const CustomSchemaField = function(props) { - return ( -
    -

    Yeah, I'm pretty dumb.

    - -
    - ); -}; - -const fields = { - SchemaField: CustomSchemaField -}; - -render(( - -), document.getElementById("app")); -``` - -If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field. - -Props passed to a custom SchemaField are the same as [the ones passed to a custom field](#field-props). - -### Customizing the default fields and widgets - -You can override any default field and widget, including the internal widgets like the `CheckboxWidget` that `ObjectField` renders for boolean values. You can override any field and widget just by providing the customized fields/widgets in the `fields` and `widgets` props: - -```jsx - -const CustomCheckbox = function(props) { - return ( - - ); -}; - -const widgets = { - CheckboxWidget: CustomCheckbox -}; - -render(( - -), document.getElementById("app")); -``` - -This allows you to create a reusable customized form class with your custom fields and widgets: - -```jsx -const customFields = {StringField: CustomString}; -const customWidgets = {CheckboxWidget: CustomCheckbox}; - -function MyForm(props) { - return ; -} - -render(( - -), document.getElementById("app")); -``` - -### Custom titles - -You can provide your own implementation of the `TitleField` base React component for rendering any title. This is useful when you want to augment how titles are handled. - -Simply pass a `fields` object having a `TitleField` property to your `Form` component: - -```jsx - -const CustomTitleField = ({title, required}) => { - const legend = required ? title + '*' : title; - return
    {legend}
    ; -}; - -const fields = { - TitleField: CustomTitleField -}; - -render(( - -), document.getElementById("app")); -``` - -### Custom descriptions - -You can provide your own implementation of the `DescriptionField` base React component for rendering any description. - -Simply pass a `fields` object having a `DescriptionField` property to your `Form` component: - -```jsx - -const CustomDescriptionField = ({id, description}) => { - return
    {description}
    ; -}; - -const fields = { - DescriptionField: CustomDescriptionField -}; - -render(( - -), document.getElementById("app")); -``` - -## Form data validation - -### Live validation - -By default, form data are only validated when the form is submitted or when a new `formData` prop is passed to the `Form` component. - -You can enable live form data validation by passing a `liveValidate` prop to the `Form` component, and set it to `true`. Then, everytime a value changes within the form data tree (eg. the user entering a character in a field), a validation operation is performed, and the validation results are reflected into the form state. - -Be warned that this is an expensive strategy, with possibly strong impact on performances. - -To disable validation entirely, you can set Form's `noValidate` prop to `true`. - -### HTML5 Validation - -By default, required field errors will cause the browser to display its standard HTML5 `required` attribute error messages and prevent form submission. If you would like to turn this off, you can set Form's `noHtml5Validate` prop to `true`, which will set `noValidate` on the `form` element. - -### Custom validation - -Form data is always validated against the JSON schema. - -But it is possible to define your own custom validation rules. This is especially useful when the validation depends on several interdependent fields. - -```js -function validate(formData, errors) { - if (formData.pass1 !== formData.pass2) { - errors.pass2.addError("Passwords don't match"); - } - return errors; -} - -const schema = { - type: "object", - properties: { - pass1: {type: "string", minLength: 3}, - pass2: {type: "string", minLength: 3}, - } -}; - -render(( - -), document.getElementById("app")); -``` - -> Notes: -> - The `validate()` function must **always** return the `errors` object -> received as second argument. -> - The `validate()` function is called **after** the JSON schema validation. - -### Custom error messages - -Validation error messages are provided by the JSON Schema validation by default. If you need to change these messages or make any other modifications to the errors from the JSON Schema validation, you can define a transform function that receives the list of JSON Schema errors and returns a new list. - -```js -function transformErrors(errors) { - return errors.map(error => { - if (error.name === "pattern") { - error.message = "Only digits are allowed" - } - return error; - }); -} - -const schema = { - type: "object", - properties: { - onlyNumbersString: {type: "string", pattern: "^\\d*$"}, - } -}; - -render(( - -), document.getElementById("app")); -``` - -> Notes: -> - The `transformErrors()` function must return the list of errors. Modifying the list in place without returning it will result in an error. - -### Error List Display - -To disable rendering of the error list at the top of the form, you can set the `showErrorList` prop to `false`. Doing so will still validate the form, but only the inline display will show. - -```js -render(( - -), document.getElementById("app")); -``` - -> Note: you can also use your own [ErrorList](#error-list-template) - -### The case of empty strings - -When a text input is empty, the field in form data is set to `undefined`. String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `undefined`. - -One consequence of this is that if you have an empty string in your `enum` array, selecting that option in the `select` input will cause the field to be set to `undefined`, not an empty string. - -If you want to have the field set to a default value when empty you can provide a `ui:emptyValue` field in the `uiSchema` object. - -## Styling your forms - -This library renders form fields and widgets leveraging the [Bootstrap](http://getbootstrap.com/) semantics. That means your forms will be beautiful by default if you're loading its stylesheet in your page. - -You're not necessarily forced to use Bootstrap; while it uses its semantics, it also provides a bunch of other class names so you can bring new styles or override default ones quite easily in your own personalized stylesheet. That's just HTML after all :) - -If you're okay with using styles from the Bootstrap ecosystem though, then the good news is that you have access to many themes for it, which are compatible with our generated forms! - -Here are some examples from the [playground](http://mozilla-services.github.io/react-jsonschema-form/), using some of the [Bootswatch](http://bootswatch.com/) free themes: - -![](http://i.imgur.com/1Z5oUK3.png) -![](http://i.imgur.com/IMFqMwK.png) -![](http://i.imgur.com/HOACwt5.png) - -Last, if you really really want to override the semantics generated by the lib, you can always create and use your own custom [widget](#custom-widget-components), [field](#custom-field-components) and/or [schema field](#custom-schemafield) components. - -## Schema definitions and references - -This library partially supports [inline schema definition dereferencing]( http://json-schema.org/latest/json-schema-core.html#rfc.section.7.2.3), which is Barbarian for *avoiding to copy and paste commonly used field schemas*: - -```json -{ - "definitions": { - "address": { - "type": "object", - "properties": { - "street_address": { "type": "string" }, - "city": { "type": "string" }, - "state": { "type": "string" } - }, - "required": ["street_address", "city", "state"] - } - }, - "type": "object", - "properties": { - "billing_address": { "$ref": "#/definitions/address" }, - "shipping_address": { "$ref": "#/definitions/address" } - } -} -``` - -*(Sample schema courtesy of the [Space Telescope Science Institute](http://spacetelescope.github.io/understanding-json-schema/structuring.html))* - -Note that it only supports local definition referencing, we do not plan on fetching foreign schemas over HTTP anytime soon. Basically, you can only reference a definition from the very schema object defining it. - -## Property dependencies - -This library supports conditionally making fields required based on the presence of other fields. - -### Unidirectional - -In the following example the `billing_address` field will be required if `credit_card` is defined. - -```json -{ - "type": "object", - - "properties": { - "name": { "type": "string" }, - "credit_card": { "type": "number" }, - "billing_address": { "type": "string" } - }, - - "required": ["name"], - - "dependencies": { - "credit_card": ["billing_address"] - } -} -``` - -### Bidirectional - -In the following example the `billing_address` field will be required if `credit_card` is defined and the `credit_card` -field will be required if `billing_address` is defined making them both required if either is defined. - -```json -{ - "type": "object", - - "properties": { - "name": { "type": "string" }, - "credit_card": { "type": "number" }, - "billing_address": { "type": "string" } - }, - - "required": ["name"], - - "dependencies": { - "credit_card": ["billing_address"], - "billing_address": ["credit_card"] - } -} -``` - -*(Sample schemas courtesy of the [Space Telescope Science Institute](https://spacetelescope.github.io/understanding-json-schema/reference/object.html#property-dependencies))* - -## Schema dependencies - -This library also supports modifying portions of a schema based on form data. - -### Conditional - -```json -{ - "type": "object", - - "properties": { - "name": { "type": "string" }, - "credit_card": { "type": "number" } - }, - - "required": ["name"], - - "dependencies": { - "credit_card": { - "properties": { - "billing_address": { "type": "string" } - }, - "required": ["billing_address"] - } - } -} -``` - -In this example the `billing_address` field will be displayed in the form if `credit_card` is defined. - -*(Sample schemas courtesy of the [Space Telescope Science Institute](https://spacetelescope.github.io/understanding-json-schema/reference/object.html#schema-dependencies))* - -### Dynamic - -The JSON Schema standard says that the dependency is triggered if the property is present. However, sometimes it's useful to have more sophisticated rules guiding the application of the dependency. For example, maybe you have three possible values for a field, and each one should lead to adding a different question. For this, we support a very restricted use of the `oneOf` keyword. - -```json -{ - "title": "Person", - "type": "object", - "properties": { - "Do you have any pets?": { - "type": "string", - "enum": [ - "No", - "Yes: One", - "Yes: More than one" - ], - "default": "No" - } - }, - "required": [ - "Do you have any pets?" - ], - "dependencies": { - "Do you have any pets?": { - "oneOf": [ - { - "properties": { - "Do you have any pets?": { - "enum": [ - "No" - ] - } - } - }, - { - "properties": { - "Do you have any pets?": { - "enum": [ - "Yes: One" - ] - }, - "How old is your pet?": { - "type": "number" - } - }, - "required": [ - "How old is your pet?" - ] - }, - { - "properties": { - "Do you have any pets?": { - "enum": [ - "Yes: More than one" - ] - }, - "Do you want to get rid of any?": { - "type": "boolean" - } - }, - "required": [ - "Do you want to get rid of any?" - ] - } - ] - } - } -} -``` - -In this example the user is prompted with different follow-up questions dynamically based on their answer to the first question. - -Note that this is quite far from complete `oneOf` support! - -In these examples, the "Do you have any pets?" question is validated against the corresponding property in each schema in the `oneOf` array. If exactly one matches, the rest of that schema is merged with the existing schema. - -## JSON Schema supporting status - -This component follows [JSON Schema](http://json-schema.org/documentation.html) specs. Due to the limitation of form widgets, there are some exceptions as follows: - -* `additionalItems` keyword for arrays - This keyword works when `items` is an array. `additionalItems: true` is not supported because there's no widget to represent an item of any type. In this case it will be treated as no additional items allowed. `additionalItems` being a valid schema is supported. -* `anyOf`, `allOf`, and `oneOf`, or multiple `types` (i.e. `"type": ["string", "array"]` - Nobody yet has come up with a PR that adds this feature with a simple and easy-to-understand UX. - You can use `oneOf` with [schema dependencies](#schema-dependencies) to dynamically add schema properties based on input data but this feature does not bring general support for `oneOf` elsewhere in a schema. - -## Tips and tricks - - - Custom field template: https://jsfiddle.net/hdp1kgn6/1/ - - Multi-step wizard: https://jsfiddle.net/sn4bnw9h/1/ - - Using classNames with uiSchema: https://jsfiddle.net/gfwp25we/1/ - - Conditional fields: https://jsfiddle.net/69z2wepo/88541/ - - Advanced conditional fields: https://jsfiddle.net/cowbellerina/zbfh96b1/ - - Use radio list for enums: https://jsfiddle.net/f2y3fq7L/2/ - - Reading file input data: https://jsfiddle.net/f9vcb6pL/1/ - - Custom errors messages with transformErrors : https://jsfiddle.net/revolunet/5r3swnr4/ - - 2 columns form with CSS and FieldTemplate : https://jsfiddle.net/n1k0/bw0ffnz4/1/ - - Validate and submit form from external control : https://jsfiddle.net/spacebaboon/g5a1re63/ +## Live Playground +A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) is hosted on gh-pages. ## Contributing - -### Coding style - -All the JavaScript code in this project conforms to the [prettier](https://github.com/prettier/prettier) coding style. A command is provided to ensure your code is always formatted accordingly: - -``` -$ npm run cs-format -``` - -The `cs-check` command ensures all files conform to that style: - -``` -$ npm run cs-check -``` - -### Development server - -``` -$ npm start -``` - -A live development server showcasing components with hot reload enabled is available at [localhost:8080](http://localhost:8080). - -If you want the development server to listen on another host or port, you can use the RJSF_DEV_SERVER env variable: - -``` -$ RJSF_DEV_SERVER=0.0.0.0:8000 npm start -``` - -### Tests - -``` -$ npm test -``` - -#### TDD - -``` -$ npm run tdd -``` - -### Releasing - -``` -$ edit package.json # update version number -$ git commit -m "Bump version $VERSION" -$ git tag v$VERSION -$ npm run dist -$ npm publish -$ git push --tags origin -``` - -## FAQ - -### Q: Does rjsf support `oneOf`, `anyOf`, multiple types in an array, etc.? - -A: Not yet (except for a special case where you can use `oneOf` in [schema dependencies](#schema-dependencies)), but perhaps you will be the person whose PR will finally add the feature in a way that gets merged. For inspiration, see [#329](https://github.com/mozilla-services/react-jsonschema-form/pull/329) or [#417](https://github.com/mozilla-services/react-jsonschema-form/pull/417). See also: [#52](https://github.com/mozilla-services/react-jsonschema-form/issues/52), [#151](https://github.com/mozilla-services/react-jsonschema-form/issues/151), [#171](https://github.com/mozilla-services/react-jsonschema-form/issues/171), [#200](https://github.com/mozilla-services/react-jsonschema-form/issues/200), [#282](https://github.com/mozilla-services/react-jsonschema-form/issues/282), [#302](https://github.com/mozilla-services/react-jsonschema-form/pull/302), [#330](https://github.com/mozilla-services/react-jsonschema-form/issues/330), [#430](https://github.com/mozilla-services/react-jsonschema-form/issues/430), [#522](https://github.com/mozilla-services/react-jsonschema-form/issues/522), [#538](https://github.com/mozilla-services/react-jsonschema-form/issues/538), [#551](https://github.com/mozilla-services/react-jsonschema-form/issues/551), [#552](https://github.com/mozilla-services/react-jsonschema-form/issues/552), or [#648](https://github.com/mozilla-services/react-jsonschema-form/issues/648). - -### Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]? - -A: Probably not. We use Bootstrap v3 and it works fine for our needs. We would like for react-jsonschema-form to support other frameworks, we just don't want to support them ourselves. Ideally, these frontend styles could be added to react-jsonschema-form with a third-party library. If there is a technical limitation preventing this, please consider opening a PR. See also: [#91](https://github.com/mozilla-services/react-jsonschema-form/issues/91), [#99](https://github.com/mozilla-services/react-jsonschema-form/issues/99), [#125](https://github.com/mozilla-services/react-jsonschema-form/issues/125), [#237](https://github.com/mozilla-services/react-jsonschema-form/issues/237), [#287](https://github.com/mozilla-services/react-jsonschema-form/issues/287), [#299](https://github.com/mozilla-services/react-jsonschema-form/issues/299), [#440](https://github.com/mozilla-services/react-jsonschema-form/issues/440), [#461](https://github.com/mozilla-services/react-jsonschema-form/issues/461), [#546](https://github.com/mozilla-services/react-jsonschema-form/issues/546), [#555](https://github.com/mozilla-services/react-jsonschema-form/issues/555), [#626](https://github.com/mozilla-services/react-jsonschema-form/issues/626), and [#623](https://github.com/mozilla-services/react-jsonschema-form/pull/623). - -### Q: Is there a way to "collapse" fields, for instance to show/hide individual fields? - -A: There's no specific built-in way to do this, but you can write your own FieldTemplate that supports hiding/showing fields according to user input. We don't yet have an example of this use, but if you write one, please add it to the "tips and tricks" section, above. See also: [#268](https://github.com/mozilla-services/react-jsonschema-form/issues/268), [#304](https://github.com/mozilla-services/react-jsonschema-form/pull/304), [#598](https://github.com/mozilla-services/react-jsonschema-form/issues/598), [#920](https://github.com/mozilla-services/react-jsonschema-form/issues/920). +Read our [contributors' guide](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) to get started. ## License - -Apache 2 +Apache 2 \ No newline at end of file diff --git a/docs/advanced-customization.md b/docs/advanced-customization.md new file mode 100644 index 0000000000..69ec69fef3 --- /dev/null +++ b/docs/advanced-customization.md @@ -0,0 +1,577 @@ +## Advanced customization + + +_ | Custom Field | Custom Template | Custom Widget +--|---------- | ------------- | ---- +What it does | Overrides all behaviour | Overrides just the layout | Overrides just the input box (not layout, labels, or help, or validation) +Usage | Global or per-field | Only global | Global or per-field +Global Example | `` | `` | `` +Per-Field Example | `"ui:field": MyField` | N/A | `"ui:widget":MyWidget` +Documentation | [Field](#field-props) | [Field Template](#field-template) - [Array Template](#array-field-template) - [Object Template](#object-field-template) - [Error List Template](#error-list-template) | [Custom Widgets](#custom-widget-components) + +### Field template + +To take control over the inner organization of each field (each form row), you can define a *field template* for your form. + +A field template is basically a React stateless component being passed field-related props, allowing you to structure your form row as you like. + +```jsx +function CustomFieldTemplate(props) { + const {id, classNames, label, help, required, description, errors, children} = props; + return ( +
    + + {description} + {children} + {errors} + {help} +
    + ); +} + +render(( + , +), document.getElementById("app")); +``` + +If you want to handle the rendering of each element yourself, you can use the props `rawHelp`, `rawDescription` and `rawErrors`. + +The following props are passed to a custom field template component: + +- `id`: The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget. +- `classNames`: A string containing the base Bootstrap CSS classes, merged with any [custom ones](#custom-css-class-names) defined in your uiSchema. +- `label`: The computed label for this field, as a string. +- `description`: A component instance rendering the field description, if one is defined (this will use any [custom `DescriptionField`](#custom-descriptions) defined). +- `rawDescription`: A string containing any `ui:description` uiSchema directive defined. +- `children`: The field or widget component instance for this field row. +- `errors`: A component instance listing any encountered errors for this field. +- `rawErrors`: An array of strings listing all generated error messages from encountered errors for this field. +- `help`: A component instance rendering any `ui:help` uiSchema directive defined. +- `rawHelp`: A string containing any `ui:help` uiSchema directive defined. **NOTE:** `rawHelp` will be `undefined` if passed `ui:help` is a React component instead of a string. +- `hidden`: A boolean value stating if the field should be hidden. +- `required`: A boolean value stating if the field is required. +- `readonly`: A boolean value stating if the field is read-only. +- `disabled`: A boolean value stating if the field is disabled. +- `displayLabel`: A boolean value stating if the label should be rendered or not. This is useful for nested fields in arrays where you don't want to clutter the UI. +- `fields`: An array containing all Form's fields including your [custom fields](#custom-field-components) and the built-in fields. +- `schema`: The schema object for this field. +- `uiSchema`: The uiSchema object for this field. +- `formContext`: The `formContext` object that you passed to Form. + +> Note: you can only define a single field template for a form. If you need many, it's probably time to look at [custom fields](#custom-field-components) instead. + +### Array Field Template + +Similarly to the `FieldTemplate` you can use an `ArrayFieldTemplate` to customize how your +arrays are rendered. This allows you to customize your array, and each element in the array. + +```jsx +function ArrayFieldTemplate(props) { + return ( +
    + {props.items.map(element => element.children)} + {props.canAdd && } +
    + ); +} + +render(( + , +), document.getElementById("app")); +``` + +Please see [customArray.js](https://github.com/mozilla-services/react-jsonschema-form/blob/master/playground/samples/customArray.js) for a better example. + +The following props are passed to each `ArrayFieldTemplate`: + +- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) +- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). +- `canAdd`: A boolean value stating whether new elements can be added to the array. +- `className`: The className string. +- `disabled`: A boolean value stating if the array is disabled. +- `idSchema`: Object +- `items`: An array of objects representing the items in the array. Each of the items represent a child with properties described below. +- `onAddClick: (event) => void`: A function that adds a new item to the array. +- `readonly`: A boolean value stating if the array is read-only. +- `required`: A boolean value stating if the array is required. +- `schema`: The schema object for this array. +- `uiSchema`: The uiSchema object for this array field. +- `title`: A string value containing the title for the array. +- `formContext`: The `formContext` object that you passed to Form. +- `formData`: The formData for this array. + +The following props are part of each element in `items`: + +- `children`: The html for the item's content. +- `className`: The className string. +- `disabled`: A boolean value stating if the array item is disabled. +- `hasMoveDown`: A boolean value stating whether the array item can be moved down. +- `hasMoveUp`: A boolean value stating whether the array item can be moved up. +- `hasRemove`: A boolean value stating whether the array item can be removed. +- `hasToolbar`: A boolean value stating whether the array item has a toolbar. +- `index`: A number stating the index the array item occurs in `items`. +- `onDropIndexClick: (index) => (event) => void`: Returns a function that removes the item at `index`. +- `onReorderClick: (index, newIndex) => (event) => void`: Returns a function that swaps the items at `index` with `newIndex`. +- `readonly`: A boolean value stating if the array item is read-only. + +### Object Field Template + +Similarly to the `FieldTemplate` you can use an `ObjectFieldTemplate` to customize how your +objects are rendered. + +```jsx +function ObjectFieldTemplate(props) { + return ( +
    + {props.title} + {props.description} + {props.properties.map(element =>
    {element.content}
    )} +
    + ); +} + +render(( + , +), document.getElementById("app")); +``` + +Please see [customObject.js](https://github.com/mozilla-services/react-jsonschema-form/blob/master/playground/samples/customObject.js) for a better example. + +The following props are passed to each `ObjectFieldTemplate`: + +- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) +- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). +- `title`: A string value containing the title for the object. +- `description`: A string value containing the description for the object. +- `disabled`: A boolean value stating if the object is disabled. +- `properties`: An array of object representing the properties in the array. Each of the properties represent a child with properties described below. +- `readonly`: A boolean value stating if the object is read-only. +- `required`: A boolean value stating if the object is required. +- `schema`: The schema object for this object. +- `uiSchema`: The uiSchema object for this object field. +- `idSchema`: An object containing the id for this object & ids for it's properties. +- `formData`: The form data for the object. +- `formContext`: The `formContext` object that you passed to Form. + +The following props are part of each element in `properties`: + +- `content`: The html for the property's content. +- `name`: A string representing the property name. +- `disabled`: A boolean value stating if the object property is disabled. +- `readonly`: A boolean value stating if the property is read-only. + +### Error List template + +To take control over how the form errors are displayed, you can define an *error list template* for your form. This list is the form global error list that appears at the top of your forms. + +An error list template is basically a React stateless component being passed errors as props so you can render them as you like: + +```jsx +function ErrorListTemplate(props) { + const {errors} = props; + return ( + + ); +} + +render(( + , +), document.getElementById("app")); +``` + +> Note: Your custom `ErrorList` template will only render when `showErrorList` is `true`. + +The following props are passed to `ErrorList` + +- `errors`: An array of the errors. +- `errorSchema`: The errorSchema constructed by `Form`. +- `schema`: The schema that was passed to `Form`. +- `uiSchema`: The uiSchema that was passed to `Form`. +- `formContext`: The `formContext` object that you passed to Form. + +### Id prefix + +To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids (the default is `root`). + +```jsx +render(( + , +), document.getElementById("app")); +``` + +This will render `` instead of `` + +### Custom widgets and fields + +The API allows to specify your own custom *widget* and *field* components: + +- A *widget* represents a HTML tag for the user to enter data, eg. `input`, `select`, etc. +- A *field* usually wraps one or more widgets and most often handles internal field state; think of a field as a form row, including the labels. + +### Custom widget components + +You can provide your own custom widgets to a uiSchema for the following json data types: + +- `string` +- `number` +- `integer` +- `boolean` + +```jsx +const schema = { + type: "string" +}; + +const uiSchema = { + "ui:widget": (props) => { + return ( + props.onChange(event.target.value)} /> + ); + } +}; + +render(( + , +), document.getElementById("app")); +``` + +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; +- `options`: A map of options passed as a prop to the component (see [Custom widget options](#custom-widget-options)). +- `formContext`: The `formContext` object that you passed to Form. + +> Note: Prior to v0.35.0, the `options` prop contained the list of options (`label` and `value`) for `enum` fields. Since v0.35.0, it now exposes this list as the `enumOptions` property within the `options` object. + +#### Custom component registration + +Alternatively, you can register them all at once by passing the `widgets` prop to the `Form` component, and reference their identifier from the `uiSchema`: + +```jsx +const MyCustomWidget = (props) => { + return ( + props.onChange(event.target.value)} /> + ); +}; + +const widgets = { + myCustomWidget: MyCustomWidget +}; + +const uiSchema = { + "ui:widget": "myCustomWidget" +} + +render(( + +), document.getElementById("app")); +``` + +This is useful if you expose the `uiSchema` as pure JSON, which can't carry functions. + +> Note: Until 0.40.0 it was possible to register a widget as object with shape `{ component: MyCustomWidget, options: {...} }`. This undocumented API has been removed. Instead, you can register a custom widget with a React `defaultProps` property. `defaultProps.options` can be an object containing your custom options. + +#### Custom widget options + +If you need to pass options to your custom widget, you can add a `ui:options` object containing those properties. If the widget has `defaultProps`, the options will be merged with the (optional) options object from `defaultProps`: + +```jsx +const schema = { + type: "string" +}; + +function MyCustomWidget(props) { + const {options} = props; + const {color, backgroundColor} = options; + return ; +} + +MyCustomWidget.defaultProps = { + options: { + color: "red" + } +}; + +const uiSchema = { + "ui:widget": MyCustomWidget, + "ui:options": { + backgroundColor: "yellow" + } +}; + +// renders red on yellow input +render(( + +), document.getElementById("app")); +``` + +> Note: This also applies to [registered custom components](#custom-component-registration). + +> Note: Since v0.41.0, the `ui:widget` object API, where a widget and options were specified with `"ui:widget": {component, options}` shape, is deprecated. It will be removed in a future release. + +#### Customizing widgets text input + +All the widgets that render a text input use the `BaseInput` component internally. If you need to customize all text inputs without customizing all widgets individually, you can provide a `BaseInput` component in the `widgets` property of `Form` (see [Custom component registration](#custom-component-registration). + +### Custom field components + +You can provide your own field components to a uiSchema for basically any json schema data type, by specifying a `ui:field` property. + +For example, let's create and register a dumb `geo` component handling a *latitude* and a *longitude*: + +```jsx +const schema = { + type: "object", + required: ["lat", "lon"], + properties: { + lat: {type: "number"}, + lon: {type: "number"} + } +}; + +// Define a custom component for handling the root position object +class GeoPosition extends React.Component { + constructor(props) { + super(props); + this.state = {...props.formData}; + } + + onChange(name) { + return (event) => { + this.setState({ + [name]: parseFloat(event.target.value) + }, () => this.props.onChange(this.state)); + }; + } + + render() { + const {lat, lon} = this.state; + return ( +
    + + +
    + ); + } +} + +// Define the custom field component to use for the root object +const uiSchema = {"ui:field": "geo"}; + +// Define the custom field components to register; here our "geo" +// custom field component +const fields = {geo: GeoPosition}; + +// Render the form with all the properties we just defined passed +// as props +render(( + +), document.getElementById("app")); +``` + +> Note: Registered fields can be reused across the entire schema. + +#### Field props + +A field component will always be passed the following props: + + - `schema`: The JSON schema for this field; + - `uiSchema`: The [uiSchema](#the-uischema-object) for this field; + - `idSchema`: The tree of unique ids for every child field; + - `formData`: The data for this field; + - `errorSchema`: The tree of errors for this field and its children; + - `registry`: A [registry](#the-registry-object) object (read next). + - `formContext`: A [formContext](#the-formcontext-object) object (read next). + +#### The `registry` object + +The `registry` is an object containing the registered custom fields and widgets as well as root schema definitions. + + - `fields`: The [custom registered fields](#custom-field-components). By default this object contains the standard `SchemaField`, `TitleField` and `DescriptionField` components; + - `widgets`: The [custom registered widgets](#custom-widget-components), if any; + - `definitions`: The root schema [definitions](#schema-definitions-and-references), if any. + - `formContext`: The [formContext](#the-formcontext-object) object. + +The registry is passed down the component tree, so you can access it from your custom field and `SchemaField` components. + +#### The `formContext` object + +You can provide a `formContext` object to the Form, which is passed down to all fields and widgets (including [TitleField](#custom-titles) and [DescriptionField](#custom-descriptions)). Useful for implementing context aware fields and widgets. + +### Custom array field buttons + +The `ArrayField` component provides a UI to add, remove and reorder array items, and these buttons use [Bootstrap glyphicons](http://getbootstrap.com/components/#glyphicons). If you don't use glyphicons but still want to provide your own icons or texts for these buttons, you can easily do so using CSS: + +```css +i.glyphicon { display: none; } +.btn-add::after { content: 'Add'; } +.array-item-move-up::after { content: 'Move Up'; } +.array-item-move-down::after { content: 'Move Down'; } +.array-item-remove::after { content: 'Remove'; } +``` + +### Custom SchemaField + +**Warning:** This is a powerful feature as you can override the whole form behavior and easily mess it up. Handle with care. + +You can provide your own implementation of the `SchemaField` base React component for rendering any JSONSchema field type, including objects and arrays. This is useful when you want to augment a given field type with supplementary powers. + +To proceed so, pass a `fields` object having a `SchemaField` property to your `Form` component; here's a rather silly example wrapping the standard `SchemaField` lib component: + +```jsx +import SchemaField from "react-jsonschema-form/lib/components/fields/SchemaField"; + +const CustomSchemaField = function(props) { + return ( +
    +

    Yeah, I'm pretty dumb.

    + +
    + ); +}; + +const fields = { + SchemaField: CustomSchemaField +}; + +render(( + +), document.getElementById("app")); +``` + +If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field. + +Props passed to a custom SchemaField are the same as [the ones passed to a custom field](#field-props). + +### Customizing the default fields and widgets + +You can override any default field and widget, including the internal widgets like the `CheckboxWidget` that `ObjectField` renders for boolean values. You can override any field and widget just by providing the customized fields/widgets in the `fields` and `widgets` props: + +```jsx + +const CustomCheckbox = function(props) { + return ( + + ); +}; + +const widgets = { + CheckboxWidget: CustomCheckbox +}; + +render(( + +), document.getElementById("app")); +``` + +This allows you to create a reusable customized form class with your custom fields and widgets: + +```jsx +const customFields = {StringField: CustomString}; +const customWidgets = {CheckboxWidget: CustomCheckbox}; + +function MyForm(props) { + return ; +} + +render(( + +), document.getElementById("app")); +``` + +### Custom titles + +You can provide your own implementation of the `TitleField` base React component for rendering any title. This is useful when you want to augment how titles are handled. + +Simply pass a `fields` object having a `TitleField` property to your `Form` component: + +```jsx + +const CustomTitleField = ({title, required}) => { + const legend = required ? title + '*' : title; + return
    {legend}
    ; +}; + +const fields = { + TitleField: CustomTitleField +}; + +render(( + +), document.getElementById("app")); +``` + +### Custom descriptions + +You can provide your own implementation of the `DescriptionField` base React component for rendering any description. + +Simply pass a `fields` object having a `DescriptionField` property to your `Form` component: + +```jsx + +const CustomDescriptionField = ({id, description}) => { + return
    {description}
    ; +}; + +const fields = { + DescriptionField: CustomDescriptionField +}; + +render(( + +), document.getElementById("app")); +``` diff --git a/docs/definitions.md b/docs/definitions.md new file mode 100644 index 0000000000..65b89ed284 --- /dev/null +++ b/docs/definitions.md @@ -0,0 +1,29 @@ +## Schema definitions and references + +This library partially supports [inline schema definition dereferencing]( http://json-schema.org/latest/json-schema-core.html#rfc.section.7.2.3), which is Barbarian for *avoiding to copy and paste commonly used field schemas*: + +```json +{ + "definitions": { + "address": { + "type": "object", + "properties": { + "street_address": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" } + }, + "required": ["street_address", "city", "state"] + } + }, + "type": "object", + "properties": { + "billing_address": { "$ref": "#/definitions/address" }, + "shipping_address": { "$ref": "#/definitions/address" } + } +} +``` + +*(Sample schema courtesy of the [Space Telescope Science Institute](http://spacetelescope.github.io/understanding-json-schema/structuring.html))* + +Note that it only supports local definition referencing; we do not plan on fetching foreign schemas over HTTP anytime soon. Basically, you can only reference a definition from the very schema object defining it. + diff --git a/docs/dependencies.md b/docs/dependencies.md new file mode 100644 index 0000000000..9eb89cc412 --- /dev/null +++ b/docs/dependencies.md @@ -0,0 +1,157 @@ +## Property dependencies + +This library supports conditionally making fields required based on the presence of other fields. + +### Unidirectional + +In the following example the `billing_address` field will be required if `credit_card` is defined. + +```json +{ + "type": "object", + + "properties": { + "name": { "type": "string" }, + "credit_card": { "type": "number" }, + "billing_address": { "type": "string" } + }, + + "required": ["name"], + + "dependencies": { + "credit_card": ["billing_address"] + } +} +``` + +### Bidirectional + +In the following example the `billing_address` field will be required if `credit_card` is defined and the `credit_card` +field will be required if `billing_address` is defined making them both required if either is defined. + +```json +{ + "type": "object", + + "properties": { + "name": { "type": "string" }, + "credit_card": { "type": "number" }, + "billing_address": { "type": "string" } + }, + + "required": ["name"], + + "dependencies": { + "credit_card": ["billing_address"], + "billing_address": ["credit_card"] + } +} +``` + +*(Sample schemas courtesy of the [Space Telescope Science Institute](https://spacetelescope.github.io/understanding-json-schema/reference/object.html#property-dependencies))* + +## Schema dependencies + +This library also supports modifying portions of a schema based on form data. + +### Conditional + +```json +{ + "type": "object", + + "properties": { + "name": { "type": "string" }, + "credit_card": { "type": "number" } + }, + + "required": ["name"], + + "dependencies": { + "credit_card": { + "properties": { + "billing_address": { "type": "string" } + }, + "required": ["billing_address"] + } + } +} +``` + +In this example the `billing_address` field will be displayed in the form if `credit_card` is defined. + +*(Sample schemas courtesy of the [Space Telescope Science Institute](https://spacetelescope.github.io/understanding-json-schema/reference/object.html#schema-dependencies))* + +### Dynamic + +The JSON Schema standard says that the dependency is triggered if the property is present. However, sometimes it's useful to have more sophisticated rules guiding the application of the dependency. For example, maybe you have three possible values for a field, and each one should lead to adding a different question. For this, we support a very restricted use of the `oneOf` keyword. + +```json +{ + "title": "Person", + "type": "object", + "properties": { + "Do you have any pets?": { + "type": "string", + "enum": [ + "No", + "Yes: One", + "Yes: More than one" + ], + "default": "No" + } + }, + "required": [ + "Do you have any pets?" + ], + "dependencies": { + "Do you have any pets?": { + "oneOf": [ + { + "properties": { + "Do you have any pets?": { + "enum": [ + "No" + ] + } + } + }, + { + "properties": { + "Do you have any pets?": { + "enum": [ + "Yes: One" + ] + }, + "How old is your pet?": { + "type": "number" + } + }, + "required": [ + "How old is your pet?" + ] + }, + { + "properties": { + "Do you have any pets?": { + "enum": [ + "Yes: More than one" + ] + }, + "Do you want to get rid of any?": { + "type": "boolean" + } + }, + "required": [ + "Do you want to get rid of any?" + ] + } + ] + } + } +} +``` + +In this example the user is prompted with different follow-up questions dynamically based on their answer to the first question. + +In these examples, the "Do you have any pets?" question is validated against the corresponding property in each schema in the `oneOf` array. If exactly one matches, the rest of that schema is merged with the existing schema. diff --git a/docs/form-customization.md b/docs/form-customization.md new file mode 100644 index 0000000000..6310c0f1d6 --- /dev/null +++ b/docs/form-customization.md @@ -0,0 +1,724 @@ +## Form customization + +### The `uiSchema` object + +JSONSchema is limited for describing how a given data type should be rendered as a form input component. That's why this lib introduces the concept of *UI schema*. + +A UI schema is basically an object literal providing information on **how** the form should be rendered, while the JSON schema tells **what**. + +The uiSchema object follows the tree structure of the form field hierarchy, and defines how each property should be rendered: + +```js +const schema = { + type: "object", + properties: { + foo: { + type: "object", + properties: { + bar: {type: "string"} + } + }, + baz: { + type: "array", + items: { + type: "object", + properties: { + description: { + "type": "string" + } + } + } + } + } +} + +const uiSchema = { + foo: { + bar: { + "ui:widget": "textarea" + }, + }, + baz: { + // note the "items" for an array + items: { + description: { + "ui:widget": "textarea" + } + } + } +} + +render(( + +), document.getElementById("app")); +``` + +### Alternative widgets + +The uiSchema `ui:widget` property tells the form which UI widget should be used to render a field. + +Example: + +```jsx +const uiSchema =  { + done: { + "ui:widget": "radio" // could also be "select" + } +}; + +render(( + +), document.getElementById("app")); +``` + +Here's a list of supported alternative widgets for different JSONSchema data types: + +#### For `boolean` fields + + * `radio`: a radio button group with `true` and `false` as selectable values; + * `select`: a select box with `true` and `false` as options; + * by default, a checkbox is used + +> Note: To set the labels for a boolean field, instead of using `true` and `false` you can set `enumNames` in your schema. Note that `enumNames` belongs in your `schema`, not the `uiSchema`, and the order is always `[true, false]`. + +#### For `string` fields + + * `textarea`: a `textarea` element is used; + * `password`: an `input[type=password]` element is used; + * `color`: an `input[type=color]` element is used; + * by default, a regular `input[type=text]` element is used. + +##### String formats + +The built-in string field also supports the JSONSchema `format` property, and will render an appropriate widget by default for the following string formats: + +- `email`: An `input[type=email]` element is used; +- `uri`: An `input[type=url]` element is used; +- `data-url`: By default, an `input[type=file]` element is used; in case the string is part of an array, multiple files will be handled automatically (see [File widgets](#file-widgets)). +- `date`: By default, an `input[type=date]` element is used; +- `date-time`: By default, an `input[type=datetime-local]` element is used. + +![](https://i.imgur.com/xqu6Lcp.png) + +Please note that, even though they are standardized, `datetime-local` and `date` input elements are not yet supported by Firefox and IE. If you plan on targeting these platforms, two alternative widgets are available: + +- `alt-datetime`: Six `select` elements are used to select the year, the month, the day, the hour, the minute and the second; +- `alt-date`: Three `select` elements are used to select the year, month and the day. + +> **Firefox 57 - 66**: Firefox partially supporting `date` and `time` input types, but not `datetime-local`, `month` or `week` + +![](https://i.imgur.com/VF5tY60.png) + +You can customize the list of years displayed in the `year` dropdown by providing a ``yearsRange`` property to ``ui:options`` in your uiSchema. Its also possible to remove the `Now` and `Clear` buttons with the `hideNowButton` and `hideClearButton` options. + +```jsx +uiSchema: { + a_date: { + "alt-datetime": { + "ui:widget": "alt-datetime", + "ui:options": { + yearsRange: [1980, 2030], + hideNowButton: true, + hideClearButton: true, + }, + }, + }, +}, +``` + +#### For `number` and `integer` fields + + * `updown`: an `input[type=number]` updown selector; + * `range`: an `input[type=range]` slider; + * `radio`: a radio button group with enum values. This can only be used when `enum` values are specified for this input. + * By default, a regular `input[type=text]` element is used. + +> Note: If JSONSchema's `minimum`, `maximum` and `multipleOf` values are defined, the `min`, `max` and `step` input attributes values will take those values. + +#### Disabled fields + +The `ui:disabled` uiSchema directive will disable all child widgets from a given field. + +#### Read-only fields + +The `ui:readonly` uiSchema directive will mark all child widgets from a given field as read-only. + +You can also set specific fields to read-only by setting the `readOnly` property in the schema. + +```js +const schema = { + type: "object", + properties: { + foo: { + type: "string", + readOnly: true + } + } +}; +``` + +> Note: If you're wondering about the difference between a `disabled` field and a `readonly` one: Marking a field as read-only will render it greyed out, but its text value will be selectable. Disabling it will prevent its value to be selected at all. + +#### Hidden widgets + +It's possible to use a hidden widget for a field by setting its `ui:widget` uiSchema directive to `hidden`: + +```js +const schema = { + type: "object", + properties: { + foo: {type: "boolean"} + } +}; + +const uiSchema = { + foo: {"ui:widget": "hidden"} +}; +``` + +Notes: + + - Hiding widgets is only supported for `boolean`, `string`, `number` and `integer` schema types; + - A hidden widget takes its value from the `formData` prop. + +#### File widgets + +This library supports a limited form of `input[type=file]` widgets, in the sense that it will propagate file contents to form data state as [data-url](http://dataurl.net/#about)s. + +There are two ways to use file widgets. + +1. By declaring a `string` json schema type along a `data-url` [format](#string-formats): +```js +const schema = { + type: "string", + format: "data-url", +}; +``` + +2. By specifying a `ui:widget` field uiSchema directive as `file`: +```js +const schema = { + type: "string", +}; + +const uiSchema = { + "ui:widget": "file", +}; +``` + +##### Multiple files + +Multiple files selectors are supported by defining an array of strings having `data-url` as a format: + +```js +const schema = { + type: "array", + items: { + type: "string", + format: "data-url", + } +}; +``` + +> Note that storing large dataURIs into form state might slow rendering. + +##### File widget input ref + +The included `FileWidget` exposes a reference to the `` element node as an `inputRef` component property. + +This allows you to programmatically trigger the browser's file selector, which can be used in a custom file widget. + +### Object fields ordering + +Since the order of object properties in Javascript and JSON is not guaranteed, the `uiSchema` object spec allows you to define the order in which properties are rendered using the `ui:order` property: + +```jsx +const schema = { + type: "object", + properties: { + foo: {type: "string"}, + bar: {type: "string"} + } +}; + +const uiSchema = { + "ui:order": ["bar", "foo"] +}; + +render(( + +), document.getElementById("app")); +``` + +If a guaranteed fixed order is only important for some fields, you can insert a wildcard `"*"` item in your `ui:order` definition. All fields that are not referenced explicitly anywhere in the list will be rendered at that point: + +```js +const uiSchema = { + "ui:order": ["bar", "*"] +}; +``` + +### Object additional properties + +You can define `additionalProperties` by setting its value to a schema object, such as the following: + +```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`: + +```jsx +const uiSchema = { + "ui:options": { + expandable: false + } +}; +``` + +### Array item options + +#### `orderable` option + +Array items are orderable by default, and react-jsonschema-form renders move up/down buttons alongside them. The `uiSchema` object spec allows you to disable ordering: + +```jsx +const schema = { + type: "array", + items: { + type: "string" + } +}; + +const uiSchema = { + "ui:options": { + orderable: false + } +}; +``` + +#### `addable` option + +If either `items` or `additionalItems` contains a schema object, an add button for new items is shown by default. You can turn this off with the `addable` option in `uiSchema`: + +```jsx +const uiSchema = { + "ui:options": { + addable: false + } +}; +``` + +#### `removable` option + +A remove button is shown by default for an item if `items` contains a schema object, or the item is an `additionalItems` instance. You can turn this off with the `removable` option in `uiSchema`: + +```jsx +const uiSchema = { + "ui:options": { + removable: false + } +}; +``` + +### Custom CSS class names + +The uiSchema object accepts a `classNames` property for each field of the schema: + +```jsx +const uiSchema = { + title: { + classNames: "task-title foo-bar" + } +}; +``` + +Will result in: + +```html +
    + +
    +``` + +### Custom labels for `enum` fields + +This library supports the [`enumNames`](https://github.com/json-schema/json-schema/wiki/enumNames-%28v5-proposal%29) property for `enum` fields, which allows defining custom labels for each option of an `enum`: + +```js +const schema = { + type: "number", + enum: [1, 2, 3], + enumNames: ["one", "two", "three"] +}; +``` + +This will be rendered using a select box like this: + +```html + +``` + +Note that string representations of numbers will be cast back and reflected as actual numbers into form state. + +#### Alternative JSON-Schema compliant approach + +JSON Schema has an alternative approach to enumerations; react-jsonschema-form supports it as well. + +```js +const schema = { + "type": "number", + "anyOf": [ + { + "type": "number", + "title": "one", + "enum": [ + 1 + ] + }, + { + "type": "number", + "title": "two", + "enum": [ + 2 + ] + }, + { + "type": "number", + "title": "three", + "enum": [ + 3 + ] + } + ] +}; +``` + +This will be rendered as follows: + +```html + +``` + +This also works for radio buttons: + +```js +const schema = { + "type": "boolean", + "oneOf": [ + { + "const": true, + "title": "Yes" + }, + { + "const": false, + "title": "No" + } + ] +}; + +const uiSchema = { + "ui:widget": "radio" +}; +``` + +This will be rendered as follows: + +```html +
    +
    + +
    +
    + +
    +
    +``` + +A live example of both approaches side-by-side can be found in the **Alternatives** tab of the [playground](https://mozilla-services.github.io/react-jsonschema-form/). + +### Disabled attribute for `enum` fields + +To disable an option, use the `enumDisabled` property in uiSchema. + +```js +const schema = { + type: "string", + enum: ["one", "two", "three"], +}; + +const uiSchema={ + "ui:enumDisabled": ['two'], +} +``` + +This will be rendered using a select box as follows: + +```html + +``` + +### Multiple-choice list + +The default behavior for array fields is a list of text inputs with add/remove buttons. There are two alternative widgets for picking multiple elements from a list of choices. Typically this applies when a schema has an `enum` list for the `items` property of an `array` field, and the `uniqueItems` property set to `true`. + +Example: + +```js +const schema = { + type: "array", + title: "A multiple-choice list", + items: { + type: "string", + enum: ["foo", "bar", "fuzz", "qux"], + }, + uniqueItems: true +}; +``` + +By default, this will render a multiple select box. If you prefer a list of checkboxes, just set the uiSchema `ui:widget` directive to `checkboxes` for that field: + +```js +const uiSchema = { + "ui:widget": "checkboxes" +}; +``` + +Note that when an array property is marked as `required`, an empty array is considered valid. If array needs to be populated, you can specify the minimum number of items using the `minItems` property. + +Example: + +```js +const schema = { + type: "array", + minItems: 2, + title: "A multiple-choice list", + items: { + type: "string", + enum: ["foo", "bar", "fuzz", "qux"], + }, + uniqueItems: true +}; +``` + +By default, checkboxes are stacked. If you prefer them inline, set the `inline` property to `true`: + +```js +const uiSchema = { + "ui:widget": "checkboxes", + "ui:options": { + inline: true + } +}; +``` + +See the "Arrays" section of the [playground](https://mozilla-services.github.io/react-jsonschema-form/) for cool demos. + +### Autogenerated widget ids + +By default, this library will generate ids unique to the form for all rendered widgets. If you plan on using multiple instances of the `Form` component in a same page, it's wise to declare a root prefix for these, using the `ui:rootFieldId` uiSchema directive: + +```js +const uiSchema = { + "ui:rootFieldId": "myform" +}; +``` + +So all widgets will have an id prefixed with `myform`. + +### Form action buttons + +You can provide custom buttons to your form via the `Form` component's `children`. Otherwise a default submit button will be rendered. + +```jsx +render(( + +
    + + +
    + +), document.getElementById("app")); +``` + +> **Warning:** There needs to be a button or an input with `type="submit"` to trigger the form submission (and then the form validation). + +### Help text + +Sometimes it's convenient to add text next to a field to guide the end user filling it. This is the purpose of the `ui:help` uiSchema directive: + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "password", + "ui:help": "Hint: Make it strong!" +}; +``` + +![](https://i.imgur.com/scJUuZo.png) + +Help texts work for any kind of field at any level, and will always be rendered immediately below the field component widget(s) (after contextualized errors, if any). + +### Title texts + +Sometimes it's convenient to change a field's title. this is the purpose of the `ui:title` uiSchema directive: + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "password", + "ui:title": "Your password" +}; +``` + +### Description texts + +Sometimes it's convenient to change the description of a field. This is the purpose of the `ui:description` uiSchema directive: + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "password", + "ui:description": "The best password" +}; +``` + +### Auto focus + +If you want to automatically focus on a text input or textarea input, set the `ui:autofocus` uiSchema directive to `true`. + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "textarea", + "ui:autofocus": true +} +``` + +### Textarea `rows` option + +You can set the initial height of a textarea widget by specifying `rows` option. + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "textarea", + "ui:options": { + rows: 15 + } +} +``` + +### Placeholders + +You can add placeholder text to an input by using the `ui:placeholder` uiSchema directive: + +```jsx +const schema = {type: "string", format: "uri"}; +const uiSchema = { + "ui:placeholder": "http://" +}; +``` + +![](https://i.imgur.com/MbHypKg.png) + +Fields using `enum` can also use `ui:placeholder`. The value will be used as the text for the empty option in the select widget. + +```jsx +const schema = {type: "string", enum: ["First", "Second"]}; +const uiSchema = { + "ui:placeholder": "Choose an option" +}; +``` + +### Field labels + +Field labels are rendered by default. Labels may be omitted by setting the `label` option to `false` in the `ui:options` uiSchema directive. + +```jsx +const schema = {type: "string"}; +const uiSchema = { + "ui:options": { + label: false + } +}; +``` + +### HTML5 Input Types + +To change the input type (for example, `tel` or `email`) you can specify the `inputType` in the `ui:options` uiSchema directive. + +```jsx +const schema = {type: "string"}; +const uiSchema = { + "ui:options": { + inputType: 'tel' + } +}; +``` + +### Form attributes + +The `Form` component supports the following html attributes: + +```jsx +
    +``` + +### Disabling a form + +It's possible to disable the whole form by setting the `disabled` prop. The `disabled` prop is then forwarded down to each field of the form. + +```jsx + +``` + +If you just want to disable some of the fields, see the [`ui:disabled`](#disabled-fields) parameter in the `uiSchema` directive. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..07b63049b4 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,296 @@ +react-jsonschema-form +===================== + +[![Build Status](https://travis-ci.org/mozilla-services/react-jsonschema-form.svg)](https://travis-ci.org/mozilla-services/react-jsonschema-form) + +A simple [React](http://facebook.github.io/react/) component capable of building HTML forms out of a [JSON schema](http://json-schema.org/) and using [Bootstrap](http://getbootstrap.com/) semantics by default. + +A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) is hosted on gh-pages. + +![Image](https://i.imgur.com/M8ZCES5.gif) + +Testing powered by BrowserStack
    + + +## Philosophy + +react-jsonschema-form is meant to automatically generate a React form based on a [JSON Schema](http://json-schema.org/). It is a major component in the [kinto-admin](https://github.com/Kinto/kinto-admin/) project. If you want to generate a form for any data, sight unseen, simply given a JSON schema, react-jsonschema-form may be for you. If you have _a priori_ knowledge of your data and want a toolkit for generating forms for it, you might look elsewhere. + +react-jsonschema-form validates that the data conforms to the given schema, but doesn't prevent the user from inputing data that doesn't fit (for example, stripping non-numbers from a number field, or adding values to an array that is already "full"). + +## Installation + +Requires React 15.0.0+. + +> Note: The `master` branch of the repository reflects ongoing development. Releases are published as [tags](https://github.com/mozilla-services/react-jsonschema-form/releases). You should never blindly install from `master`, but rather check what the available stable releases are. + + +### As a npm-based project dependency + +```bash +$ npm install react-jsonschema-form --save +``` + +> Note: While the library renders [Bootstrap](http://getbootstrap.com/) HTML semantics, you have to build and load the Bootstrap styles on your own. + +### As a script served from a CDN + +```html + +``` + +Source maps are available at [this url](https://unpkg.com/react-jsonschema-form/dist/react-jsonschema-form.js.map). + +> Note: The CDN version **does not** embed `react` or `react-dom`. + +You'll also need to alias the default export property to use the Form component: + +```jsx +const Form = JSONSchemaForm.default; +// or +const {default: Form} = JSONSchemaForm; +``` + +## Usage + +```jsx +import React, { Component } from "react"; +import { render } from "react-dom"; + +import Form from "react-jsonschema-form"; + +const schema = { + title: "Todo", + type: "object", + required: ["title"], + properties: { + title: {type: "string", title: "Title", default: "A new task"}, + done: {type: "boolean", title: "Done?", default: false} + } +}; + +const log = (type) => console.log.bind(console, type); + +render(( + +), document.getElementById("app")); +``` + +This will generate a form like this (assuming you loaded the standard [Bootstrap](http://getbootstrap.com/) stylesheet): + +![](https://i.imgur.com/DZQYPyu.png) + +### Form initialization + +Often you'll want to prefill a form with existing data; this is done by passing a `formData` prop object matching the schema: + +```jsx +const formData = { + title: "First task", + done: true +}; + +render(( + +), document.getElementById("app")); +``` + +> Note: If your form has a single field, pass a single value to `formData`. ex: `formData='Charlie'` + +> WARNING: If you have situations where your parent component can re-render, make sure you listen to the `onChange` event and update the data you pass to the `formData` attribute. + +### Form event handlers + +#### Form submission + +You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form data you're usually after. The original event will also be passed as a second parameter: + +```js +const onSubmit = ({formData}, e) => console.log("Data submitted: ", formData); + +render(( + +), document.getElementById("app")); +``` + +#### Form error event handler + +To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of encountered errors: + +```js +const onError = (errors) => console.log("I have", errors.length, "errors to fix"); + +render(( + +), document.getElementById("app")); +``` + +#### Form data changes + +If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same args as `onSubmit` any time a value is updated in the form. + +#### Form field blur events + +Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass an `onBlur` handler, which will receive the id of the input that was blurred and the field value. + +#### Form field focus events + +Sometimes you may want to trigger events or modify external state when a field has been focused, so you can pass an `onFocus` handler, which will receive the id of the input that is focused and the field value. + +### Submit form programmatically +You can use the reference to get your `Form` component and call the `submit` method to submit the form programmatically without a submit button. +This method will dispatch the `submit` event of the form, and the function, that is passed to `onSubmit` props, will be called. + +```js +const onSubmit = ({formData}) => console.log("Data submitted: ", formData); +let yourForm; + +render(( + {yourForm = form;}}/> +), document.getElementById("app")); + +yourForm.submit(); +``` + +## Styling your forms + +This library renders form fields and widgets leveraging the [Bootstrap](http://getbootstrap.com/) semantics. That means your forms will be beautiful by default if you're loading its stylesheet in your page. + +You're not necessarily forced to use Bootstrap; while it uses its semantics, it also provides a bunch of other class names so you can bring new styles or override default ones quite easily in your own personalized stylesheet. That's just HTML after all :) + +If you're okay with using styles from the Bootstrap ecosystem though, then the good news is that you have access to many themes for it, which are compatible with our generated forms! + +Here are some examples from the [playground](http://mozilla-services.github.io/react-jsonschema-form/), using some of the [Bootswatch](http://bootswatch.com/) free themes: + +![](https://i.imgur.com/1Z5oUK3.png) +![](https://i.imgur.com/IMFqMwK.png) +![](https://i.imgur.com/HOACwt5.png) + +Last, if you really really want to override the semantics generated by the lib, you can always create and use your own custom [widget](advanced-customization.md#custom-widget-components), [field](advanced-customization.md#custom-field-components) and/or [schema field](advanced-customization.md#custom-schemafield) components. + + +## JSON Schema supporting status + +This component follows [JSON Schema](http://json-schema.org/documentation.html) specs. Due to the limitation of form widgets, there are some exceptions as follows: + +* `additionalItems` keyword for arrays + + This keyword works when `items` is an array. `additionalItems: true` is not supported because there's no widget to represent an item of any type. In this case it will be treated as no additional items allowed. `additionalItems` being a valid schema is supported. + +* `anyOf`, `allOf`, and `oneOf`, or multiple `types` (i.e. `"type": ["string", "array"]`) + + The `anyOf` and `oneOf` keywords are supported, however, properties declared inside the `anyOf/oneOf` should not overlap with properties "outside" of the `anyOf/oneOf`. + + You can also use `oneOf` with [schema dependencies](dependencies.md#schema-dependencies) to dynamically add schema properties based on input data. + +* `"additionalProperties":false` produces incorrect schemas when used with [schema dependencies](#schema-dependencies). This library does not remove extra properties, which causes validation to fail. It is recommended to avoid setting `"additionalProperties":false` when you use schema dependencies. See [#848](https://github.com/mozilla-services/react-jsonschema-form/issues/848) [#902](https://github.com/mozilla-services/react-jsonschema-form/issues/902) [#992](https://github.com/mozilla-services/react-jsonschema-form/issues/992) + +## Tips and tricks + + - Custom field template: + - Multi-step wizard: + - Using classNames with uiSchema: + - Conditional fields: + - Advanced conditional fields: + - Use radio list for enums: + - Reading file input data: + - Custom errors messages with transformErrors: + - 2 columns form with CSS and FieldTemplate: + - Validate and submit form from external control: + - Custom component for Help text with `ui:help`: + +## Contributing + +### Coding style + +All the JavaScript code in this project conforms to the [prettier](https://github.com/prettier/prettier) coding style. A command is provided to ensure your code is always formatted accordingly: + +``` +$ npm run cs-format +``` + +The `cs-check` command ensures all files conform to that style: + +``` +$ npm run cs-check +``` + +### Development server + +``` +$ npm start +``` + +A live development server showcasing components with hot reload enabled is available at [localhost:8080](http://localhost:8080). + +If you want the development server to listen on another host or port, you can use the RJSF_DEV_SERVER env variable: + +``` +$ RJSF_DEV_SERVER=0.0.0.0:8000 npm start +``` + +### Build documentation + +We use [mkdocs](https://www.mkdocs.org/) to build our documentation. To run documentation locally, run: +``` +$ pip install mkdocs==1.0.4 +$ mkdocs serve +``` + +Documentation will be served by [localhost:8000](http://localhost:8000). + +### Tests + +``` +$ npm test +``` + +#### TDD + +``` +$ npm run tdd +``` + +#### Code coverage + +Code coverage reports are generated using [nyc](https://github.com/istanbuljs/nyc) each time the `npm test-coverage` script is run. +The full report can be seen by opening `./coverage/lcov-report/index.html`. + +### Releasing + +``` +$ edit package.json # update version number +$ git commit -m "Bump version $VERSION" +$ git tag v$VERSION +$ npm run dist +$ npm publish +$ git push --tags origin master +``` + +## FAQ + +### Q: Does rjsf support `oneOf`, `anyOf`, multiple types in an array, etc.? + +A: The `anyOf` and `oneOf` keywords are supported, however, properties declared inside the `anyOf/oneOf` should not overlap with properties "outside" of the `anyOf/oneOf`. +There is also special cased where you can use `oneOf` in [schema dependencies](dependencies.md#schema-dependencies), If you'd like to help improve support for these keywords, see the following issues for inspiration [#329](https://github.com/mozilla-services/react-jsonschema-form/pull/329) or [#417](https://github.com/mozilla-services/react-jsonschema-form/pull/417). See also: [#52](https://github.com/mozilla-services/react-jsonschema-form/issues/52), [#151](https://github.com/mozilla-services/react-jsonschema-form/issues/151), [#171](https://github.com/mozilla-services/react-jsonschema-form/issues/171), [#200](https://github.com/mozilla-services/react-jsonschema-form/issues/200), [#282](https://github.com/mozilla-services/react-jsonschema-form/issues/282), [#302](https://github.com/mozilla-services/react-jsonschema-form/pull/302), [#330](https://github.com/mozilla-services/react-jsonschema-form/issues/330), [#430](https://github.com/mozilla-services/react-jsonschema-form/issues/430), [#522](https://github.com/mozilla-services/react-jsonschema-form/issues/522), [#538](https://github.com/mozilla-services/react-jsonschema-form/issues/538), [#551](https://github.com/mozilla-services/react-jsonschema-form/issues/551), [#552](https://github.com/mozilla-services/react-jsonschema-form/issues/552), or [#648](https://github.com/mozilla-services/react-jsonschema-form/issues/648). + +In addition, "nullable" types are supported in a narrow sense: If a property declares a type of `["", "null"]`, then `"some-type"` will be passed through as the type used to determine which widget to use for rendering the field. However, the actual rendering and handling of the field is unchanged; you are free to handle this using an approach (`'ui:emptyValue': null`, for example) best-suited to your use case. + +### Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]? + +A: Probably not. We use Bootstrap v3 and it works fine for our needs. We would like for react-jsonschema-form to support other frameworks, we just don't want to support them ourselves. Ideally, these frontend styles could be added to react-jsonschema-form with a third-party library. If there is a technical limitation preventing this, please consider opening a PR. See also: [#91](https://github.com/mozilla-services/react-jsonschema-form/issues/91), [#99](https://github.com/mozilla-services/react-jsonschema-form/issues/99), [#125](https://github.com/mozilla-services/react-jsonschema-form/issues/125), [#237](https://github.com/mozilla-services/react-jsonschema-form/issues/237), [#287](https://github.com/mozilla-services/react-jsonschema-form/issues/287), [#299](https://github.com/mozilla-services/react-jsonschema-form/issues/299), [#440](https://github.com/mozilla-services/react-jsonschema-form/issues/440), [#461](https://github.com/mozilla-services/react-jsonschema-form/issues/461), [#546](https://github.com/mozilla-services/react-jsonschema-form/issues/546), [#555](https://github.com/mozilla-services/react-jsonschema-form/issues/555), [#626](https://github.com/mozilla-services/react-jsonschema-form/issues/626), and [#623](https://github.com/mozilla-services/react-jsonschema-form/pull/623). + +### Q: Is there a way to "collapse" fields, for instance to show/hide individual fields? + +A: There's no specific built-in way to do this, but you can write your own FieldTemplate that supports hiding/showing fields according to user input. We don't yet have an example of this use, but if you write one, please add it to the "tips and tricks" section, above. See also: [#268](https://github.com/mozilla-services/react-jsonschema-form/issues/268), [#304](https://github.com/mozilla-services/react-jsonschema-form/pull/304), [#598](https://github.com/mozilla-services/react-jsonschema-form/issues/598), [#920](https://github.com/mozilla-services/react-jsonschema-form/issues/920). + +## License + +Apache 2 diff --git a/docs/validation.md b/docs/validation.md new file mode 100644 index 0000000000..0f1d3c2daa --- /dev/null +++ b/docs/validation.md @@ -0,0 +1,165 @@ +## Form data validation + +### Live validation + +By default, form data are only validated when the form is submitted or when a new `formData` prop is passed to the `Form` component. + +You can enable live form data validation by passing a `liveValidate` prop to the `Form` component, and set it to `true`. Then, everytime a value changes within the form data tree (eg. the user entering a character in a field), a validation operation is performed, and the validation results are reflected into the form state. + +Be warned that this is an expensive strategy, with possibly strong impact on performances. + +To disable validation entirely, you can set Form's `noValidate` prop to `true`. + +### HTML5 Validation + +By default, required field errors will cause the browser to display its standard HTML5 `required` attribute error messages and prevent form submission. If you would like to turn this off, you can set Form's `noHtml5Validate` prop to `true`, which will set `noValidate` on the `form` element. + +### Custom validation + +Form data is always validated against the JSON schema. + +But it is possible to define your own custom validation rules. This is especially useful when the validation depends on several interdependent fields. + +```js +function validate(formData, errors) { + if (formData.pass1 !== formData.pass2) { + errors.pass2.addError("Passwords don't match"); + } + return errors; +} + +const schema = { + type: "object", + properties: { + pass1: {type: "string", minLength: 3}, + pass2: {type: "string", minLength: 3}, + } +}; + +render(( + +), document.getElementById("app")); +``` + +> Notes: +> - The `validate()` function must **always** return the `errors` object +> received as second argument. +> - The `validate()` function is called **after** the JSON schema validation. + +### Custom string formats + +[Pre-defined semantic formats](https://json-schema.org/latest/json-schema-validation.html#rfc.section.7) are limited. react-jsonschema-form adds two formats, `color` and `data-url`, to support certain [alternative widgets](form-customization.md#alternative-widgets). You can add formats of your own through the `customFormats` prop to your `Form` component: + +```jsx +const schema = { + phoneNumber: { + type: 'string', + format: 'phone-us' + } +}; + +const customFormats = { + 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/ +}; + +render(( + +), document.getElementById("app")); +``` + +Format values can be anything AJV’s [`addFormat` method](https://github.com/epoberezkin/ajv#addformatstring-name-stringregexpfunctionobject-format---ajv) accepts. + +### Custom schema validation + +To have your schemas validated against any other meta schema than draft-07 (the current version of [JSON Schema](http://json-schema.org/)), make sure your schema has a `$schema` attribute that enables the validator to use the correct meta schema. For example: + +```json +{ + "$schema": "http://json-schema.org/draft-04/schema#", + ... +} +``` + +Note that react-jsonschema-form only supports the latest version of JSON Schema, draft-07, by default. To support additional meta schemas pass them through the `additionalMetaSchemas` prop to your `Form` component: + +```jsx +const additionalMetaSchemas = require("ajv/lib/refs/json-schema-draft-04.json"); + +render(( + +), document.getElementById("app")); +``` + +In this example `schema` passed as props to `Form` component can be validated against draft-07 (default) and by draft-04 (added), depending on the value of `$schema` attribute. + +`additionalMetaSchemas` also accepts more than one meta schema: + +```jsx +render(( + +), document.getElementById("app")); +``` + +### Custom error messages + +Validation error messages are provided by the JSON Schema validation by default. If you need to change these messages or make any other modifications to the errors from the JSON Schema validation, you can define a transform function that receives the list of JSON Schema errors and returns a new list. + +```js +function transformErrors(errors) { + return errors.map(error => { + if (error.name === "pattern") { + error.message = "Only digits are allowed" + } + return error; + }); +} + +const schema = { + type: "object", + properties: { + onlyNumbersString: {type: "string", pattern: "^\\d*$"}, + } +}; + +render(( + +), document.getElementById("app")); +``` + +> Notes: +> - The `transformErrors()` function must return the list of errors. Modifying the list in place without returning it will result in an error. + +Each element in the `errors` list passed to `transformErrors` has the following properties: + +- `name`: name of the error, for example, "required" or "minLength" +- `message`: message, for example, "is a required property" or "should NOT be shorter than 3 characters" +- `params`: an object with the error params returned by ajv ([see doc](https://github.com/epoberezkin/ajv#error-parameters) for more info). +- `property`: a string in Javascript property accessor notation to the data path of the field with the error. For example, `.name` or `['first-name']`. +- `stack`: full error name, for example ".name is a required property". +- `schemaPath`: JSON pointer to the schema of the keyword that failed validation. For example, `#/fields/firstName/required`. (Note: this may sometimes be wrong due to a [https://github.com/epoberezkin/ajv/issues/512](bug in ajv)). + +### Error List Display + +To disable rendering of the error list at the top of the form, you can set the `showErrorList` prop to `false`. Doing so will still validate the form, but only the inline display will show. + +```js +render(( + +), document.getElementById("app")); +``` + +> Note: you can also use your own [ErrorList](advanced-customization.md#error-list-template) + +### The case of empty strings + +When a text input is empty, the field in form data is set to `undefined`. String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `undefined`. + +One consequence of this is that if you have an empty string in your `enum` array, selecting that option in the `select` input will cause the field to be set to `undefined`, not an empty string. + +If you want to have the field set to a default value when empty you can provide a `ui:emptyValue` field in the `uiSchema` object. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..d3d8fcbdcc --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: react-jsonschema-form documentation +docs_dir: docs +theme: readthedocs + +nav: + - Introduction: index.md + - Advanced Customization: advanced-customization.md + - Definitions: definitions.md + - Dependencies: dependencies.md + - Form Customization: form-customization.md + - Validation: validation.md + - Playground: https://mozilla-services.github.io/react-jsonschema-form/ + +markdown_extensions: + - toc: + permalink: true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 29ad37eaa9..772b910910 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,210 @@ { "name": "@airhelp/react-jsonschema-form", - "version": "1.0.4", + "version": "2.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.3.tgz", + "integrity": "sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==", + "dev": true, + "requires": { + "@babel/types": "^7.3.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.3.tgz", + "integrity": "sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==", + "dev": true + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" + } + }, + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.7.8", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz", @@ -254,14 +455,14 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-errors": { @@ -426,11 +627,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -1354,13 +1550,6 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" - } } }, "babel-template": { @@ -1775,12 +1964,6 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, "caniuse-api": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", @@ -1993,39 +2176,6 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -2035,7 +2185,8 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "coa": { "version": "1.0.4", @@ -2246,9 +2397,9 @@ "dev": true }, "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" }, "core-util-is": { "version": "1.0.2", @@ -2771,14 +2922,6 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -2936,6 +3079,18 @@ "text-table": "~0.2.0" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -2988,6 +3143,12 @@ "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=", "dev": true }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, "globals": { "version": "11.5.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", @@ -3010,6 +3171,12 @@ "esprima": "^4.0.0" } }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -3378,9 +3545,9 @@ "dev": true }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -3399,27 +3566,6 @@ "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", "dev": true }, - "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.9" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } - } - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -3666,24 +3812,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "optional": true, "requires": { @@ -3693,12 +3843,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -3707,34 +3859,40 @@ }, "chownr": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "requires": { @@ -3743,25 +3901,29 @@ }, "deep-extend": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -3770,13 +3932,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -3792,7 +3956,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "optional": true, "requires": { @@ -3806,13 +3971,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.21", - "bundled": true, + "resolved": false, + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "dev": true, "optional": true, "requires": { @@ -3821,7 +3988,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -3830,7 +3998,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -3840,18 +4009,21 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -3859,13 +4031,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -3873,12 +4047,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "minipass": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, "requires": { "safe-buffer": "^5.1.1", @@ -3887,7 +4063,8 @@ }, "minizlib": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "dev": true, "optional": true, "requires": { @@ -3896,7 +4073,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -3904,13 +4082,15 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "needle": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "dev": true, "optional": true, "requires": { @@ -3921,7 +4101,8 @@ }, "node-pre-gyp": { "version": "0.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "dev": true, "optional": true, "requires": { @@ -3939,7 +4120,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -3949,13 +4131,15 @@ }, "npm-bundled": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "dev": true, "optional": true, "requires": { @@ -3965,7 +4149,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -3977,18 +4162,21 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -3996,19 +4184,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -4018,19 +4209,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "dev": true, "optional": true, "requires": { @@ -4042,7 +4236,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -4050,7 +4245,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -4065,7 +4261,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "optional": true, "requires": { @@ -4074,42 +4271,49 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -4119,7 +4323,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -4128,7 +4333,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -4136,13 +4342,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "dev": true, "optional": true, "requires": { @@ -4157,13 +4365,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "optional": true, "requires": { @@ -4172,12 +4382,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", "dev": true } } @@ -4388,13 +4600,39 @@ "requires": { "ajv": "^5.1.0", "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + } + } + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, "requires": { "function-bind": "^1.0.2" } @@ -4601,6 +4839,7 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -4831,12 +5070,6 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ipaddr.js": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", @@ -5085,7 +5318,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-svg": { "version": "2.1.0", @@ -5108,12 +5342,6 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -5142,21 +5370,33 @@ "isarray": "1.0.0" } }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + }, "js-base64": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", @@ -5243,9 +5483,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -5292,15 +5532,6 @@ "is-buffer": "^1.1.5" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5726,6 +5957,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, "requires": { "js-tokens": "^3.0.0" } @@ -5781,12 +6013,6 @@ "object-visit": "^1.0.0" } }, - "marked": { - "version": "0.3.19", - "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, "math-expression-evaluator": { "version": "1.2.17", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", @@ -6204,15 +6430,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, "node-libs-browser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", @@ -6252,92 +6469,1126 @@ } } }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "npm-path": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", - "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", - "dev": true, - "requires": { - "which": "^1.2.10" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", - "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", - "dev": true, - "requires": { - "commander": "^2.9.0", - "npm-path": "^2.0.2", - "which": "^1.2.10" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwmatcher": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", - "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", - "dev": true - }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-path": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", + "dev": true, + "requires": { + "which": "^1.2.10" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", + "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", + "dev": true, + "requires": { + "commander": "^2.9.0", + "npm-path": "^2.0.2", + "which": "^1.2.10" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "dev": true + }, + "nyc": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", + "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^3.0.1", + "convert-source-map": "^1.6.0", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "make-dir": "^1.3.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.1.0", + "uuid": "^3.3.2", + "yargs": "^12.0.5", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caching-transform": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^1.3.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "camelcase": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commander": { + "version": "2.17.1", + "bundled": true, + "dev": true, + "optional": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "hasha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "bundled": true, + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.10", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "uglify-js": { + "version": "3.4.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "12.0.5", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -6514,15 +7765,6 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -7433,14 +8675,6 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -7448,13 +8682,23 @@ "dev": true }, "prop-types": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", - "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } } }, "proxy-addr": { @@ -7697,6 +8941,12 @@ } } }, + "react-addons-test-utils": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", + "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=", + "dev": true + }, "react-codemirror2": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-4.3.0.tgz", @@ -7733,6 +8983,11 @@ } } }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + }, "react-proxy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", @@ -8164,7 +9419,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "samsam": { "version": "1.1.2", @@ -8271,7 +9527,8 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true }, "setprototypeof": { "version": "1.1.0", @@ -8805,6 +10062,18 @@ "string-width": "^2.1.1" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -8825,12 +10094,24 @@ "supports-color": "^5.3.0" } }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -8942,16 +10223,6 @@ } } }, - "toctoc": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/toctoc/-/toctoc-0.2.4.tgz", - "integrity": "sha512-0m1SIvDrHAF/ujztVqmiqg147NwzBRag5Bdw0gCvVWWRCyA/w4sXdRqGoaTAF68hzOeXwgcKgi1BUcRJhrUZvw==", - "dev": true, - "requires": { - "marked": "^0.3.9", - "yargs": "^4.3.1" - } - }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", @@ -9032,11 +10303,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "ua-parser-js": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", - "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" - }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", @@ -9252,7 +10518,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" }, @@ -9260,8 +10525,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -10459,11 +11723,6 @@ } } }, - "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" - }, "whatwg-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz", @@ -10489,18 +11748,6 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "window-size": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", - "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", - "dev": true - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -10598,128 +11845,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true - }, - "yargs": { - "version": "4.8.1", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", - "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", - "dev": true, - "requires": { - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "lodash.assign": "^4.0.3", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.1", - "which-module": "^1.0.0", - "window-size": "^0.2.0", - "y18n": "^3.2.1", - "yargs-parser": "^2.4.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "yargs-parser": { - "version": "2.4.1", - "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", - "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "lodash.assign": "^4.0.6" - }, - "dependencies": { - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - } - } } } } diff --git a/package.json b/package.json index 636e1c6ef0..d3ed8e1ee3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,8 @@ { "name": "@airhelp/react-jsonschema-form", - "version": "1.0.4", + "version": "2.0.1", "description": "A simple React component capable of building HTML forms out of a JSON schema.", "scripts": { - "build:readme": "toctoc README.md -w", "build:lib": "rimraf lib && cross-env NODE_ENV=production babel -d lib/ src/", "build:dist": "rimraf dist && cross-env NODE_ENV=production webpack --config webpack.config.dist.js", "build:playground": "rimraf build && cross-env NODE_ENV=production webpack --config webpack.config.prod.js && cp playground/index.prod.html build/index.html", @@ -14,11 +13,13 @@ "prepare": "npm run dist", "precommit": "lint-staged", "publish-to-gh-pages": "npm run build:playground && gh-pages --dist build/", - "publish-to-npm": "npm run build:readme && npm run dist && npm publish", - "preversion": "npm run build:playground && npm run dist && npm run build:readme && npm run cs-check && npm run lint", + "publish-to-npm": "npm run dist && npm publish", + "preversion": "npm run build:playground && npm run dist && npm run cs-check && npm run lint", "start": "node devServer.js", "tdd": "cross-env NODE_ENV=test mocha --require babel-register --watch --require ./test/setup-jsdom.js test/**/*_test.js", - "test": "cross-env NODE_ENV=test mocha --require babel-register --require ./test/setup-jsdom.js test/**/*_test.js" + "test": "cross-env NODE_ENV=test mocha --require babel-register --require ./test/setup-jsdom.js test/**/*_test.js", + "test-coverage": "cross-env NODE_ENV=test nyc --reporter=lcov mocha --require babel-register --require ./test/setup-jsdom.js test/**/*_test.js", + "test-debug": "cross-env NODE_ENV=test mocha --require babel-register --require ./test/setup-jsdom.js --debug-brk --inspect test/Form_test.js" }, "prettierOptions": "--jsx-bracket-same-line --trailing-comma es5 --semi --tab-width 2", "lint-staged": { @@ -43,7 +44,7 @@ "react-dom": ">=16" }, "dependencies": { - "ajv": "^5.2.3", + "ajv": "^6.7.0", "babel-runtime": "^6.26.0", "core-js": "^2.5.7", "lodash.topath": "^4.5.2", @@ -83,8 +84,10 @@ "lint-staged": "^3.3.1", "mini-css-extract-plugin": "^0.4.3", "mocha": "^5.2.0", + "nyc": "^13.2.0", "prettier": "^1.15.1", "react": "^16.7.0", + "react-addons-test-utils": "^15.3.2", "react-codemirror2": "^4.1.0", "react-dom": "^16.7.0", "react-transform-catch-errors": "^1.0.0", @@ -93,7 +96,6 @@ "rimraf": "^2.5.4", "sinon": "^1.17.6", "style-loader": "^0.13.1", - "toctoc": "^0.2.3", "webpack": "^4.20.2", "webpack-cli": "^3.1.2", "webpack-dev-middleware": "^3.4.0", diff --git a/playground/app.js b/playground/app.js index 24570a31fb..b7e370cb70 100644 --- a/playground/app.js +++ b/playground/app.js @@ -359,6 +359,9 @@ class App extends Component { load = data => { // Reset the ArrayFieldTemplate whenever you load new data const { ArrayFieldTemplate, ObjectFieldTemplate } = data; + // uiSchema is missing on some examples. Provide a default to + // clear the field in all cases. + const { uiSchema = {} } = data; // force resetting form component instance this.setState({ form: false }, _ => this.setState({ @@ -366,6 +369,7 @@ class App extends Component { form: true, ArrayFieldTemplate, ObjectFieldTemplate, + uiSchema, }) ); }; @@ -474,9 +478,10 @@ class App extends Component { uiSchema={uiSchema} formData={formData} onChange={this.onFormDataChange} - onSubmit={({ formData }) => - console.log("submitted formData", formData) - } + onSubmit={({ formData }, e) => { + console.log("submitted formData", formData); + console.log("submit event", e); + }} fields={{ geo: GeoPosition }} validate={validate} onBlur={(id, value) => diff --git a/playground/samples/alternatives.js b/playground/samples/alternatives.js index cfdbc6e441..fd1647cfaa 100644 --- a/playground/samples/alternatives.js +++ b/playground/samples/alternatives.js @@ -22,6 +22,20 @@ module.exports = { }, ], }, + Toggle: { + title: "Toggle", + type: "boolean", + oneOf: [ + { + title: "Enable", + const: true, + }, + { + title: "Disable", + const: false, + }, + ], + }, }, title: "Image editor", type: "object", @@ -39,6 +53,10 @@ module.exports = { }, title: "Color mask", }, + toggleMask: { + title: "Apply color mask", + $ref: "#/definitions/Toggle", + }, colorPalette: { type: "array", title: "Color palette", @@ -58,6 +76,9 @@ module.exports = { blendMode: { "ui:enumDisabled": ["multiply"], }, + toggleMask: { + "ui:widget": "radio", + }, }, formData: { currentColor: "#00ff00", diff --git a/playground/samples/anyOf.js b/playground/samples/anyOf.js new file mode 100644 index 0000000000..8ff87f8523 --- /dev/null +++ b/playground/samples/anyOf.js @@ -0,0 +1,59 @@ +module.exports = { + schema: { + type: "object", + properties: { + age: { + type: "integer", + title: "Age", + }, + items: { + type: "array", + items: { + type: "object", + anyOf: [ + { + properties: { + foo: { + type: "string", + }, + }, + }, + { + properties: { + bar: { + type: "string", + }, + }, + }, + ], + }, + }, + }, + anyOf: [ + { + title: "First method of identification", + properties: { + firstName: { + type: "string", + title: "First name", + default: "Chuck", + }, + lastName: { + type: "string", + title: "Last name", + }, + }, + }, + { + title: "Second method of identification", + properties: { + idCode: { + type: "string", + title: "ID code", + }, + }, + }, + ], + }, + formData: {}, +}; diff --git a/playground/samples/index.js b/playground/samples/index.js index b496c0fb6d..4e6a4fa031 100644 --- a/playground/samples/index.js +++ b/playground/samples/index.js @@ -1,4 +1,6 @@ import arrays from "./arrays"; +import anyOf from "./anyOf"; +import oneOf from "./oneOf"; import nested from "./nested"; import numbers from "./numbers"; import simple from "./simple"; @@ -18,6 +20,8 @@ import alternatives from "./alternatives"; import propertyDependencies from "./propertyDependencies"; import schemaDependencies from "./schemaDependencies"; import additionalProperties from "./additionalProperties"; +import nullable from "./nullable"; +import nullField from "./null"; export const samples = { Simple: simple, @@ -40,4 +44,8 @@ export const samples = { "Property dependencies": propertyDependencies, "Schema dependencies": schemaDependencies, "Additional Properties": additionalProperties, + "Any Of": anyOf, + "One Of": oneOf, + "Null fields": nullField, + Nullable: nullable, }; diff --git a/playground/samples/null.js b/playground/samples/null.js new file mode 100644 index 0000000000..e6154e6a1e --- /dev/null +++ b/playground/samples/null.js @@ -0,0 +1,28 @@ +module.exports = { + schema: { + title: "Null field example", + description: "A short form with a null field", + type: "object", + required: ["firstName"], + properties: { + helpText: { + title: "A null field", + description: + "Null fields like this are great for adding extra information", + type: "null", + }, + firstName: { + type: "string", + title: "A regular string field", + default: "Chuck", + }, + }, + }, + uiSchema: { + firstName: { + "ui:autofocus": true, + "ui:emptyValue": "", + }, + }, + formData: {}, +}; diff --git a/playground/samples/nullable.js b/playground/samples/nullable.js new file mode 100644 index 0000000000..2ccc62de9b --- /dev/null +++ b/playground/samples/nullable.js @@ -0,0 +1,73 @@ +module.exports = { + schema: { + title: "A registration form (nullable)", + description: "A simple form example using nullable types", + type: "object", + required: ["firstName", "lastName"], + properties: { + firstName: { + type: "string", + title: "First name", + default: "Chuck", + }, + lastName: { + type: "string", + title: "Last name", + }, + age: { + type: ["integer", "null"], + title: "Age", + }, + bio: { + type: ["string", "null"], + title: "Bio", + }, + password: { + type: "string", + title: "Password", + minLength: 3, + }, + telephone: { + type: "string", + title: "Telephone", + minLength: 10, + }, + }, + }, + uiSchema: { + firstName: { + "ui:autofocus": true, + "ui:emptyValue": "", + }, + age: { + "ui:widget": "updown", + "ui:title": "Age of person", + "ui:description": "(earthian year)", + "ui:emptyValue": null, + }, + bio: { + "ui:widget": "textarea", + "ui:placeholder": + "Leaving this field empty will cause formData property to be `null`", + "ui:emptyValue": null, + }, + password: { + "ui:widget": "password", + "ui:help": "Hint: Make it strong!", + }, + date: { + "ui:widget": "alt-datetime", + }, + telephone: { + "ui:options": { + inputType: "tel", + }, + }, + }, + formData: { + lastName: "Norris", + age: 75, + bio: null, + password: "noneed", + }, +}; diff --git a/playground/samples/oneOf.js b/playground/samples/oneOf.js new file mode 100644 index 0000000000..4ed4004916 --- /dev/null +++ b/playground/samples/oneOf.js @@ -0,0 +1,24 @@ +module.exports = { + schema: { + type: "object", + oneOf: [ + { + properties: { + lorem: { + type: "string", + }, + }, + required: ["lorem"], + }, + { + properties: { + ipsum: { + type: "string", + }, + }, + required: ["ipsum"], + }, + ], + }, + formData: {}, +}; diff --git a/playground/samples/widgets.js b/playground/samples/widgets.js index 9a7d575d13..3530334440 100644 --- a/playground/samples/widgets.js +++ b/playground/samples/widgets.js @@ -73,6 +73,12 @@ module.exports = { title: "A readonly field", default: "I am read-only.", }, + readonly2: { + type: "string", + title: "Another readonly field", + default: "I am also read-only.", + readOnly: true, + }, widgetOptions: { title: "Custom widget with options", type: "string", diff --git a/src/components/Form.js b/src/components/Form.js index 6863d74374..bded378062 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -58,9 +58,10 @@ export default class Form extends Component { const { definitions } = schema; const formData = getDefaultFormState(schema, props.formData, definitions); const retrievedSchema = retrieveSchema(schema, definitions, formData); - + const customFormats = props.customFormats; + const additionalMetaSchemas = props.additionalMetaSchemas; const { errors, errorSchema } = mustValidate - ? this.validate(formData, schema) + ? this.validate(formData, schema, additionalMetaSchemas, customFormats) : { errors: state.errors || [], errorSchema: state.errorSchema || {}, @@ -80,6 +81,7 @@ export default class Form extends Component { edit, errors, errorSchema, + additionalMetaSchemas, }; } @@ -87,7 +89,12 @@ export default class Form extends Component { return shouldRender(this, nextProps, nextState); } - validate(formData, schema = this.props.schema) { + validate( + formData, + schema = this.props.schema, + additionalMetaSchemas = this.props.additionalMetaSchemas, + customFormats = this.props.customFormats + ) { const { validate, transformErrors } = this.props; const { definitions } = this.getRegistry(); const resolvedSchema = retrieveSchema(schema, definitions, formData); @@ -95,7 +102,9 @@ export default class Form extends Component { formData, resolvedSchema, validate, - transformErrors + transformErrors, + additionalMetaSchemas, + customFormats ); } @@ -151,6 +160,7 @@ export default class Form extends Component { onSubmit = event => { event.preventDefault(); + event.persist(); if (!this.props.noValidate) { const { errors, errorSchema } = this.validate(this.state.formData); @@ -168,7 +178,7 @@ export default class Form extends Component { this.setState({ errors: [], errorSchema: {} }, () => { if (this.props.onSubmit) { - this.props.onSubmit({ ...this.state, status: "submitted" }); + this.props.onSubmit({ ...this.state, status: "submitted" }, event); } }); }; @@ -250,11 +260,11 @@ export default class Form extends Component { {children ? ( children ) : ( -

    +

    -

    +
    )} ); @@ -294,5 +304,7 @@ if (process.env.NODE_ENV !== "production") { transformErrors: PropTypes.func, safeRenderCompletion: PropTypes.bool, formContext: PropTypes.object, + customFormats: PropTypes.object, + additionalMetaSchemas: PropTypes.arrayOf(PropTypes.object), }; } diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 79638bab58..16ecdf23a9 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -1,8 +1,8 @@ import AddButton from "../AddButton"; import IconButton from "../IconButton"; import React, { Component } from "react"; -import PropTypes from "prop-types"; import includes from "core-js/library/fn/array/includes"; +import * as types from "../../types"; import UnsupportedField from "./UnsupportedField"; import { @@ -21,8 +21,7 @@ import { function ArrayFieldTitle({ TitleField, idSchema, title, required }) { if (!title) { - // See #312: Ensure compatibility with old versions of React. - return
    ; + return null; } const id = `${idSchema.$id}__title`; return ; @@ -30,8 +29,7 @@ function ArrayFieldTitle({ TitleField, idSchema, title, required }) { function ArrayFieldDescription({ DescriptionField, idSchema, description }) { if (!description) { - // See #312: Ensure compatibility with old versions of React. - return
    ; + return null; } const id = `${idSchema.$id}__description`; return ; @@ -91,7 +89,7 @@ function DefaultArrayItem(props) { function DefaultFixedArrayFieldTemplate(props) { return ( -
    +
    +
    { - // i is string, index and newIndex are numbers, - // so using "==" to compare - if (i == newIndex) { - return formData[index]; - } else if (i == index) { - return formData[newIndex]; - } else { - return item; - } - }), - newErrorSchema - ); + + function reOrderArray() { + // Copy item + let newFormData = formData.slice(); + + // Moves item from index to newIndex + newFormData.splice(index, 1); + newFormData.splice(newIndex, 0, formData[index]); + + return newFormData; + } + + onChange(reOrderArray(), newErrorSchema); }; }; @@ -412,6 +409,7 @@ class ArrayField extends Component { formData, disabled, readonly, + required, autofocus, onBlur, onFocus, @@ -439,6 +437,7 @@ class ArrayField extends Component { value={items} disabled={disabled} readonly={readonly} + required={required} formContext={formContext} autofocus={autofocus} rawErrors={rawErrors} @@ -649,34 +648,7 @@ class ArrayField extends Component { } if (process.env.NODE_ENV !== "production") { - ArrayField.propTypes = { - schema: PropTypes.object.isRequired, - uiSchema: PropTypes.shape({ - "ui:options": PropTypes.shape({ - addable: PropTypes.bool, - orderable: PropTypes.bool, - removable: PropTypes.bool, - }), - }), - idSchema: PropTypes.object, - errorSchema: PropTypes.object, - onChange: PropTypes.func.isRequired, - onBlur: PropTypes.func, - onFocus: PropTypes.func, - formData: PropTypes.array, - required: PropTypes.bool, - disabled: PropTypes.bool, - readonly: PropTypes.bool, - autofocus: PropTypes.bool, - registry: PropTypes.shape({ - widgets: PropTypes.objectOf( - PropTypes.oneOfType([PropTypes.func, PropTypes.object]) - ).isRequired, - fields: PropTypes.objectOf(PropTypes.func).isRequired, - definitions: PropTypes.object.isRequired, - formContext: PropTypes.object.isRequired, - }), - }; + ArrayField.propTypes = types.fieldProps; } export default ArrayField; diff --git a/src/components/fields/BooleanField.js b/src/components/fields/BooleanField.js index 00014f28c9..37f134ed2d 100644 --- a/src/components/fields/BooleanField.js +++ b/src/components/fields/BooleanField.js @@ -1,5 +1,5 @@ import React from "react"; -import PropTypes from "prop-types"; +import * as types from "../../types"; import { getWidget, @@ -29,10 +29,27 @@ function BooleanField(props) { const { widgets, formContext } = registry; const { widget = "checkbox", ...options } = getUiOptions(uiSchema); const Widget = getWidget(schema, widget, widgets); - const enumOptions = optionsList({ - enum: [true, false], - enumNames: schema.enumNames || ["yes", "no"], - }); + + let enumOptions; + + if (Array.isArray(schema.oneOf)) { + enumOptions = optionsList({ + oneOf: schema.oneOf.map(option => ({ + ...option, + title: option.title || (option.const === true ? "yes" : "no"), + })), + }); + } else { + enumOptions = optionsList({ + enum: schema.enum || [true, false], + enumNames: + schema.enumNames || + (schema.enum && schema.enum[0] === false + ? ["no", "yes"] + : ["yes", "no"]), + }); + } + return ( ({ + required: [key], + })), + }; + + let augmentedSchema; + + // If the "anyOf" keyword already exists, wrap the augmentation in an "allOf" + if (option.anyOf) { + // Create a shallow clone of the option + const { ...shallowClone } = option; + + if (!shallowClone.allOf) { + shallowClone.allOf = []; + } else { + // If "allOf" already exists, shallow clone the array + shallowClone.allOf = shallowClone.allOf.slice(); + } + + shallowClone.allOf.push(requiresAnyOf); + + augmentedSchema = shallowClone; + } else { + augmentedSchema = Object.assign({}, option, requiresAnyOf); + } + + // Remove the "required" field as it's likely that not all fields have + // been filled in yet, which will mean that the schema is not valid + delete augmentedSchema.required; + + if (isValid(augmentedSchema, formData)) { + return i; + } + } else if (isValid(options[i], formData)) { + return i; + } + } + + // If the form data matches none of the options, use the first option + return 0; + } + + onOptionChange = option => { + const selectedOption = parseInt(option, 10); + const { formData, onChange, options } = this.props; + + const newOption = options[selectedOption]; + + // If the new option is of type object and the current data is an object, + // discard properties added using the old option. + if ( + guessType(formData) === "object" && + (newOption.type === "object" || newOption.properties) + ) { + const newFormData = Object.assign({}, formData); + + const optionsToDiscard = options.slice(); + optionsToDiscard.splice(selectedOption, 1); + + // Discard any data added using other options + for (const option of optionsToDiscard) { + if (option.properties) { + for (const key in option.properties) { + if (newFormData.hasOwnProperty(key)) { + delete newFormData[key]; + } + } + } + } + + onChange(newFormData); + } else { + onChange(undefined); + } + + this.setState({ + selectedOption: parseInt(option, 10), + }); + }; + + render() { + const { + baseType, + disabled, + errorSchema, + formData, + idPrefix, + idSchema, + onBlur, + onChange, + onFocus, + options, + registry, + safeRenderCompletion, + uiSchema, + } = 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; + + if (option) { + // If the subschema doesn't declare a type, infer the type from the + // parent schema + optionSchema = option.type + ? option + : Object.assign({}, option, { type: baseType }); + } + + const enumOptions = options.map((option, index) => ({ + label: option.title || `Option ${index + 1}`, + value: index, + })); + + return ( +
    +
    + +
    + + {option !== null && ( + <_SchemaField + schema={optionSchema} + uiSchema={uiSchema} + errorSchema={errorSchema} + idSchema={idSchema} + idPrefix={idPrefix} + formData={formData} + onChange={onChange} + onBlur={onBlur} + onFocus={onFocus} + registry={registry} + safeRenderCompletion={safeRenderCompletion} + disabled={disabled} + /> + )} +
    + ); + } +} + +AnyOfField.defaultProps = { + disabled: false, + errorSchema: {}, + idSchema: {}, + uiSchema: {}, +}; + +if (process.env.NODE_ENV !== "production") { + AnyOfField.propTypes = { + options: PropTypes.arrayOf(PropTypes.object).isRequired, + baseType: PropTypes.string, + uiSchema: PropTypes.object, + idSchema: PropTypes.object, + formData: PropTypes.any, + errorSchema: PropTypes.object, + registry: types.registry.isRequired, + }; +} + +export default AnyOfField; diff --git a/src/components/fields/NullField.js b/src/components/fields/NullField.js new file mode 100644 index 0000000000..f82e436626 --- /dev/null +++ b/src/components/fields/NullField.js @@ -0,0 +1,20 @@ +import { Component } from "react"; +import * as types from "../../types"; + +class NullField extends Component { + componentDidMount() { + if (this.props.formData === undefined) { + this.props.onChange(null); + } + } + + render() { + return null; + } +} + +if (process.env.NODE_ENV !== "production") { + NullField.propTypes = types.fieldProps; +} + +export default NullField; diff --git a/src/components/fields/NumberField.js b/src/components/fields/NumberField.js index 30c297b19b..396da678ca 100644 --- a/src/components/fields/NumberField.js +++ b/src/components/fields/NumberField.js @@ -1,28 +1,95 @@ import React from "react"; -import PropTypes from "prop-types"; +import * as types from "../../types"; import { asNumber } from "../../utils"; -function NumberField(props) { - const { StringField } = props.registry.fields; - return ( - props.onChange(asNumber(value))} - /> - ); +// Matches a string that ends in a . character, optionally followed by a sequence of +// digits followed by any number of 0 characters up until the end of the line. +// Ensuring that there is at least one prefixed character is important so that +// you don't incorrectly match against "0". +const trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/; + +// This is used for trimming the trailing 0 and . characters without affecting +// the rest of the string. Its possible to use one RegEx with groups for this +// functionality, but it is fairly complex compared to simply defining two +// different matchers. +const trailingCharMatcher = /[0.]0*$/; + +/** + * The NumberField class has some special handling for dealing with trailing + * decimal points and/or zeroes. This logic is designed to allow trailing values + * to be visible in the input element, but not be represented in the + * corresponding form data. + * + * The algorithm is as follows: + * + * 1. When the input value changes the value is cached in the component state + * + * 2. The value is then normalized, removing trailing decimal points and zeros, + * then passed to the "onChange" callback + * + * 3. When the component is rendered, the formData value is checked against the + * value cached in the state. If it matches the cached value, the cached + * value is passed to the input instead of the formData value + */ +class NumberField extends React.Component { + constructor(props) { + super(props); + + this.state = { + lastValue: props.value, + }; + } + + handleChange = value => { + // Cache the original value in component state + this.setState({ lastValue: value }); + + // Normalize decimals that don't start with a zero character in advance so + // that the rest of the normalization logic is simpler + if (`${value}`.charAt(0) === ".") { + value = `0${value}`; + } + + // Check that the value is a string (this can happen if the widget used is a + //