├── .npmignore ├── test ├── mocha.opts ├── .eslintrc ├── setup-jsdom.js ├── test_utils.js ├── const_test.js ├── NullField_test.js ├── DescriptionField_test.js ├── TitleField_test.js ├── FieldTemplate_test.js ├── ObjectFieldTemplate_test.js ├── FormContext_test.js ├── performance_test.js ├── withTheme_test.js └── ArrayFieldTemplate_test.js ├── netlify.toml ├── .editorconfig ├── src ├── index.js ├── components │ ├── IconButton.js │ ├── widgets │ │ ├── URLWidget.js │ │ ├── EmailWidget.js │ │ ├── PasswordWidget.js │ │ ├── TextWidget.js │ │ ├── DateWidget.js │ │ ├── UpDownWidget.js │ │ ├── HiddenWidget.js │ │ ├── RangeWidget.js │ │ ├── AltDateTimeWidget.js │ │ ├── ColorWidget.js │ │ ├── index.js │ │ ├── TextareaWidget.js │ │ ├── DateTimeWidget.js │ │ ├── RadioWidget.js │ │ ├── CheckboxWidget.js │ │ ├── BaseInput.js │ │ ├── CheckboxesWidget.js │ │ ├── FileWidget.js │ │ ├── SelectWidget.js │ │ └── AltDateWidget.js │ ├── fields │ │ ├── NullField.js │ │ ├── TitleField.js │ │ ├── DescriptionField.js │ │ ├── index.js │ │ ├── UnsupportedField.js │ │ ├── StringField.js │ │ ├── BooleanField.js │ │ ├── NumberField.js │ │ ├── MultiSchemaField.js │ │ └── ObjectField.js │ ├── AddButton.js │ └── ErrorList.js ├── withTheme.js ├── types.js └── validate.js ├── playground ├── samples │ ├── single.js │ ├── custom.js │ ├── oneOf.js │ ├── files.js │ ├── null.js │ ├── additionalProperties.js │ ├── errors.js │ ├── ordering.js │ ├── large.js │ ├── validation.js │ ├── anyOf.js │ ├── date.js │ ├── numbers.js │ ├── references.js │ ├── simple.js │ ├── customArray.js │ ├── index.js │ ├── customObject.js │ ├── nested.js │ ├── nullable.js │ ├── alternatives.js │ ├── propertyDependencies.js │ ├── arrays.js │ ├── widgets.js │ └── schemaDependencies.js ├── index.html └── index.prod.html ├── README.md ├── .travis.yml ├── .babelrc ├── mkdocs.yml ├── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── types ├── README.md ├── LICENSE └── index.d.ts ├── ISSUE_TEMPLATE.md ├── devServer.js ├── .eslintrc ├── webpack.config.dev.js ├── docs ├── definitions.md ├── theme-customization.md ├── dependencies.md └── validation.md ├── webpack.config.dist.js ├── webpack.config.prod.js ├── .gitignore └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 5000 2 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [context.deploy-preview.environment] 2 | SHOW_NETLIFY_BADGE = "true" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | 3 | indent_style = space 4 | indent_size = 2 5 | charset = utf-8 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Form from "./components/Form"; 2 | import withTheme from "./withTheme"; 3 | 4 | export { withTheme }; 5 | export default Form; 6 | -------------------------------------------------------------------------------- /playground/samples/single.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A single-field form", 4 | type: "string", 5 | }, 6 | formData: "initial value", 7 | uiSchema: {}, 8 | }; 9 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true, 4 | }, 5 | "globals": { 6 | d: true 7 | }, 8 | "rules": { 9 | "no-unused-vars": [ 10 | 2, 11 | { 12 | "varsIgnorePattern": "^d$" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATION NOTICE 2 | ================== 3 | 4 | This project is no longer maintained. Current versions of [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) now include a Bootstrap 4 theme. It is recommended that you switch to that instead. 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: 3 | - node_js 4 | node_js: 5 | - "6" 6 | - "7" 7 | - "8" 8 | - "9" 9 | - "10" 10 | - "11" 11 | - "12" 12 | env: 13 | - ACTION=test 14 | - ACTION="run lint" 15 | - ACTION="run cs-check" 16 | - ACTION="run dist" 17 | script: 18 | - npm $ACTION 19 | -------------------------------------------------------------------------------- /src/components/IconButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function IconButton(props) { 4 | const { type = "default", icon, className, ...otherProps } = props; 5 | return ( 6 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgets/URLWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function URLWidget(props) { 5 | const { BaseInput } = props.registry.widgets; 6 | return ; 7 | } 8 | 9 | if (process.env.NODE_ENV !== "production") { 10 | URLWidget.propTypes = { 11 | value: PropTypes.string, 12 | }; 13 | } 14 | 15 | export default URLWidget; 16 | -------------------------------------------------------------------------------- /src/components/widgets/EmailWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function EmailWidget(props) { 5 | const { BaseInput } = props.registry.widgets; 6 | return ; 7 | } 8 | 9 | if (process.env.NODE_ENV !== "production") { 10 | EmailWidget.propTypes = { 11 | value: PropTypes.string, 12 | }; 13 | } 14 | 15 | export default EmailWidget; 16 | -------------------------------------------------------------------------------- /src/components/widgets/PasswordWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function PasswordWidget(props) { 5 | const { BaseInput } = props.registry.widgets; 6 | return ; 7 | } 8 | 9 | if (process.env.NODE_ENV !== "production") { 10 | PasswordWidget.propTypes = { 11 | value: PropTypes.string, 12 | }; 13 | } 14 | 15 | export default PasswordWidget; 16 | -------------------------------------------------------------------------------- /playground/samples/custom.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A localisation form", 4 | type: "object", 5 | required: ["lat", "lon"], 6 | properties: { 7 | lat: { 8 | type: "number", 9 | }, 10 | lon: { 11 | type: "number", 12 | }, 13 | }, 14 | }, 15 | uiSchema: { 16 | "ui:field": "geo", 17 | }, 18 | formData: { 19 | lat: 0, 20 | lon: 0, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/widgets/TextWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function TextWidget(props) { 5 | const { BaseInput } = props.registry.widgets; 6 | return ; 7 | } 8 | 9 | if (process.env.NODE_ENV !== "production") { 10 | TextWidget.propTypes = { 11 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 12 | id: PropTypes.string, 13 | }; 14 | } 15 | 16 | export default TextWidget; 17 | -------------------------------------------------------------------------------- /src/components/fields/NullField.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import * as types from "../../types"; 3 | 4 | class NullField extends Component { 5 | componentDidMount() { 6 | if (this.props.formData === undefined) { 7 | this.props.onChange(null); 8 | } 9 | } 10 | 11 | render() { 12 | return null; 13 | } 14 | } 15 | 16 | if (process.env.NODE_ENV !== "production") { 17 | NullField.propTypes = types.fieldProps; 18 | } 19 | 20 | export default NullField; 21 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | "@babel/preset-env" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-object-rest-spread", 8 | "@babel/plugin-proposal-class-properties", 9 | [ 10 | "@babel/plugin-transform-runtime", 11 | { 12 | "corejs": 2 13 | } 14 | ] 15 | ], 16 | "env": { 17 | "development": { 18 | "plugins": [ 19 | "@babel/plugin-transform-react-jsx" 20 | ] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /playground/samples/oneOf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | type: "object", 4 | oneOf: [ 5 | { 6 | properties: { 7 | lorem: { 8 | type: "string", 9 | }, 10 | }, 11 | required: ["lorem"], 12 | }, 13 | { 14 | properties: { 15 | ipsum: { 16 | type: "string", 17 | }, 18 | }, 19 | required: ["ipsum"], 20 | }, 21 | ], 22 | }, 23 | formData: {}, 24 | }; 25 | -------------------------------------------------------------------------------- /playground/samples/files.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "Files", 4 | type: "object", 5 | properties: { 6 | file: { 7 | type: "string", 8 | format: "data-url", 9 | title: "Single file", 10 | }, 11 | files: { 12 | type: "array", 13 | title: "Multiple files", 14 | items: { 15 | type: "string", 16 | format: "data-url", 17 | }, 18 | }, 19 | }, 20 | }, 21 | uiSchema: {}, 22 | formData: {}, 23 | }; 24 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: react-jsonschema-form documentation 2 | docs_dir: docs 3 | theme: readthedocs 4 | 5 | nav: 6 | - Introduction: index.md 7 | - Advanced Customization: advanced-customization.md 8 | - Definitions: definitions.md 9 | - Dependencies: dependencies.md 10 | - Form Customization: form-customization.md 11 | - Theme Customization: theme-customization.md 12 | - Validation: validation.md 13 | - Playground: https://mozilla-services.github.io/react-jsonschema-form/ 14 | 15 | markdown_extensions: 16 | - toc: 17 | permalink: true 18 | -------------------------------------------------------------------------------- /src/components/AddButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import IconButton from "./IconButton"; 3 | 4 | export default function AddButton({ className, onClick, disabled }) { 5 | return ( 6 |
7 |

8 | 16 |

17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ErrorList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function ErrorList(props) { 4 | const { errors } = props; 5 | return ( 6 |
7 |
Errors
8 |
    9 | {errors.map((error, i) => { 10 | return ( 11 |
  • 12 | {error.stack} 13 |
  • 14 | ); 15 | })} 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/widgets/DateWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function DateWidget(props) { 5 | const { 6 | onChange, 7 | registry: { 8 | widgets: { BaseInput }, 9 | }, 10 | } = props; 11 | return ( 12 | onChange(value || undefined)} 16 | /> 17 | ); 18 | } 19 | 20 | if (process.env.NODE_ENV !== "production") { 21 | DateWidget.propTypes = { 22 | value: PropTypes.string, 23 | }; 24 | } 25 | 26 | export default DateWidget; 27 | -------------------------------------------------------------------------------- /src/components/widgets/UpDownWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import { rangeSpec } from "../../utils"; 5 | 6 | function UpDownWidget(props) { 7 | const { 8 | registry: { 9 | widgets: { BaseInput }, 10 | }, 11 | } = props; 12 | return ; 13 | } 14 | 15 | if (process.env.NODE_ENV !== "production") { 16 | UpDownWidget.propTypes = { 17 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 18 | }; 19 | } 20 | 21 | export default UpDownWidget; 22 | -------------------------------------------------------------------------------- /test/setup-jsdom.js: -------------------------------------------------------------------------------- 1 | var jsdom = require("jsdom"); 2 | 3 | // Setup the jsdom environment 4 | // @see https://github.com/facebook/react/issues/5046 5 | if (!global.hasOwnProperty("window")) { 6 | global.document = jsdom.jsdom(""); 7 | global.window = document.defaultView; 8 | global.navigator = global.window.navigator; 9 | global.File = global.window.File; 10 | } 11 | 12 | // atob 13 | global.atob = require("atob"); 14 | 15 | // HTML debugging helper 16 | global.d = function d(node) { 17 | console.log(require("html").prettyPrint(node.outerHTML, { indent_size: 2 })); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/widgets/HiddenWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function HiddenWidget({ id, value }) { 5 | return ( 6 | 11 | ); 12 | } 13 | 14 | if (process.env.NODE_ENV !== "production") { 15 | HiddenWidget.propTypes = { 16 | id: PropTypes.string.isRequired, 17 | value: PropTypes.oneOfType([ 18 | PropTypes.string, 19 | PropTypes.number, 20 | PropTypes.bool, 21 | ]), 22 | }; 23 | } 24 | 25 | export default HiddenWidget; 26 | -------------------------------------------------------------------------------- /src/components/fields/TitleField.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const REQUIRED_FIELD_SYMBOL = "*"; 5 | 6 | function TitleField(props) { 7 | const { id, title, required } = props; 8 | return ( 9 | 10 | {title} 11 | {required && {REQUIRED_FIELD_SYMBOL}} 12 | 13 | ); 14 | } 15 | 16 | if (process.env.NODE_ENV !== "production") { 17 | TitleField.propTypes = { 18 | id: PropTypes.string, 19 | title: PropTypes.string, 20 | required: PropTypes.bool, 21 | }; 22 | } 23 | 24 | export default TitleField; 25 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Reasons for making this change 2 | 3 | [Please describe them here] 4 | 5 | If this is related to existing tickets, include links to them as well. 6 | 7 | ### Checklist 8 | 9 | * [ ] **I'm updating documentation** 10 | - [ ] I've [checked the rendering](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) of the Markdown text I've added 11 | * [ ] **I'm adding or updating code** 12 | - [ ] I've added and/or updated tests 13 | - [ ] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) if needed 14 | * [ ] **I'm adding a new feature** 15 | - [ ] I've updated the playground with an example use of the feature 16 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | react-jsonschema-form playground 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /playground/index.prod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | react-jsonschema-form playground 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /playground/samples/null.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "Null field example", 4 | description: "A short form with a null field", 5 | type: "object", 6 | required: ["firstName"], 7 | properties: { 8 | helpText: { 9 | title: "A null field", 10 | description: 11 | "Null fields like this are great for adding extra information", 12 | type: "null", 13 | }, 14 | firstName: { 15 | type: "string", 16 | title: "A regular string field", 17 | default: "Chuck", 18 | }, 19 | }, 20 | }, 21 | uiSchema: { 22 | firstName: { 23 | "ui:autofocus": true, 24 | "ui:emptyValue": "", 25 | }, 26 | }, 27 | formData: {}, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/widgets/RangeWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import { rangeSpec } from "../../utils"; 5 | 6 | function RangeWidget(props) { 7 | const { 8 | schema, 9 | value, 10 | registry: { 11 | widgets: { BaseInput }, 12 | }, 13 | } = props; 14 | return ( 15 |
16 | 17 | {value} 18 |
19 | ); 20 | } 21 | 22 | if (process.env.NODE_ENV !== "production") { 23 | RangeWidget.propTypes = { 24 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 25 | }; 26 | } 27 | 28 | export default RangeWidget; 29 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | 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. 9 | 10 | 16 | -------------------------------------------------------------------------------- /src/withTheme.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import Form from "./"; 4 | 5 | function withTheme(themeProps) { 6 | return class extends Component { 7 | render() { 8 | let { fields, widgets, ...directProps } = this.props; 9 | fields = { ...themeProps.fields, ...fields }; 10 | widgets = { ...themeProps.widgets, ...widgets }; 11 | return ( 12 |
18 | ); 19 | } 20 | }; 21 | } 22 | 23 | withTheme.propTypes = { 24 | widgets: PropTypes.object, 25 | fields: PropTypes.object, 26 | }; 27 | 28 | export default withTheme; 29 | -------------------------------------------------------------------------------- /src/components/widgets/AltDateTimeWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import AltDateWidget from "./AltDateWidget"; 4 | 5 | function AltDateTimeWidget(props) { 6 | const { AltDateWidget } = props.registry.widgets; 7 | return ; 8 | } 9 | 10 | if (process.env.NODE_ENV !== "production") { 11 | AltDateTimeWidget.propTypes = { 12 | schema: PropTypes.object.isRequired, 13 | id: PropTypes.string.isRequired, 14 | value: PropTypes.string, 15 | required: PropTypes.bool, 16 | onChange: PropTypes.func, 17 | options: PropTypes.object, 18 | }; 19 | } 20 | 21 | AltDateTimeWidget.defaultProps = { 22 | ...AltDateWidget.defaultProps, 23 | time: true, 24 | }; 25 | 26 | export default AltDateTimeWidget; 27 | -------------------------------------------------------------------------------- /src/components/widgets/ColorWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function ColorWidget(props) { 5 | const { 6 | disabled, 7 | readonly, 8 | registry: { 9 | widgets: { BaseInput }, 10 | }, 11 | } = props; 12 | return ; 13 | } 14 | 15 | if (process.env.NODE_ENV !== "production") { 16 | ColorWidget.propTypes = { 17 | schema: PropTypes.object.isRequired, 18 | id: PropTypes.string.isRequired, 19 | value: PropTypes.string, 20 | required: PropTypes.bool, 21 | disabled: PropTypes.bool, 22 | readonly: PropTypes.bool, 23 | autofocus: PropTypes.bool, 24 | onChange: PropTypes.func, 25 | }; 26 | } 27 | 28 | export default ColorWidget; 29 | -------------------------------------------------------------------------------- /playground/samples/additionalProperties.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A customizable registration form", 4 | description: "A simple form with additional properties example.", 5 | type: "object", 6 | required: ["firstName", "lastName"], 7 | additionalProperties: { 8 | type: "string", 9 | }, 10 | properties: { 11 | firstName: { 12 | type: "string", 13 | title: "First name", 14 | }, 15 | lastName: { 16 | type: "string", 17 | title: "Last name", 18 | }, 19 | }, 20 | }, 21 | uiSchema: { 22 | firstName: { 23 | "ui:autofocus": true, 24 | "ui:emptyValue": "", 25 | }, 26 | }, 27 | formData: { 28 | firstName: "Chuck", 29 | lastName: "Norris", 30 | assKickCount: "infinity", 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/fields/DescriptionField.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function DescriptionField(props) { 5 | const { id, description } = props; 6 | if (!description) { 7 | return null; 8 | } 9 | if (typeof description === "string") { 10 | return ( 11 |

12 | {description} 13 |

14 | ); 15 | } else { 16 | return ( 17 |
18 | {description} 19 |
20 | ); 21 | } 22 | } 23 | 24 | if (process.env.NODE_ENV !== "production") { 25 | DescriptionField.propTypes = { 26 | id: PropTypes.string, 27 | description: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), 28 | }; 29 | } 30 | 31 | export default DescriptionField; 32 | -------------------------------------------------------------------------------- /src/components/fields/index.js: -------------------------------------------------------------------------------- 1 | import ArrayField from "./ArrayField"; 2 | import BooleanField from "./BooleanField"; 3 | import DescriptionField from "./DescriptionField"; 4 | import MultiSchemaField from "./MultiSchemaField"; 5 | import NumberField from "./NumberField"; 6 | import ObjectField from "./ObjectField"; 7 | import SchemaField from "./SchemaField"; 8 | import StringField from "./StringField"; 9 | import TitleField from "./TitleField"; 10 | import NullField from "./NullField"; 11 | import UnsupportedField from "./UnsupportedField"; 12 | 13 | export default { 14 | AnyOfField: MultiSchemaField, 15 | ArrayField, 16 | BooleanField, 17 | DescriptionField, 18 | NumberField, 19 | ObjectField, 20 | OneOfField: MultiSchemaField, 21 | SchemaField, 22 | StringField, 23 | TitleField, 24 | NullField, 25 | UnsupportedField, 26 | }; 27 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | > `npm install --save @types/react-jsonschema-form` 3 | 4 | # Summary 5 | This package contains type definitions for react-jsonschema-form (https://github.com/mozilla-services/react-jsonschema-form). 6 | 7 | # Details 8 | Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-jsonschema-form 9 | 10 | Additional Details 11 | * Last updated: Thu, 29 Nov 2018 23:46:25 GMT 12 | * Dependencies: react, json-schema 13 | * Global values: none 14 | 15 | # Credits 16 | These definitions were written by Dan Fox , Ivan Jiang , Philippe Bourdages , Lucian Buzzo , Sylvain Thénault , Sebastian Busch . 17 | -------------------------------------------------------------------------------- /src/components/fields/UnsupportedField.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function UnsupportedField({ schema, idSchema, reason }) { 5 | return ( 6 |
7 |

8 | Unsupported field schema 9 | {idSchema && idSchema.$id && ( 10 | 11 | {" for"} field {idSchema.$id} 12 | 13 | )} 14 | {reason && : {reason}}. 15 |

16 | {schema &&
{JSON.stringify(schema, null, 2)}
} 17 |
18 | ); 19 | } 20 | 21 | if (process.env.NODE_ENV !== "production") { 22 | UnsupportedField.propTypes = { 23 | schema: PropTypes.object.isRequired, 24 | idSchema: PropTypes.object, 25 | reason: PropTypes.string, 26 | }; 27 | } 28 | 29 | export default UnsupportedField; 30 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | - [ ] I have read the [documentation](https://react-jsonschema-form.readthedocs.io/); 4 | - [ ] In the case of a bug report, I understand that providing a [SSCCE](http://sscce.org/) example is tremendously useful to the maintainers. 5 | - [ ] 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. 6 | 7 | ### Description 8 | 9 | [Description of the bug or feature] 10 | 11 | ### Steps to Reproduce 12 | 13 | 1. [First Step] 14 | 2. [Second Step] 15 | 3. [and so on...] 16 | 17 | #### Expected behavior 18 | 19 | [What you expected to happen] 20 | 21 | #### Actual behavior 22 | 23 | [What actually happened] 24 | 25 | ### Version 26 | 27 | You can usually get this information in your `package.json` or in the file URL if you're using the unpkg one. 28 | -------------------------------------------------------------------------------- /test/test_utils.js: -------------------------------------------------------------------------------- 1 | /* Utils for tests. */ 2 | 3 | import React from "react"; 4 | import sinon from "sinon"; 5 | import { renderIntoDocument } from "react-dom/test-utils"; 6 | import { findDOMNode, render } from "react-dom"; 7 | 8 | import Form from "../src"; 9 | 10 | export function createComponent(Component, props) { 11 | const comp = renderIntoDocument(); 12 | const node = findDOMNode(comp); 13 | return { comp, node }; 14 | } 15 | 16 | export function createFormComponent(props) { 17 | return createComponent(Form, { ...props, safeRenderCompletion: true }); 18 | } 19 | 20 | export function createSandbox() { 21 | const sandbox = sinon.sandbox.create(); 22 | // Ensure we catch any React warning and mark them as test failures. 23 | sandbox.stub(console, "error", error => { 24 | throw new Error(error); 25 | }); 26 | return sandbox; 27 | } 28 | 29 | export function setProps(comp, newProps) { 30 | const node = findDOMNode(comp); 31 | render(React.createElement(comp.constructor, newProps), node.parentNode); 32 | } 33 | -------------------------------------------------------------------------------- /playground/samples/errors.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "Contextualized errors", 4 | type: "object", 5 | properties: { 6 | firstName: { 7 | type: "string", 8 | title: "First name", 9 | minLength: 8, 10 | pattern: "\\d+", 11 | }, 12 | active: { 13 | type: "boolean", 14 | title: "Active", 15 | }, 16 | skills: { 17 | type: "array", 18 | items: { 19 | type: "string", 20 | minLength: 5, 21 | }, 22 | }, 23 | multipleChoicesList: { 24 | type: "array", 25 | title: "Pick max two items", 26 | uniqueItems: true, 27 | maxItems: 2, 28 | items: { 29 | type: "string", 30 | enum: ["foo", "bar", "fuzz"], 31 | }, 32 | }, 33 | }, 34 | }, 35 | uiSchema: {}, 36 | formData: { 37 | firstName: "Chuck", 38 | active: "wrong", 39 | skills: ["karate", "budo", "aikido"], 40 | multipleChoicesList: ["foo", "bar", "fuzz"], 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const express = require("express"); 3 | const webpack = require("webpack"); 4 | 5 | const server = process.env.RJSF_DEV_SERVER || "localhost:8080"; 6 | const splitServer = server.split(":"); 7 | const host = splitServer[0]; 8 | const port = splitServer[1]; 9 | const env = "dev"; 10 | 11 | const webpackConfig = require("./webpack.config." + env); 12 | const compiler = webpack(webpackConfig); 13 | const app = express(); 14 | 15 | app.use(require("webpack-dev-middleware")(compiler, { 16 | publicPath: webpackConfig.output.publicPath, 17 | noInfo: true 18 | })); 19 | 20 | app.use(require("webpack-hot-middleware")(compiler)); 21 | 22 | app.get("/favicon.ico", function(req, res) { 23 | res.status(204).end(); 24 | }); 25 | 26 | app.get("/", function(req, res) { 27 | res.sendFile(path.join(__dirname, "playground", "index.html")); 28 | }); 29 | 30 | app.listen(port, host, function(err) { 31 | if (err) { 32 | console.log(err); 33 | return; 34 | } 35 | 36 | console.log(`Listening at http://${server}`); 37 | }); 38 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "react/jsx-uses-react": 2, 5 | "react/jsx-uses-vars": 2, 6 | "react/react-in-jsx-scope": 2, 7 | "react/jsx-tag-spacing": [1, { 8 | "beforeSelfClosing": "always" 9 | }], 10 | "curly": [2], 11 | "linebreak-style": [2, "unix"], 12 | "semi": [2, "always"], 13 | "comma-dangle": [0], 14 | "no-unused-vars": [2, { 15 | "vars": "all", 16 | "args": "none", 17 | "ignoreRestSiblings": true 18 | }], 19 | "no-console": [0], 20 | "object-curly-spacing": [2, "always"], 21 | "keyword-spacing": ["error"] 22 | }, 23 | "env": { 24 | "es6": true, 25 | "browser": true, 26 | "node": true 27 | }, 28 | "extends": "eslint:recommended", 29 | "ecmaFeatures": { 30 | "modules": true, 31 | "jsx": true, 32 | "experimentalObjectRestSpread": true 33 | }, 34 | "plugins": [ 35 | "jsx-a11y", 36 | "react" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /playground/samples/ordering.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A registration form", 4 | type: "object", 5 | required: ["firstName", "lastName"], 6 | properties: { 7 | password: { 8 | type: "string", 9 | title: "Password", 10 | }, 11 | lastName: { 12 | type: "string", 13 | title: "Last name", 14 | }, 15 | bio: { 16 | type: "string", 17 | title: "Bio", 18 | }, 19 | firstName: { 20 | type: "string", 21 | title: "First name", 22 | }, 23 | age: { 24 | type: "integer", 25 | title: "Age", 26 | }, 27 | }, 28 | }, 29 | uiSchema: { 30 | "ui:order": ["firstName", "lastName", "*", "password"], 31 | age: { 32 | "ui:widget": "updown", 33 | }, 34 | bio: { 35 | "ui:widget": "textarea", 36 | }, 37 | password: { 38 | "ui:widget": "password", 39 | }, 40 | }, 41 | formData: { 42 | firstName: "Chuck", 43 | lastName: "Norris", 44 | age: 75, 45 | bio: "Roundhouse kicking asses since 1940", 46 | password: "noneed", 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /playground/samples/large.js: -------------------------------------------------------------------------------- 1 | function largeEnum(n) { 2 | const list = []; 3 | for (let i = 0; i < n; i++) { 4 | list.push("option #" + i); 5 | } 6 | return list; 7 | } 8 | 9 | module.exports = { 10 | schema: { 11 | definitions: { 12 | largeEnum: { type: "string", enum: largeEnum(100) }, 13 | }, 14 | title: "A rather large form", 15 | type: "object", 16 | properties: { 17 | string: { 18 | type: "string", 19 | title: "Some string", 20 | }, 21 | choice1: { $ref: "#/definitions/largeEnum" }, 22 | choice2: { $ref: "#/definitions/largeEnum" }, 23 | choice3: { $ref: "#/definitions/largeEnum" }, 24 | choice4: { $ref: "#/definitions/largeEnum" }, 25 | choice5: { $ref: "#/definitions/largeEnum" }, 26 | choice6: { $ref: "#/definitions/largeEnum" }, 27 | choice7: { $ref: "#/definitions/largeEnum" }, 28 | choice8: { $ref: "#/definitions/largeEnum" }, 29 | choice9: { $ref: "#/definitions/largeEnum" }, 30 | choice10: { $ref: "#/definitions/largeEnum" }, 31 | }, 32 | }, 33 | uiSchema: { 34 | choice1: { 35 | "ui:placeholder": "Choose one", 36 | }, 37 | }, 38 | formData: {}, 39 | }; 40 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | module.exports = { 5 | mode: "development", 6 | devtool: "source-map", 7 | entry: [ 8 | "webpack-hot-middleware/client?reload=true", 9 | "./playground/app" 10 | ], 11 | output: { 12 | path: path.join(__dirname, "build"), 13 | filename: "bundle.js", 14 | publicPath: "/static/" 15 | }, 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin(), 18 | ], 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | use: [ 24 | "babel-loader", 25 | ], 26 | include: [ 27 | path.join(__dirname, "src"), 28 | path.join(__dirname, "playground"), 29 | path.join(__dirname, "node_modules", "codemirror", "mode", "javascript"), 30 | ] 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: [ 35 | "style-loader", 36 | "css-loader", 37 | ], 38 | include: [ 39 | path.join(__dirname, "css"), 40 | path.join(__dirname, "playground"), 41 | path.join(__dirname, "node_modules"), 42 | ], 43 | }, 44 | ] 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | 3 | export const registry = PropTypes.shape({ 4 | ArrayFieldTemplate: PropTypes.func, 5 | FieldTemplate: PropTypes.func, 6 | ObjectFieldTemplate: PropTypes.func, 7 | definitions: PropTypes.object.isRequired, 8 | fields: PropTypes.objectOf(PropTypes.func).isRequired, 9 | formContext: PropTypes.object.isRequired, 10 | widgets: PropTypes.objectOf( 11 | PropTypes.oneOfType([PropTypes.func, PropTypes.object]) 12 | ).isRequired, 13 | }); 14 | 15 | export const fieldProps = { 16 | autofocus: PropTypes.bool, 17 | disabled: PropTypes.bool, 18 | errorSchema: PropTypes.object, 19 | formData: PropTypes.any, 20 | idSchema: PropTypes.object, 21 | onBlur: PropTypes.func, 22 | onChange: PropTypes.func.isRequired, 23 | onFocus: PropTypes.func, 24 | rawErrors: PropTypes.arrayOf(PropTypes.string), 25 | readonly: PropTypes.bool, 26 | registry: registry.isRequired, 27 | required: PropTypes.bool, 28 | schema: PropTypes.object.isRequired, 29 | uiSchema: PropTypes.shape({ 30 | "ui:options": PropTypes.shape({ 31 | addable: PropTypes.bool, 32 | orderable: PropTypes.bool, 33 | removable: PropTypes.bool, 34 | }), 35 | }), 36 | }; 37 | -------------------------------------------------------------------------------- /docs/definitions.md: -------------------------------------------------------------------------------- 1 | ## Schema definitions and references 2 | 3 | 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*: 4 | 5 | ```json 6 | { 7 | "definitions": { 8 | "address": { 9 | "type": "object", 10 | "properties": { 11 | "street_address": { "type": "string" }, 12 | "city": { "type": "string" }, 13 | "state": { "type": "string" } 14 | }, 15 | "required": ["street_address", "city", "state"] 16 | } 17 | }, 18 | "type": "object", 19 | "properties": { 20 | "billing_address": { "$ref": "#/definitions/address" }, 21 | "shipping_address": { "$ref": "#/definitions/address" } 22 | } 23 | } 24 | ``` 25 | 26 | *(Sample schema courtesy of the [Space Telescope Science Institute](http://spacetelescope.github.io/understanding-json-schema/structuring.html))* 27 | 28 | 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. 29 | 30 | -------------------------------------------------------------------------------- /types/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /webpack.config.dist.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | module.exports = { 5 | mode: "production", 6 | cache: true, 7 | context: __dirname + "/src", 8 | entry: "./index.js", 9 | output: { 10 | path: path.join(__dirname, "dist"), 11 | publicPath: "/dist/", 12 | filename: "react-jsonschema-form.js", 13 | library: "JSONSchemaForm", 14 | libraryTarget: "umd" 15 | }, 16 | plugins: [ 17 | new webpack.DefinePlugin({ 18 | "process.env": { 19 | NODE_ENV: JSON.stringify("production") 20 | } 21 | }) 22 | ], 23 | devtool: "source-map", 24 | externals: { 25 | react: { 26 | root: "React", 27 | commonjs: "react", 28 | commonjs2: "react", 29 | amd: "react" 30 | }, 31 | 'react-dom': { 32 | root: "ReactDOM", 33 | commonjs2: 'react-dom', 34 | commonjs: 'react-dom', 35 | amd: 'react-dom', 36 | umd: 'react-dom', 37 | } 38 | }, 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.js$/, 43 | use: [ 44 | "babel-loader", 45 | ], 46 | exclude: [ 47 | path.join(__dirname, "node_modules", "core-js"), 48 | path.join(__dirname, "node_modules", "babel-runtime"), 49 | ], 50 | }, 51 | ] 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /playground/samples/validation.js: -------------------------------------------------------------------------------- 1 | function validate({ pass1, pass2 }, errors) { 2 | if (pass1 !== pass2) { 3 | errors.pass2.addError("Passwords don't match."); 4 | } 5 | return errors; 6 | } 7 | 8 | function transformErrors(errors) { 9 | return errors.map(error => { 10 | if (error.name === "minimum" && error.property === "instance.age") { 11 | return Object.assign({}, error, { 12 | message: "You need to be 18 because of some legal thing", 13 | }); 14 | } 15 | return error; 16 | }); 17 | } 18 | 19 | export default { 20 | schema: { 21 | title: "Custom validation", 22 | description: 23 | "This form defines custom validation rules checking that the two passwords match.", 24 | type: "object", 25 | properties: { 26 | pass1: { 27 | title: "Password", 28 | type: "string", 29 | minLength: 3, 30 | }, 31 | pass2: { 32 | title: "Repeat password", 33 | type: "string", 34 | minLength: 3, 35 | }, 36 | age: { 37 | title: "Age", 38 | type: "number", 39 | minimum: 18, 40 | }, 41 | }, 42 | }, 43 | uiSchema: { 44 | pass1: { "ui:widget": "password" }, 45 | pass2: { "ui:widget": "password" }, 46 | }, 47 | formData: {}, 48 | validate, 49 | transformErrors, 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/widgets/index.js: -------------------------------------------------------------------------------- 1 | import AltDateWidget from "./AltDateWidget"; 2 | import AltDateTimeWidget from "./AltDateTimeWidget"; 3 | import BaseInput from "./BaseInput"; 4 | import CheckboxWidget from "./CheckboxWidget"; 5 | import CheckboxesWidget from "./CheckboxesWidget"; 6 | import ColorWidget from "./ColorWidget"; 7 | import DateWidget from "./DateWidget"; 8 | import DateTimeWidget from "./DateTimeWidget"; 9 | import EmailWidget from "./EmailWidget"; 10 | import FileWidget from "./FileWidget"; 11 | import HiddenWidget from "./HiddenWidget"; 12 | import PasswordWidget from "./PasswordWidget"; 13 | import RadioWidget from "./RadioWidget"; 14 | import RangeWidget from "./RangeWidget"; 15 | import SelectWidget from "./SelectWidget"; 16 | import TextareaWidget from "./TextareaWidget"; 17 | import TextWidget from "./TextWidget"; 18 | import URLWidget from "./URLWidget"; 19 | import UpDownWidget from "./UpDownWidget"; 20 | 21 | export default { 22 | BaseInput, 23 | PasswordWidget, 24 | RadioWidget, 25 | UpDownWidget, 26 | RangeWidget, 27 | SelectWidget, 28 | TextWidget, 29 | DateWidget, 30 | DateTimeWidget, 31 | AltDateWidget, 32 | AltDateTimeWidget, 33 | EmailWidget, 34 | URLWidget, 35 | TextareaWidget, 36 | HiddenWidget, 37 | ColorWidget, 38 | FileWidget, 39 | CheckboxWidget, 40 | CheckboxesWidget, 41 | }; 42 | -------------------------------------------------------------------------------- /playground/samples/anyOf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | type: "object", 4 | properties: { 5 | age: { 6 | type: "integer", 7 | title: "Age", 8 | }, 9 | items: { 10 | type: "array", 11 | items: { 12 | type: "object", 13 | anyOf: [ 14 | { 15 | properties: { 16 | foo: { 17 | type: "string", 18 | }, 19 | }, 20 | }, 21 | { 22 | properties: { 23 | bar: { 24 | type: "string", 25 | }, 26 | }, 27 | }, 28 | ], 29 | }, 30 | }, 31 | }, 32 | anyOf: [ 33 | { 34 | title: "First method of identification", 35 | properties: { 36 | firstName: { 37 | type: "string", 38 | title: "First name", 39 | default: "Chuck", 40 | }, 41 | lastName: { 42 | type: "string", 43 | title: "Last name", 44 | }, 45 | }, 46 | }, 47 | { 48 | title: "Second method of identification", 49 | properties: { 50 | idCode: { 51 | type: "string", 52 | title: "ID code", 53 | }, 54 | }, 55 | }, 56 | ], 57 | }, 58 | formData: {}, 59 | }; 60 | -------------------------------------------------------------------------------- /playground/samples/date.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "Date and time widgets", 4 | type: "object", 5 | properties: { 6 | native: { 7 | title: "Native", 8 | description: 9 | "May not work on some browsers, notably Firefox Desktop and IE.", 10 | type: "object", 11 | properties: { 12 | datetime: { 13 | type: "string", 14 | format: "date-time", 15 | }, 16 | date: { 17 | type: "string", 18 | format: "date", 19 | }, 20 | }, 21 | }, 22 | alternative: { 23 | title: "Alternative", 24 | description: "These work on most platforms.", 25 | type: "object", 26 | properties: { 27 | "alt-datetime": { 28 | type: "string", 29 | format: "date-time", 30 | }, 31 | "alt-date": { 32 | type: "string", 33 | format: "date", 34 | }, 35 | }, 36 | }, 37 | }, 38 | }, 39 | uiSchema: { 40 | alternative: { 41 | "alt-datetime": { 42 | "ui:widget": "alt-datetime", 43 | "ui:options": { 44 | yearsRange: [1980, 2030], 45 | }, 46 | }, 47 | "alt-date": { 48 | "ui:widget": "alt-date", 49 | "ui:options": { 50 | yearsRange: [1980, 2030], 51 | }, 52 | }, 53 | }, 54 | }, 55 | formData: {}, 56 | }; 57 | -------------------------------------------------------------------------------- /playground/samples/numbers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | type: "object", 4 | title: "Number fields & widgets", 5 | properties: { 6 | number: { 7 | title: "Number", 8 | type: "number", 9 | }, 10 | integer: { 11 | title: "Integer", 12 | type: "integer", 13 | }, 14 | numberEnum: { 15 | type: "number", 16 | title: "Number enum", 17 | enum: [1, 2, 3], 18 | }, 19 | numberEnumRadio: { 20 | type: "number", 21 | title: "Number enum", 22 | enum: [1, 2, 3], 23 | }, 24 | integerRange: { 25 | title: "Integer range", 26 | type: "integer", 27 | minimum: 42, 28 | maximum: 100, 29 | }, 30 | integerRangeSteps: { 31 | title: "Integer range (by 10)", 32 | type: "integer", 33 | minimum: 50, 34 | maximum: 100, 35 | multipleOf: 10, 36 | }, 37 | }, 38 | }, 39 | uiSchema: { 40 | integer: { 41 | "ui:widget": "updown", 42 | }, 43 | numberEnumRadio: { 44 | "ui:widget": "radio", 45 | "ui:options": { 46 | inline: true, 47 | }, 48 | }, 49 | integerRange: { 50 | "ui:widget": "range", 51 | }, 52 | integerRangeSteps: { 53 | "ui:widget": "range", 54 | }, 55 | }, 56 | formData: { 57 | number: 3.14, 58 | integer: 42, 59 | numberEnum: 2, 60 | integerRange: 42, 61 | integerRangeSteps: 80, 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /test/const_test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { createFormComponent, createSandbox } from "./test_utils"; 4 | 5 | describe("const", () => { 6 | let sandbox; 7 | 8 | beforeEach(() => { 9 | sandbox = createSandbox(); 10 | }); 11 | 12 | afterEach(() => { 13 | sandbox.restore(); 14 | }); 15 | 16 | it("should render a schema that uses const with a string value", () => { 17 | const schema = { 18 | type: "object", 19 | properties: { 20 | foo: { const: "bar" }, 21 | }, 22 | }; 23 | 24 | const { node } = createFormComponent({ 25 | schema, 26 | }); 27 | 28 | expect(node.querySelector("input#root_foo")).not.eql(null); 29 | }); 30 | 31 | it("should render a schema that uses const with a number value", () => { 32 | const schema = { 33 | type: "object", 34 | properties: { 35 | foo: { const: 123 }, 36 | }, 37 | }; 38 | 39 | const { node } = createFormComponent({ 40 | schema, 41 | }); 42 | 43 | expect(node.querySelector("input#root_foo")).not.eql(null); 44 | }); 45 | 46 | it("should render a schema that uses const with a boolean value", () => { 47 | const schema = { 48 | type: "object", 49 | properties: { 50 | foo: { const: true }, 51 | }, 52 | }; 53 | 54 | const { node } = createFormComponent({ 55 | schema, 56 | }); 57 | 58 | expect(node.querySelector("input#root_foo[type='checkbox']")).not.eql(null); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/NullField_test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { createFormComponent, createSandbox } from "./test_utils"; 4 | 5 | describe("NullField", () => { 6 | let sandbox; 7 | 8 | beforeEach(() => { 9 | sandbox = createSandbox(); 10 | }); 11 | 12 | afterEach(() => { 13 | sandbox.restore(); 14 | }); 15 | 16 | describe("No widget", () => { 17 | it("should render a null field", () => { 18 | const { node } = createFormComponent({ 19 | schema: { 20 | type: "null", 21 | }, 22 | }); 23 | 24 | expect(node.querySelectorAll(".field")).to.have.length.of(1); 25 | }); 26 | 27 | it("should render a null field with a label", () => { 28 | const { node } = createFormComponent({ 29 | schema: { 30 | type: "null", 31 | title: "foo", 32 | }, 33 | }); 34 | 35 | expect(node.querySelector(".field label").textContent).eql("foo"); 36 | }); 37 | 38 | it("should assign a default value", () => { 39 | const { comp } = createFormComponent({ 40 | schema: { 41 | type: "null", 42 | default: null, 43 | }, 44 | }); 45 | 46 | expect(comp.state.formData).eql(null); 47 | }); 48 | 49 | it("should not overwrite existing data", () => { 50 | const { comp } = createFormComponent({ 51 | schema: { 52 | type: "null", 53 | }, 54 | formData: 3, 55 | }); 56 | 57 | expect(comp.state.formData).eql(3); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | var MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | 5 | module.exports = { 6 | mode: "production", 7 | entry: "./playground/app", 8 | output: { 9 | path: path.join(__dirname, "build"), 10 | filename: "bundle.js", 11 | publicPath: "/static/" 12 | }, 13 | plugins: [ 14 | new MiniCssExtractPlugin({filename: "styles.css", allChunks: true}), 15 | new webpack.DefinePlugin({ 16 | "process.env": { 17 | NODE_ENV: JSON.stringify("production"), 18 | SHOW_NETLIFY_BADGE: JSON.stringify(process.env.SHOW_NETLIFY_BADGE) 19 | } 20 | }) 21 | ], 22 | resolve: { 23 | extensions: [".js", ".jsx", ".css"] 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.jsx?$/, 29 | use: [ 30 | "babel-loader", 31 | ], 32 | include: [ 33 | path.join(__dirname, "src"), 34 | path.join(__dirname, "playground"), 35 | path.join(__dirname, "node_modules", "codemirror", "mode", "javascript"), 36 | ], 37 | }, 38 | { 39 | test: /\.css$/, 40 | use: [ 41 | { 42 | loader: MiniCssExtractPlugin.loader, 43 | }, 44 | "css-loader", 45 | ], 46 | include: [ 47 | path.join(__dirname, "css"), 48 | path.join(__dirname, "playground"), 49 | path.join(__dirname, "node_modules"), 50 | ], 51 | } 52 | ] 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /playground/samples/references.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | definitions: { 4 | address: { 5 | type: "object", 6 | properties: { 7 | street_address: { type: "string" }, 8 | city: { type: "string" }, 9 | state: { type: "string" }, 10 | }, 11 | required: ["street_address", "city", "state"], 12 | }, 13 | node: { 14 | type: "object", 15 | properties: { 16 | name: { type: "string" }, 17 | children: { 18 | type: "array", 19 | items: { 20 | $ref: "#/definitions/node", 21 | }, 22 | }, 23 | }, 24 | }, 25 | }, 26 | type: "object", 27 | properties: { 28 | billing_address: { 29 | title: "Billing address", 30 | $ref: "#/definitions/address", 31 | }, 32 | shipping_address: { 33 | title: "Shipping address", 34 | $ref: "#/definitions/address", 35 | }, 36 | tree: { 37 | title: "Recursive references", 38 | $ref: "#/definitions/node", 39 | }, 40 | }, 41 | }, 42 | uiSchema: { 43 | "ui:order": ["shipping_address", "billing_address", "tree"], 44 | }, 45 | formData: { 46 | billing_address: { 47 | street_address: "21, Jump Street", 48 | city: "Babel", 49 | state: "Neverland", 50 | }, 51 | shipping_address: { 52 | street_address: "221B, Baker Street", 53 | city: "London", 54 | state: "N/A", 55 | }, 56 | tree: { 57 | name: "root", 58 | children: [{ name: "leaf" }], 59 | }, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /test/DescriptionField_test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { expect } from "chai"; 3 | 4 | import DescriptionField from "../src/components/fields/DescriptionField"; 5 | import { createSandbox, createComponent } from "./test_utils"; 6 | 7 | describe("DescriptionField", () => { 8 | let sandbox; 9 | 10 | beforeEach(() => { 11 | sandbox = createSandbox(); 12 | }); 13 | 14 | afterEach(() => { 15 | sandbox.restore(); 16 | }); 17 | 18 | // For some reason, stateless components needs to be wrapped into a stateful 19 | // one to be rendered into the document. 20 | class DescriptionFieldWrapper extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | } 24 | render() { 25 | return ; 26 | } 27 | } 28 | 29 | it("should return a div for a custom component", () => { 30 | const props = { 31 | description: description, 32 | }; 33 | const { node } = createComponent(DescriptionFieldWrapper, props); 34 | 35 | expect(node.tagName).to.equal("DIV"); 36 | }); 37 | 38 | it("should return a p for a description text", () => { 39 | const props = { 40 | description: "description", 41 | }; 42 | const { node } = createComponent(DescriptionFieldWrapper, props); 43 | 44 | expect(node.tagName).to.equal("P"); 45 | }); 46 | 47 | it("should have the expected id", () => { 48 | const props = { 49 | description: "Field description", 50 | id: "sample_id", 51 | }; 52 | const { node } = createComponent(DescriptionFieldWrapper, props); 53 | 54 | expect(node.id).to.equal("sample_id"); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /playground/samples/simple.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A registration form", 4 | description: "A simple form example.", 5 | type: "object", 6 | required: ["firstName", "lastName"], 7 | properties: { 8 | firstName: { 9 | type: "string", 10 | title: "First name", 11 | default: "Chuck", 12 | }, 13 | lastName: { 14 | type: "string", 15 | title: "Last name", 16 | }, 17 | age: { 18 | type: "integer", 19 | title: "Age", 20 | }, 21 | bio: { 22 | type: "string", 23 | title: "Bio", 24 | }, 25 | password: { 26 | type: "string", 27 | title: "Password", 28 | minLength: 3, 29 | }, 30 | telephone: { 31 | type: "string", 32 | title: "Telephone", 33 | minLength: 10, 34 | }, 35 | }, 36 | }, 37 | uiSchema: { 38 | firstName: { 39 | "ui:autofocus": true, 40 | "ui:emptyValue": "", 41 | }, 42 | age: { 43 | "ui:widget": "updown", 44 | "ui:title": "Age of person", 45 | "ui:description": "(earthian year)", 46 | }, 47 | bio: { 48 | "ui:widget": "textarea", 49 | }, 50 | password: { 51 | "ui:widget": "password", 52 | "ui:help": "Hint: Make it strong!", 53 | }, 54 | date: { 55 | "ui:widget": "alt-datetime", 56 | }, 57 | telephone: { 58 | "ui:options": { 59 | inputType: "tel", 60 | }, 61 | }, 62 | }, 63 | formData: { 64 | lastName: "Norris", 65 | age: 75, 66 | bio: "Roundhouse kicking asses since 1940", 67 | password: "noneed", 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /playground/samples/customArray.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function ArrayFieldTemplate(props) { 4 | return ( 5 |
6 | {props.items && 7 | props.items.map(element => ( 8 |
9 |
{element.children}
10 | {element.hasMoveDown && ( 11 | 18 | )} 19 | {element.hasMoveUp && ( 20 | 27 | )} 28 | 31 |
32 |
33 | ))} 34 | 35 | {props.canAdd && ( 36 |
37 |

38 | 41 |

42 |
43 | )} 44 |
45 | ); 46 | } 47 | 48 | export default { 49 | schema: { 50 | title: "Custom array of strings", 51 | type: "array", 52 | items: { 53 | type: "string", 54 | }, 55 | }, 56 | formData: ["react", "jsonschema", "form"], 57 | ArrayFieldTemplate, 58 | }; 59 | -------------------------------------------------------------------------------- /playground/samples/index.js: -------------------------------------------------------------------------------- 1 | import arrays from "./arrays"; 2 | import anyOf from "./anyOf"; 3 | import oneOf from "./oneOf"; 4 | import nested from "./nested"; 5 | import numbers from "./numbers"; 6 | import simple from "./simple"; 7 | import widgets from "./widgets"; 8 | import ordering from "./ordering"; 9 | import references from "./references"; 10 | import custom from "./custom"; 11 | import errors from "./errors"; 12 | import large from "./large"; 13 | import date from "./date"; 14 | import validation from "./validation"; 15 | import files from "./files"; 16 | import single from "./single"; 17 | import customArray from "./customArray"; 18 | import customObject from "./customObject"; 19 | import alternatives from "./alternatives"; 20 | import propertyDependencies from "./propertyDependencies"; 21 | import schemaDependencies from "./schemaDependencies"; 22 | import additionalProperties from "./additionalProperties"; 23 | import nullable from "./nullable"; 24 | import nullField from "./null"; 25 | 26 | export const samples = { 27 | Simple: simple, 28 | Nested: nested, 29 | Arrays: arrays, 30 | Numbers: numbers, 31 | Widgets: widgets, 32 | Ordering: ordering, 33 | References: references, 34 | Custom: custom, 35 | Errors: errors, 36 | Large: large, 37 | "Date & time": date, 38 | Validation: validation, 39 | Files: files, 40 | Single: single, 41 | "Custom Array": customArray, 42 | "Custom Object": customObject, 43 | Alternatives: alternatives, 44 | "Property dependencies": propertyDependencies, 45 | "Schema dependencies": schemaDependencies, 46 | "Additional Properties": additionalProperties, 47 | "Any Of": anyOf, 48 | "One Of": oneOf, 49 | "Null fields": nullField, 50 | Nullable: nullable, 51 | }; 52 | -------------------------------------------------------------------------------- /playground/samples/customObject.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function ObjectFieldTemplate({ TitleField, properties, title, description }) { 4 | return ( 5 |
6 | 7 |
8 | {properties.map(prop => ( 9 |
12 | {prop.content} 13 |
14 | ))} 15 |
16 | {description} 17 |
18 | ); 19 | } 20 | 21 | export default { 22 | schema: { 23 | title: "A registration form", 24 | description: 25 | "This is the same as the simple form, but it is rendered as a bootstrap grid. Try shrinking the browser window to see it in action.", 26 | type: "object", 27 | required: ["firstName", "lastName"], 28 | properties: { 29 | firstName: { 30 | type: "string", 31 | title: "First name", 32 | }, 33 | lastName: { 34 | type: "string", 35 | title: "Last name", 36 | }, 37 | age: { 38 | type: "integer", 39 | title: "Age", 40 | }, 41 | bio: { 42 | type: "string", 43 | title: "Bio", 44 | }, 45 | password: { 46 | type: "string", 47 | title: "Password", 48 | minLength: 3, 49 | }, 50 | telephone: { 51 | type: "string", 52 | title: "Telephone", 53 | minLength: 10, 54 | }, 55 | }, 56 | }, 57 | formData: { 58 | firstName: "Chuck", 59 | lastName: "Norris", 60 | age: 75, 61 | bio: "Roundhouse kicking asses since 1940", 62 | password: "noneed", 63 | }, 64 | ObjectFieldTemplate, 65 | }; 66 | -------------------------------------------------------------------------------- /playground/samples/nested.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | schema: { 3 | title: "A list of tasks", 4 | type: "object", 5 | required: ["title"], 6 | properties: { 7 | title: { 8 | type: "string", 9 | title: "Task list title", 10 | }, 11 | tasks: { 12 | type: "array", 13 | title: "Tasks", 14 | items: { 15 | type: "object", 16 | required: ["title"], 17 | properties: { 18 | title: { 19 | type: "string", 20 | title: "Title", 21 | description: "A sample title", 22 | }, 23 | details: { 24 | type: "string", 25 | title: "Task details", 26 | description: "Enter the task details", 27 | }, 28 | done: { 29 | type: "boolean", 30 | title: "Done?", 31 | default: false, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, 37 | }, 38 | uiSchema: { 39 | tasks: { 40 | items: { 41 | details: { 42 | "ui:widget": "textarea", 43 | }, 44 | }, 45 | }, 46 | }, 47 | formData: { 48 | title: "My current tasks", 49 | tasks: [ 50 | { 51 | title: "My first task", 52 | details: 53 | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 54 | done: true, 55 | }, 56 | { 57 | title: "My second task", 58 | details: 59 | "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur", 60 | done: false, 61 | }, 62 | ], 63 | }, 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/widgets/TextareaWidget.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | function TextareaWidget(props) { 5 | const { 6 | id, 7 | options, 8 | placeholder, 9 | value, 10 | required, 11 | disabled, 12 | readonly, 13 | autofocus, 14 | onChange, 15 | onBlur, 16 | onFocus, 17 | } = props; 18 | const _onChange = ({ target: { value } }) => { 19 | return onChange(value === "" ? options.emptyValue : value); 20 | }; 21 | return ( 22 |