diff --git a/.eslintrc-javascript b/.eslintrc-javascript new file mode 100644 index 0000000000..a5068c33e6 --- /dev/null +++ b/.eslintrc-javascript @@ -0,0 +1,34 @@ +{ + "extends": "eslint:recommended", + "parser": "@babel/eslint-parser", + "parserOptions": { + "babelOptions": { + "presets": ["@babel/preset-react"] + } + }, + "rules": { + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "react/jsx-tag-spacing": [1, { + "beforeSelfClosing": "always" + }], + "curly": [2], + "linebreak-style": [2, "unix"], + "semi": [2, "always"], + "comma-dangle": [0], + "no-unused-vars": [2, { + "vars": "all", + "args": "none", + "ignoreRestSiblings": true + }], + "no-console": [0], + "object-curly-spacing": [2, "always"], + "keyword-spacing": ["error"] + }, + "env": { + "es6": true, + "browser": true, + "node": true + } +} diff --git a/.eslintrc-typescript b/.eslintrc-typescript new file mode 100644 index 0000000000..d0d394b6da --- /dev/null +++ b/.eslintrc-typescript @@ -0,0 +1,42 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended" + ], + "rules": { + "react/prop-types": 0, + "react/no-find-dom-node": 0, + "react/display-name": 0, + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "react/jsx-tag-spacing": [1, { + "beforeSelfClosing": "always" + }], + "curly": [2], + "linebreak-style": [2, "unix"], + "semi": [2, "always"], + "comma-dangle": [0], + "@typescript-eslint/no-unused-vars": [2, { + "vars": "all", + "args": "none", + "ignoreRestSiblings": true + }], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "no-console": [0], + "object-curly-spacing": [2, "always"], + "keyword-spacing": ["error"], + "no-prototype-builtins": "warn", + "@typescript-eslint/no-empty-function": "warn", + "@typescript-eslint/no-var-requires": "warn" + }, + "env": { + "es6": true, + "browser": true, + "node": true + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b86905f214..2d3b094a28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,16 +2,16 @@ name: CI on: push: - branches: [ master ] + branches: [ master, rjsf-v5 ] pull_request: - branches: [ master ] + branches: [ master, rjsf-v5 ] jobs: build: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x] + node-version: [12.x, 14.x, 16.x, 18.x] steps: - uses: actions/checkout@v3 @@ -22,7 +22,7 @@ jobs: - run: npm ci - run: npm run lint - run: npm run cs-check - + - name: Build with Netlify badge if: github.ref != 'refs/heads/master' run: npm run build @@ -31,14 +31,14 @@ jobs: - name: Build if: github.ref == 'refs/heads/master' run: npm run build - - - if: matrix.node-version == '14.x' + + - if: matrix.node-version == '16.x' uses: actions/upload-artifact@v2 with: name: dist path: packages/playground/build - run: npm test - + deploy_preview: runs-on: ubuntu-latest if: github.ref != 'refs/heads/master' @@ -58,7 +58,7 @@ jobs: NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} - + deploy_playground: runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' @@ -78,7 +78,7 @@ jobs: build_dir: dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + # deploy_canary: # runs-on: ubuntu-latest # if: github.ref == 'refs/heads/master' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e01e437a76..0f2c4ee99a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Use Node.js 14.x + - name: Use Node.js 16.x uses: actions/setup-node@v3 with: - node-version: 14.x + node-version: 16.x - run: npm ci - run: npm run build - run: npm test diff --git a/.gitignore b/.gitignore index 21cab454a6..c3e05d6f25 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +.rts2_** # End of https://www.gitignore.io/api/osx,node,linux,windows @@ -127,6 +128,7 @@ yarn.lock .vscode .idea *.iml +.editorconfig # Code coverage coverage @@ -134,3 +136,4 @@ coverage venv *.orig +out/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..5341e4121d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" +# Hopefully this disables husky on github CI +[ -n "$CI" ] && exit 0 +npm run pre-commit:husky diff --git a/.node-version b/.node-version index 8351c19397..b6a7d89c68 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14 +16 diff --git a/.nvmrc b/.nvmrc index 8351c19397..b6a7d89c68 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14 +16 diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000000..2759974a6f --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +bracketSameLine: false +trailingComma: es5 +useTabs: false +semi: true +tabWidth: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 917fddec1e..328e99ac57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,98 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> -# v5.0.0 (coming soon) +# v5.0.0-beta.1 + +## Global changes across all themes: +- Node 16 is now the default node engine for all packages, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2687) +- Refactored all themes to use the new `@rjsf/utils` library functions and types +- Refactored the individual theme forms to consolidate `templates` as part of the fix for https://github.com/rjsf-team/react-jsonschema-form/issues/2526 + - All the work implementing the `BaseInputTemplate` should fix (https://github.com/rjsf-team/react-jsonschema-form/issues/2926, https://github.com/rjsf-team/react-jsonschema-form/issues/2889, https://github.com/rjsf-team/react-jsonschema-form/issues/2875, https://github.com/rjsf-team/react-jsonschema-form/issues/2223) + - Also made the display of `title` and `description` consistent across themes, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2481, https://github.com/rjsf-team/react-jsonschema-form/issues/2363, https://github.com/rjsf-team/react-jsonschema-form/issues/2219) + - This change also ensures that all templates are properly exported, resolving (https://github.com/rjsf-team/react-jsonschema-form/issues/2365) +- Bumped most devDependencies to the latest versions where possible +- Switched all repos `package.json` and `package-lock.json` files to be built and maintained by Node 16. +- Adding button templates help to change text for buttons (https://github.com/rjsf-team/react-jsonschema-form/issues/2082, https://github.com/rjsf-team/react-jsonschema-form/issues/2357) + +## @rjsf/utils +- New package created by refactoring and converting to Typescript the `utils.js` file from `core` into independent functions. + - Resolves (https://github.com/rjsf-team/react-jsonschema-form/issues/1655, https://github.com/rjsf-team/react-jsonschema-form/issues/2480, https://github.com/rjsf-team/react-jsonschema-form/issues/2341) +- Updated `types` from `core` in `utils` to better match the implementation across all themes + - Included adding a bunch of new types for existing and new features + - The type updates should fix (https://github.com/rjsf-team/react-jsonschema-form/issues/2871, https://github.com/rjsf-team/react-jsonschema-form/issues/2673, https://github.com/rjsf-team/react-jsonschema-form/issues/2347, https://github.com/rjsf-team/react-jsonschema-form/issues/2186) +- Clear errors on `formData` change when `liveOmit=true` when "additionalProperties: false" [issue 1507](https://github.com/rjsf-team/react-jsonschema-form/issues/1507) (https://github.com/rjsf-team/react-jsonschema-form/pull/2631) + +## @rjsf/validator-ajv6 +- New package created by refactoring and converting to Typescript the `validator.js` file from `core` into independent functions as well as a class that implements the new `ValidatorType` interface. + - [#2693](https://github.com/rjsf-team/react-jsonschema-form/issues/2693). +- Added support for customizing the options passed to the creation of the `ajv` instance. + +## @rjsf/validator-ajv6 +- A **BREAKING CHANGE** to `toErrorList()` was made so that it takes `fieldPath: string[]` rather than `fieldName='root'` as part of the fix to (https://github.com/rjsf-team/react-jsonschema-form/issues/1596) + - The returned `errors` also now adds `property` from the `fieldPath` along with the proper path from the `property` to the `stack` message, making it consistent with the AJV errors. + - Previously the `stack` attribute would say `root: error message`; now it says `. error message` + - In addition, the extra information provided by AJV is no longer lost from the `errors` when merged with custom validation, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/1596). + +## @rjsf/core +- Converted core to Typescript (https://github.com/rjsf-team/react-jsonschema-form/issues/583) +- `ui:emptyValue` now works with selects (https://github.com/rjsf-team/react-jsonschema-form/issues/1041) +- Refactoring `utils.js` into the new `@rjsf/utils` fixes (https://github.com/rjsf-team/react-jsonschema-form/issues/2719) +- **BREAKING CHANGE** Fix overriding core submit button className (https://github.com/rjsf-team/react-jsonschema-form/issues/2979) +- Fix `ui:field` with anyOf or oneOf no longer rendered twice (#2890) +- **BREAKING CHANGE** Fixed `anyOf` and `oneOf` getting incorrect, potentially duplicate ids when combined with array (https://github.com/rjsf-team/react-jsonschema-form/issues/2197) +- `formContext` is now passed properly to `SchemaField`, fixes (https://github.com/rjsf-team/react-jsonschema-form/issues/2394, https://github.com/rjsf-team/react-jsonschema-form/issues/2274) +- Added `ui:duplicateKeySuffixSeparator` to customize how duplicate object keys are renamed when using `additionalProperties`. +- The `extraErrors` are now consistently appended onto the end of the schema validation-based `errors` information that is returned via the `onErrors()` callback when submit fails. + - In addition, the extra information provided by AJV is no longer stripped from the `errors` during the merge process, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/1596). +- Fixed id generation for `RadioWidget` to no longer use random numbers fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2461) +- Correctly call the `onChange` handler in the new set of props if it changed, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/1708). +- Fixed race condition for `onChange` when `formData` is controlled prop, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/513), + +## @rjsf/antd +- Fix esm build to use `@rollup/plugin-replace` to replace `antd/lib` and `rc-picker/lib` with `antd/es` and `rc-picker/es` respectively, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2962) + +## @rjsf/bootstrap-4 +- Bootstrap-4 `withTheme` customizations should work properly now (https://github.com/rjsf-team/react-jsonschema-form/issues/2058) +- `ArrayFieldTemplate` refactor seems to have fixed https://github.com/rjsf-team/react-jsonschema-form/issues/2775 +- Fix issues with `SelectField` (https://github.com/rjsf-team/react-jsonschema-form/issues/2616, https://github.com/rjsf-team/react-jsonschema-form/issues/2875) + +## @rjsf/chakra-ui +- Properly handle the hidden field in this theme (https://github.com/rjsf-team/react-jsonschema-form/issues/2571) + +## @rjsf/material-ui +- The theme for Material UI version 5 (i.e. `@rjsf/mui`) was split out of the theme for version 4 (i.e. `@rjsf/material-ui`) to resolve the following issues: + - [#2762](https://github.com/rjsf-team/react-jsonschema-form/issues/2762) + - [#2858](https://github.com/rjsf-team/react-jsonschema-form/issues/2858) + - [#2905](https://github.com/rjsf-team/react-jsonschema-form/issues/2905) + - [#2945](https://github.com/rjsf-team/react-jsonschema-form/issues/2945) + - [#2774](https://github.com/rjsf-team/react-jsonschema-form/issues/2774) +- Material-UI TextWidget now respects `inputType` in uiSchema (https://github.com/rjsf-team/react-jsonschema-form/issues/2057) + - Also respects `step` for `number` type (https://github.com/rjsf-team/react-jsonschema-form/issues/2488) +- Material-UI UpDownWidget now support min/max/step (https://github.com/rjsf-team/react-jsonschema-form/issues/2022) +- Properly handle the hidden field in this theme (https://github.com/rjsf-team/react-jsonschema-form/issues/2571) +- Select properly accepts true or false (https://github.com/rjsf-team/react-jsonschema-form/issues/2326) + +## @rjsf/mui +- The theme for Material UI version 5 (i.e. `@rjsf/mui`) was split out of the theme for version 4 (i.e. `@rjsf/material-ui`) to resolve the following issues: + - [#2762](https://github.com/rjsf-team/react-jsonschema-form/issues/2762) + - [#2858](https://github.com/rjsf-team/react-jsonschema-form/issues/2858) + - [#2905](https://github.com/rjsf-team/react-jsonschema-form/issues/2905) + - [#2945](https://github.com/rjsf-team/react-jsonschema-form/issues/2945) + - [#2774](https://github.com/rjsf-team/react-jsonschema-form/issues/2774) +- Material-UI TextWidget now respects `inputType` in uiSchema (https://github.com/rjsf-team/react-jsonschema-form/issues/2057) + - Also respects `step` for `number` type (https://github.com/rjsf-team/react-jsonschema-form/issues/2488) +- Material-UI UpDownWidget now support min/max/step (https://github.com/rjsf-team/react-jsonschema-form/issues/2022) +- Properly handle the hidden field in this theme (https://github.com/rjsf-team/react-jsonschema-form/issues/2571) + +## @rjsf/semantic-ui +- Fix missing error class on fields (https://github.com/rjsf-team/react-jsonschema-form/issues/2666) +- Fixed the `main` definition in `semantic-ui` to fix (https://github.com/withastro/astro/issues/4357) +- Properly handle the hidden field in this theme (https://github.com/rjsf-team/react-jsonschema-form/issues/2571) + +## Dev / docs / playground +- Demonstrate use of `ui:field` with `anyOf` (#2890) +- Playground now uses webpack 5 +- Corrected number field default (https://github.com/rjsf-team/react-jsonschema-form/issues/2358) # 4.2.1 * fix typo by @epicfaace in https://github.com/rjsf-team/react-jsonschema-form/pull/2854 @@ -47,7 +138,7 @@ should change the heading of the (upcoming) version to include a major version b - NOTE: `@rjsf/material-ui` was retained to avoid a breaking change, but using it will continue to cause bundler warnings - See the `README.md` for the `@rjsf/material-ui` package for updated usage information - Fixed (#2831) for `material-ui` by removing the `DefaultChildren` passed into the themes - + ## @rjsf/bootstrap-4 - SubmitButton widget to use new ui:submitButtonOptions on the submit button for forms (https://github.com/rjsf-team/react-jsonschema-form/pull/2640) diff --git a/docs/3.x upgrade guide.md b/docs/3.x upgrade guide.md index 4ff446ef80..bad2dd34c3 100644 --- a/docs/3.x upgrade guide.md +++ b/docs/3.x upgrade guide.md @@ -12,7 +12,7 @@ Dropped support for Node 8, 9, 10. Minimum supported version of Node.js is now 1 ### Help field IDs -IDs for [Help fields](https://react-jsonschema-form.readthedocs.io/en/latest/api-reference/uiSchema/#help) are now suffixed by `__help` so that the IDs are unique. Previously, their IDs would be nonexistent or the same as the fields that they were describing. +IDs for [Help fields](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/uiSchema/#help) are now suffixed by `__help` so that the IDs are unique. Previously, their IDs would be nonexistent or the same as the fields that they were describing. ### Bring your own polyfills diff --git a/docs/4.x upgrade guide.md b/docs/4.x upgrade guide.md index f6fd2c61a3..3ebc1fa99a 100644 --- a/docs/4.x upgrade guide.md +++ b/docs/4.x upgrade guide.md @@ -8,4 +8,4 @@ No longer actively supporting React version < 16.3. The minimum supported versio ### @rjsf/material-ui package - Minimum version of material-ui 4 -If you are using the material-ui 4 theme, @material-ui/core and @material-ui/icons packages should be updated to the latest versions. The minimum versions supported for @material-ui/core and @material-ui/icons are 4.12.0 and 4.11.1 respectively. This change is required for [support for the material-ui version 5 theme](https://github.com/rjsf-team/react-jsonschema-form/tree/master/packages/material-ui) \ No newline at end of file +If you are using the material-ui 4 theme, @material-ui/core and @material-ui/icons packages should be updated to the latest versions. The minimum versions supported for @material-ui/core and @material-ui/icons are 4.12.0 and 4.11.1 respectively. This change is required for [support for the material-ui version 5 theme](https://github.com/rjsf-team/react-jsonschema-form/tree/master/packages/material-ui) diff --git a/docs/5.x upgrade guide.md b/docs/5.x upgrade guide.md new file mode 100644 index 0000000000..eccd4101ad --- /dev/null +++ b/docs/5.x upgrade guide.md @@ -0,0 +1,493 @@ +# 5.x Upgrade Guide + +## Breaking changes + +There were several significant **breaking changes** in RJSF version 5 that were necessary in order to support the following new features: + +- Schema validation was decoupled from `@rjsf/core` to resolve issue [#2693](https://github.com/rjsf-team/react-jsonschema-form/issues/2693). + - Additionally, in order to break a circular dependency in the validation refactor, the `@rjsf/core/utils.js` file was split out into its own `@rjsf/utils` package as was suggested in [#1655](https://github.com/rjsf-team/react-jsonschema-form/issues/1655). +- The theme for Material UI version 5 (i.e. `@rjsf/mui`) was split out of the theme for version 4 (i.e. `@rjsf/material-ui`) to resolve the following issues: + - [#2762](https://github.com/rjsf-team/react-jsonschema-form/issues/2762) + - [#2858](https://github.com/rjsf-team/react-jsonschema-form/issues/2858) + - [#2905](https://github.com/rjsf-team/react-jsonschema-form/issues/2905) + - [#2945](https://github.com/rjsf-team/react-jsonschema-form/issues/2945) +- As part of the fix for [#2526](https://github.com/rjsf-team/react-jsonschema-form/issues/2526) all the existing templates in the previous version were moved into a new `templates` dictionary, similar to how `widgets` and `fields` work + - This `templates` dictionary was added to the `Registry` and also the `Form` props, replacing the `ArrayFieldTemplate`, `FieldTemplate`, `ObjectFieldTemplate` and `ErrorList` props. + - In addition, several of the `fields` and `widgets` based components were moved into the `templates` dictionary as they were more like templates than true `Field`s or `Widget`s. + - [#2945](https://github.com/rjsf-team/react-jsonschema-form/issues/2945) +- Fixed `anyOf` and `oneOf` getting incorrect, potentially duplicate ids when combined with array (https://github.com/rjsf-team/react-jsonschema-form/issues/2197) + +### Node support + +Version 5 is dropping official support for Node 12 as it is no longer a [maintained version](https://nodejs.org/en/about/releases/). +Please use Node 16 when making any changes to `package.json` and `package-lock.json` files. +All PR and branch builds are running against Node 14, 16 and 18. + +### React version + +RJSF is no longer actively supporting React version < 16.14.x. +React 17 is officially supported on all the themes where the underlying theme library also supports React 17 (only `semantic-ui` is current restricted to React 16). + +Unfortunately, there is required work pending to properly support React 18, so use it at your own risk. + +### New packages + +There are three new packages added in RJSF version 5: +- `@rjsf/utils`: All of the [utility functions](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/utiltity-functions) previously imported from `@rjsf/core/utils` as well as the Typescript types for RJSF version 5. + - The following new utility functions were added: `createSchemaUtils()`, `getInputProps()`, `mergeValidationData()` and `processSelectValue()` +- `@rjsf/validator-ajv6`: The [ajv](https://github.com/ajv-validator/ajv)-v6-based validator refactored out of `@rjsf/core@4.x`, that implements the `ValidatorType` interface defined in `@rjsf/utils`. +- `@rjsf/mui`: Previously `@rjsf/material-ui/v5`, now provided as its own theme. + +### `@rjsf/core` BREAKING CHANGES + +#### Types +In version 4, RJSF exported all its types directly from `@rjsf/core`. +In version 5, only the types for the `Form` component and the `withTheme()` HOC are exported directly from `@rjsf/core`. +All the rest of the types for RJSF are now exported from the new `@rjsf/utils` package. + +NOTE: The types in `@rjsf/utils` have been improved significantly from those in version 4. +Some of the most notable changes are: +- `RJSFSchema` has replaced the use of `JSON7Schema` for future compatibility reasons. + - Currently `RJSFSchema` is simply an alias to `JSON7Schema` so this change is purely a naming one. + - It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` so that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected. +- `RJSFSchemaDefinition` has replaced the use of `JSONSchema7Definition` for the same reasons. +- The use of the generic `T` (defaulting to `any`) for the `formData` type has been expanded to cover all type hierarchies that use `formData`. +- A new generic `F` (defaulting to `any`) was added for the `formContext` type, and all types in the hierarchy that use `formContext` have had that generic added to them. +- The new `CustomValidator`, `ErrorTransformer`, `ValidationData`, `ValidatorType` and `SchemaUtilsType` types were added to support the decoupling of the validation implementation. +- The new `TemplatesType`, `ArrayFieldDescriptionProps`, `ArrayFieldTitleProps`, `UnsupportedFieldProps`, `IconButtonProps`, `SubmitButtonProps` and `UIOptionsBaseType` were added to support the consolidation (and expansion) of `templates` in the `Registry` and `Form`. +- **BREAKING CHANGE** The `DescriptionField` and `TitleField` props were removed from the `ArrayFieldTemplateProps` and `ObjectFieldTemplateProps` as they can now be derived from the `templates` or `uiSchema` via the new `getTemplate()` utility function. +- **BREAKING CHANGE** The `fields` prop was removed from the `FieldTemplateProps` as you can simply use `registry.fields` instead. + +You can view all these [types](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/utils/src/types.ts) on Github. + + +#### Form props +In version 5, the `Form` component's two optional props `additionalMetaSchemas` and `customFormats` were replaced with the new, required `validator` prop, in order to support the decoupling of the validation implementation. +This new `validator` prop is expected to be an implementation of the `ValidatorType` interface. +The new `@rjsf/validator-ajv6` package contains the refactored implementation of the version 4 validator. + +There are two ways to use this new package to provide a `validator` for a `Form`. +First, you can simply import the default validator from the package and pass it to a `Form`. + +```tsx +import { RJSFSchema } from "@rjsf/utils"; +import Form from "@rjsf/core"; +import validator from "@rjsf/validator-ajv6"; + +// Your schema +const schema: RJSFSchema = { ... }; + +render(( +
+), document.getElementById("app")); +``` + +Second, if you were actually providing one (or both) of the removed optional props to your `Form`, you can continue using them by creating a customized validator. + +```tsx +import { RJSFSchema } from "@rjsf/utils"; +import Form from "@rjsf/core"; +import { customizeValidator, CustomValidatorOptionsType } from "@rjsf/validator-ajv6"; + +// Your schema, additionalMetaSchemas and/or customFormats +const schema: RJSFSchema = { ... }; +const additionalMetaSchemas: CustomValidatorOptionsType['additionalMetaSchemas'] = [{ ... }]; +const customFormats: CustomValidatorOptionsType['customFormats'] = { ... }; + +const validator = customizeValidator({ additionalMetaSchemas, customFormats }); + +render(( + +), document.getElementById("app")); +``` + +##### `validate` prop renamed +Additionally, in version 5, the `validate` prop on `Form` was renamed to `customValidate` to avoid confusion with the new `validator` prop. + +##### `fields` prop changes +In previous versions, it was possible to provide an override to the `DescriptionField`, `TitleField` and/or `UnsupportedField` components by providing a custom implementation in the `fields` prop on the `Form`. +Since these components do not actually support the `FieldProps` interface, they were moved into the `templates` dictionary instead. +If you were previously overriding any (or all) of these components, you can override them now via the `templates` prop on `Form` instead: + +```tsx +import { DescriptionFieldProps, RJSFSchema, TitleFieldProps } from "@rjsf/utils"; +import Form from "@rjsf/core"; +import validator from "@rjsf/validator-ajv6"; + +// Your schema +const schema: RJSFSchema = { ... }; + +// Your custom fields +const CustomDescriptionField = (props: DescriptionFieldProps) => { ... }; +const CustomTitleField = (props: TitleFieldProps) => { ... }; +const CustomUnsupportedField = (props: ObjectFieldTemplateProps) => { ... +}; + +const templates: Partial = { + DescriptionFieldTemplate: CustomDescriptionField, + TitleFieldTemplate: CustomTitleField, + UnsupportedFieldTemplate: CustomUnsupportedField, +}; + +render(( + +), document.getElementById("app")); +``` + +##### new `templates` prop +Additionally, in version 5, the `ArrayFieldTemplate`, `FieldTemplate`, `ObjectFieldTemplate` and `ErrorList` props were replaced with the `templates` prop as part of the `TemplatesType` consolidation. +If you were previously overriding any (or all) of these templates, you can simply consolidate them into the new `templates` prop on `Form` instead: + +```tsx +import { ArrayFieldTemplateProps, ErrorListProps, FieldTemplateProps, ObjectFieldTemplateProps, RJSFSchema } from "@rjsf/utils"; +import Form from "@rjsf/core"; +import validator from "@rjsf/validator-ajv6"; + +// Your schema +const schema: RJSFSchema = { ... }; + +// Your custom templates +const CustomArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { ... }; +const CustomFieldTemplate = (props: FieldTemplateProps) => { ... }; +const CustomObjectFieldTemplate = (props: ObjectFieldTemplateProps) => { ... }; +const CustomErrorField = (props: ErrorListProps) => { ... }; + +const templates: Partial = { + ArrayFieldTemplate: CustomArrayFieldTemplate, + FieldTemplate: CustomFieldTemplate, + ObjectFieldTemplate: CustomObjectFieldTemplate, + ErrorFieldTemplate: CustomErrorField, +}; + +render(( + +), document.getElementById("app")); +``` + +NOTE: In version 5, the `ArrayField` implementation was refactored to add 3 additional templates for presenting arrays along with the `ArrayFieldTemplate`. +If you were updating the `ArrayFieldTemplate` to modify just a subset of the UI, it may be easier for you to implement one of the other new templates instead. +See the [Custom Templates](https://react-jsonschema-form.readthedocs.io/en/stable/advanced-customization/custom-templates) documentation for more details. + +##### `widgets` prop change +In the previous version, it was possible to provide an override to the `SubmitButton` component by providing a custom implementation in the `widgets` prop on the `Form`. +Since this component only requires a tiny fraction of the `WidgetProps` interface, it was moved into the `templates.ButtonTemplates` dictionary instead with its own, reduced set of props. +If you were previously overriding this component, you can override it now via the `templates` prop on `Form` instead: + +```tsx +import { RJSFSchema, SubmitButtonProps } from "@rjsf/utils"; +import Form from "@rjsf/core"; +import validator from "@rjsf/validator-ajv6"; + +// Your schema +const schema: RJSFSchema = { ... }; + +// Your custom button +const CustomSubmitButton = (props: SubmitButtonProps) => { ... +}; + +const templates: Partial = { + ButtonTemplates: { + SubmitButton: CustomSubmitButton, + } +}; + +render(( + +), document.getElementById("app")); +``` + +#### utils.js +In version 5, all the utility functions that were previously accessed via `import { utils } from '@rjsf/core';` are now available via `import utils from '@rjsf/utils';`. +Because of the decoupling of validation from `@rjsf/core` there is a breaking change for all the [validator-based utility functions](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/utiltity-functions#validator-based-utility-functions), since they now require an additional `ValidatorType` parameter. +More over, one previously exported function `resolveSchema()` is no longer exposed in the `@rjsf/utils`, so use `retrieveSchema()` instead. + +If you have built custom fields or widgets that utilized any of these breaking-change functions, don't worry, there is a quick and easy solution for you. +The `registry` has a breaking-change which removes the previously deprecated `definitions` property while adding the new `schemaUtils` property. +This new `registry.schemaUtils` property implements the `SchemaUtilsType` interface, which allows you to call a version of each of these breaking-change functions without the need for passing either a `validator` or `rootSchema`. +Because all fields and widgets are guaranteed to be passed the `registry` as a prop, if your custom field/widget happens to use either the `registry.definitions` object or a breaking-change validator-based utility function you make the following changes: + +```tsx +import { RJSFSchema, FieldProps } from '@rjsf/utils'; + +function YourField(props: FieldProps) { + const { registry } = props; +// Change `registry.definitions` to `registry.rootSchema.definitions` +// const { definitions } = registry; <- version 4 + const { rootSchema } = registry; + const { definitions } = rootSchema; + ... +} +``` + +```tsx +// Change breaking-change function to schemaUtils instead, otherwise import from @rjsf/utils +// import { utils } from '@rjsf/core'; <- version 4 +// const { isMultiSelect, resolveSchema, getUiOptions } = utils; <- version 4 +import { RJSFSchema, WidgetProps, getUiOptions } from '@rjsf/utils'; + +function YourWidget(props: WidgetProps) { + const { registry, uiSchema } = props; + const { schemaUtils } = registry; +// const isMultiSelect = isMultiSelect(schema, rootSchema); <- version 4 +// const newSchema = resolveSchema(schema, formData, rootSchema); <- version 4 + const isMultiSelect = schemaUtils.isMultiSelect(schema); + const newSchema: RJSFSchema = schemaUtils.retrieveSchema(schema, formData); + const options = getUiOptions(uiSchema); + +... +} +``` + +#### validator.js +Because of the decoupling of validation from `@rjsf/core` this file was refactored into its own `@rjsf/validator-ajv6` package. +During that refactor a few **breaking changes** were made to how it works related to custom validation and `ErrorSchema` conversion. + +##### toErrorList param changed +In previous versions, the `toErrorList()` function used to take a `fieldName` string defaulted to `root`, and used it to format the `stack` message. +In version 5, `fieldName` was changed to `fieldPath` string array defaulted to an empty array, and is used to recursively add the field name to the errors as the `property` object along with the raw `message`. +The result is that if you had an `ErrorSchema` that looks like: + +```tsx +const errorSchema: ErrorSchema = { + __error: [ "error message 1"], + password: { __error: "passwords do not match" } +} +``` + +The returned result from calling `toErrorList(errorSchema)` has changed as follows: + +```tsx +// version 4 result +[ + { stack: "root: error message 1" }, + { stack: "password: passwords do not match" } +] + +// version 5 result +[ + { property: ".", message: "error message 1", stack: ". error message 1" }, + { property: ".password", message: "passwords do not match", stack: ".password passwords do not match" } +] +``` + +##### Custom validation and extraErrors +In previous versions, when using a custom validator on the `Form`, any errors that were generated were inconsistently inserted into the validations `errors` list. +In addition, there was an [issue](https://github.com/rjsf-team/react-jsonschema-form/issues/1596) where the non-`stack` AJV error information was lost when custom validation generated errors. +This issue has been fixed. +Also, when `extraErrors` were provided, they were being inconsistently inserted into the `errors` list and the non-`stack` AJV error information was lost. +In version 5, all of these errors will be consistently appended onto the end of the validation `errors` list, and the additional AJV error information is maintained. + +In other words, if custom validation or `extraErrors` produced the following `ErrorSchema`: + +```tsx +{ + __error: [ "Please correct your password"], + password2: { __error: "passwords do not match" } +} +``` + +And the AJV validation produced the following `ErrorSchema`: + +```tsx +{ + __error: [{ + message: "should NOT be shorter than 3 characters", + name: "minLength", + params: { limit: 3 }, + property: ".password2", + schemaPath: "#/properties/password2/minLength", + stack: ".password2 should NOT be shorter than 3 characters", + }] +} +``` + +The resulting `errors` list has changed as follows: + +```tsx +// version 4 +[ + { stack: "root: Please correct your password" }, + { stack: "password2: passwords do not match" }, + { stack: "password2: should NOT be shorter than 3 characters" } +] + +// version 5 +[ + { + message: "should NOT be shorter than 3 characters", + name: "minLength", + params: { limit: 3 }, + property: ".password2", + schemaPath: "#/properties/password2/minLength", + stack: ".password2 should NOT be shorter than 3 characters", + }, + { property: ".", message: "Please correct your password", stack: ". Please correct your password" }, + { property: ".", message: "passwords do not match", stack: ".password2 passwords do not match" } +] +``` + +#### Generate correct ids when arrays are combined with `anyOf`/`oneOf` + +In v4, with arrays inside `anyOf` or `oneOf`, the parent name was not used to generate ids. +For example, given a schema such as: + +```json +{ + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "properties": { + "foo": { + "type": "string", + }, + }, + }, + { + "properties": { + "bar": { + "type": "string", + }, + }, + }, + ], + }, + }, + }, +} +``` + +We would get fields with id `root_foo` and `root_bar`. +As you can imagine, we could end up with duplicated ids if there's actually a `foo` or a `bar` in the root of the schema. + +From v5, the child fields will correctly use the parent id when generating its own id, generating ids such as `root_items_0_foo`. + +#### Deprecations added in v5 + +##### Non-standard `enumNames` property + +`enumNames` is a non-standard JSON Schema field that was deprecated in version 5. +`enumNames` could be included in the schema to apply labels that differed from an enumeration value. +This behavior can still be accomplished with `oneOf` or `anyOf` containing `const` values, so `enumNames` support may be removed from a future major version of RJSF. +For more information, see [#532](https://github.com/rjsf-team/react-jsonschema-form/issues/532). + +##### uiSchema.classNames + +In versions previous to 5, `uiSchema.classNames` was the only property that did not require the `ui:` prefix. +Additionally, it did not support being added into the `ui:options` object. +This was fixed in version 5 to be consistent with all the other properties in the `uiSchema`, so the `uiSchema.classNames` support may be removed from a future major version of RJSF. + +If you are using `classNames` as follows, simply add the `ui:` prefix to it to remove the deprecation warning that will be displayed for each `uiSchema.classNames` you have: + +```jsx +// This uiSchema will log a deprecation warning to the console +const uiSchema = { + title: { + "classNames": "myClass" + } +}; +// This uiSchema will not +const uiSchema = { + title: { + "ui:classNames": "myClass" + } +}; +``` + +### `@rjsf/material-ui` BREAKING CHANGES + +This theme was simplified back to working only with Material UI version 4 after an unsuccessful attempt to have it fully support both versions 4 and 5 simultaneously. +As a result, the `MuiComponentContext`, `MuiForm5`, `Theme5` components and the `useMuiComponent` hook were removed from the export. +In addition, the `/v4` and `/v5` sub-packages were also removed. + +#### Migrating for Material UI version 4 +If you were using this theme for Material UI version 4 AND you were using the sub-package, simply remove the `/v4` from your imports. + +If you modified your Typescript configuration for the `/v4` sub-package, remove the following from your `tsconfig.json`: + +``` +{ + ... + "compilerOptions": { + ... + "baseUrl": ".", + "paths": { + "@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"] + } + } +} +``` + +If you modified your Jest configuration for the `/v4` sub-package, remove the following from your `jest.config.json`: + +``` + "moduleNameMapper": { + "@rjsf/material-ui/v4": "/node_modules/@rjsf/material-ui/dist/v4.js" + }, +``` + +#### Migrating for Material UI version 5 +If you were using this theme for Material UI version 5, you will want to use `@rjsf/mui` instead. +See below for some before and after examples. + +If you modified your Typescript configuration for the `/v5` sub-package, remove the following from your `tsconfig.json`: + +``` +{ + ... + "compilerOptions": { + ... + "baseUrl": ".", + "paths": { + "@rjsf/material-ui/*": ["node_modules/@rjsf/material-ui/dist/*"] + } + } +} +``` + +If you modified your Jest configuration for the `/v5` sub-package, remove the following from your `jest.config.json`: + +``` + "moduleNameMapper": { + "@rjsf/material-ui/v5": "/node_modules/@rjsf/material-ui/dist/v5.js" + }, +``` + +##### Before +```jsx +import Form5 from '@rjsf/material-ui'; +``` + or +```jsx +import Form from '@rjsf/material-ui/v5'; +``` + or +```jsx +import { withTheme } from '@rjsf/core'; +import Theme from '@rjsf/material-ui/v5'; +// Make modifications to the theme with your own fields and widgets +const Form = withTheme(Theme); +``` + or +```jsx +import { withTheme } from '@rjsf/core'; +import Theme5 from '@rjsf/material-ui'; +// Make modifications to the theme with your own fields and widgets +const Form = withTheme(Theme5); +``` + +##### After +```jsx +import Form from '@rjsf/mui'; +``` +or +```jsx +import { withTheme } from '@rjsf/core'; +import Theme from '@rjsf/mui'; +// Make modifications to the theme with your own fields and widgets +const Form = withTheme(Theme); +``` diff --git a/docs/advanced-customization/custom-templates.md b/docs/advanced-customization/custom-templates.md index af29a624ba..dc445fee55 100644 --- a/docs/advanced-customization/custom-templates.md +++ b/docs/advanced-customization/custom-templates.md @@ -2,21 +2,55 @@ This is an advanced feature that lets you customize even more aspects of the form: -_ | Custom Field | Custom Template | Custom Widget ---|---------- | ------------- | ---- -**What it does** | Overrides all behaviour | Overrides just the layout (not behaviour) | Overrides just the input box (not layout, labels, or help, or validation) -**Usage** | Global or per-field | Global or per-field | Global or per-field -**Global Example** | `` | `` | `` -**Per-Field Example** | `"ui:field": MyCustomField` | `"ui:ArrayFieldTemplate": MyArrayTemplate` | `"ui:widget":MyCustomWidget` -**Documentation** | [Custom Fields](custom-widgets-fields.md) | [FieldTemplate](#FieldTemplate) / [ArrayFieldTemplate](#ArrayFieldTemplate) / [ObjectFieldTemplate](#ObjectFieldTemplate) | [Custom Widgets](custom-widgets-fields.md) +| | Custom Field | Custom Template | Custom Widget | +|-----------------------|-------------------------------------------|----------------------------------------------------------------|---------------------------------------------------------------------------| +| **What it does** | Overrides all behaviour | Overrides just the layout (not behaviour) | Overrides just the input box (not layout, labels, or help, or validation) | +| **Usage** | Global or per-field | Global or per-field | Global or per-field | +| **Global Example** | `` | `` | `` | +| **Per-Field Example** | `"ui:field": MyCustomField` | `"ui:ArrayFieldTemplate": MyArrayTemplate` | `"ui:widget":MyCustomWidget` | +| **Documentation** | [Custom Fields](custom-widgets-fields.md) | See documentation below | [Custom Widgets](custom-widgets-fields.md) | + +In version 5, all existing `templates` were consolidated into a new `TemplatesType` interface that is provided as part of the `Registry`. +They can also be overloaded globally on the `Form` via the `templates` prop as well as globally or per-field through the `uiSchema`. +Further, many new templates were added or repurposed from existing `widgets` and `fields` in an effort to simplify the effort needed by theme authors to build new and/or maintain current themes. +These new templates can also be overridden by individual users to customize the specific needs of their application. +A special category of templates, `ButtonTemplates`, were also added to support the easy replacement of the `Submit` button on the form, the `Add` and `Remove` buttons associated with `additionalProperties` on objects and elements of arrays, as well as the `Move up` and `Move down` buttons used for reordering arrays. +This category, unlike the others, can only be overridden globally via the `templates` prop on `Form`. + +Below is the table that lists all the `templates`, their props interface, their `uiSchema` name and from where they originated in the previous version of RJSF: + +| Template* | Props Type | UiSchema name | Origin | +|------------------------------------------------------------------|----------------------------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [ArrayFieldTemplate](#ArrayFieldTemplate) | ArrayFieldTemplateProps | ui:ArrayFieldTemplate | Formerly `Form.ArrayFieldTemplate` or `Registry.ArrayFieldTemplate` | +| [ArrayFieldDescriptionTemplate*](#ArrayFieldDescriptionTemplate) | ArrayFieldDescriptionProps | ui:ArrayFieldDescriptionTemplate | Formerly part of `@rjsf/core` ArrayField, refactored as a template, used in all `ArrayFieldTemplate` implementations | +| [ArrayFieldItemTemplate*](#ArrayFieldItemTemplate) | ArrayFieldTemplateItemType | ui:ArrayFieldItemTemplate | Formerly an internal class for `ArrayFieldTemplate`s in all themes, refactored as a template in each theme, used in all `ArrayFieldTemplate` implementations | +| [ArrayFieldTitleTemplate*](#ArrayFieldTitleTemplate) | ArrayFieldTitleProps | ui:ArrayFieldTitleTemplate | Formerly part of `@rjsf/core` ArrayField, refactored as a template, used in all `ArrayFieldTemplate` implementations. | +| [BaseInputTemplate*](#BaseInputTemplate) | WidgetProps | ui:BaseInputTemplate | Formerly a `widget` in `@rjsf.core` moved to `templates` and newly implemented in each theme to maximize code reuse. | +| [DescriptionFieldTemplate*](#DescriptionFieldTemplate) | DescriptionFieldProps | ui:DescriptionFieldTemplate | Formerly a `field` in `@rjsf.core` moved to `templates` with the `Template` suffix. Previously implemented in each theme. | +| [ErrorListTemplate*](#ErrorListTemplate) | ErrorListProps | ui:ErrorListTemplate | Formerly `Form.ErrorList` moved to `templates` with the `Templates` suffix. Previously implemented in each theme. | +| [FieldTemplate](#FieldTemplate) | FieldTemplateProps | ui:FieldTemplate | Formerly `Form.FieldTemplate` or `Registry.FieldTemplate` | +| [ObjectFieldTemplate](#ObjectFieldTemplate) | ObjectFieldTemplateProps | ui:ObjectFieldTemplate | Formerly `Form.ObjectFieldTemplate` or `Registry.ObjectFieldTemplate` | +| [TitleFieldTemplate*](#TitleFieldTemplate) | TitleFieldProps | ui:TitleFieldTemplate | Formerly a `field` in `@rjsf.core` moved to `templates` with the `Template` suffix. Previously implemented in each theme. | +| [UnsupportedFieldTemplate*](#UnsupportedFieldTemplate) | UnsupportedFieldProps | ui:UnsupportedFieldTemplate | Formerly a `field` in `@rjsf.core` moved to `templates` with the `Template` suffix. | +| [ButtonTemplates.AddButton*](#AddButton) | IconButtonProps | n/a | Formerly an internal implementation in each theme | +| [ButtonTemplates.MoveDownButton*](#MoveDownButton) | IconButtonProps | n/a | Formerly an internal implementation in each theme | +| [ButtonTemplates.MoveUpButton*](#MoveUpButton) | IconButtonProps | n/a | Formerly an internal implementation in each theme | +| [ButtonTemplates.RemoveButton*](#RemoveButton) | IconButtonProps | n/a | Formerly an internal implementation in each theme | +| [ButtonTemplates.SubmitButton*](#SubmitButton) | SubmitButtonProps | n/a | Formerly a `field` in each theme move to `templates.ButtonTemplates` | + +\* indicates a new template in version 5 ## ArrayFieldTemplate 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. You can also customize arrays by specifying a widget in the relevant `ui:widget` schema, more details over on [Custom Widgets](../usage/arrays.md#custom-widgets). +This allows you to customize your array, and each element in the array. +If you only want to customize how the array's title, description or how the array items are presented, you may want to consider providing your own [ArrayFieldDescriptionTemplate](#ArrayFieldDescriptionTemplate), [ArrayFieldItemTemplate](#ArrayFieldItemTemplate) and/or [ArrayFieldTitleTemplate](#ArrayFieldTitleTemplate) instead. +You can also customize arrays by specifying a widget in the relevant `ui:widget` schema, more details over on [Custom Widgets](../usage/arrays.md#custom-widgets). +```tsx +import { ArrayFieldTemplateProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; -```jsx const schema = { type: "array", items: { @@ -24,7 +58,7 @@ const schema = { } }; -function ArrayFieldTemplate(props) { +function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { return (
{props.items.map(element => element.children)} @@ -34,8 +68,7 @@ function ArrayFieldTemplate(props) { } render(( - + ), document.getElementById("app")); ``` @@ -47,25 +80,25 @@ const uiSchema = { } ``` -Please see [customArray.js](https://github.com/rjsf-team/react-jsonschema-form/blob/4542cd254ffdc6dfaf55e8c9f6f17dc900d0d041/packages/playground/src/samples/customArray.js) for another example. +Please see [customArray.js](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/playground/src/samples/customArray.js) for another 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 +- `idSchema`: An object containing the id for this object & ids for its properties - `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. +- `hideError`: A boolean value stating if the field is hiding its errors. - `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. +- `rawErrors`: An array of strings listing all generated error messages from encountered errors for this widget - `registry`: The `registry` object. The following props are part of each element in `items`: @@ -83,8 +116,338 @@ The following props are part of each element 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. +- `registry`: The `registry` object. + +> Note: Array and object field templates are always rendered inside the FieldTemplate. To fully customize an array field template, you may need to specify both `ui:FieldTemplate` and `ui:ArrayFieldTemplate`. + +## ArrayFieldDescriptionTemplate + +The out-of-the-box version of this template will render the `DescriptionFieldTemplate` with a generated id, if there is a `description` otherwise nothing is rendered. +If you want different behavior for the rendering of the description of an array field, you can customize this template. +If you want a different behavior for the rendering of ALL descriptions in the `Form`, see [DescriptionFieldTemplate](#descriptionfieldtemplate) + +```tsx +import { ArrayFieldDescriptionProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "array", + items: { + type: "string" + } +}; + +function ArrayFieldDescriptionTemplate(props: ArrayFieldDescriptionProps) { + const { description, idSchema } = props; + const id = `${idSchema.$id}__description`; + return ( +
+ Description + {description} +
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property. + +```js +const uiSchema = { + "ui:ArrayFieldDescriptionTemplate": ArrayFieldDescriptionTemplate +} +``` + +The following props are passed to each `ArrayFieldDescriptionTemplate`: + +- `description`: The description of the array field being rendered. +- `idSchema`: The idSchema of the array field in the hierarchy. +- `uiSchema`: The uiSchema object for this array field. +- `registry`: The `registry` object. + +## ArrayFieldItemTemplate + +The `ArrayFieldItemTemplate` is used to render the representation of a single item in an array. +All of the `ArrayFieldTemplate` implementations in all themes get this template from the `registry` in order to render array fields items. +Each theme has an implementation of the `ArrayFieldItemTemplate` to render an array field item in a manner best suited to the theme. +If you want to change how an array field item is rendered you can customize this template (for instance to remove the move up/down and remove buttons). + +```tsx +import { ArrayFieldTemplateItemType } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "array", + items: { + type: "string" + } +}; + +function ArrayFieldItemTemplate(props: ArrayFieldTemplateItemType) { + const { children, className } = props; + return ( +
{children}
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to each `ArrayFieldItemTemplate`: + +- `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`. +- `key`: A stable, unique key for the array item. +- `onAddIndexClick: (index) => (event?) => void`: Returns a function that adds a new item at `index`. +- `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. +- `registry`: The `registry` object. + +## ArrayFieldTitleTemplate -> Note: Array and object field templates are always rendered inside of the FieldTemplate. To fully customize an array field template, you may need to specify both `ui:FieldTemplate` and `ui:ArrayFieldTemplate`. +The out-of-the-box version of this template will render the `TitleFieldTemplate` with a generated id, if there is a `title` otherwise nothing is rendered. +If you want a different behavior for the rendering of the title of an array field, you can customize this template. +If you want a different behavior for the rendering of ALL titles in the `Form`, see [TitleFieldTemplate](#titlefieldtemplate) + +```tsx +import { ArrayFieldTitleTemplateProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "array", + items: { + type: "string" + } +}; + +function ArrayFieldTitleTemplate(props: ArrayFieldTitleProps) { + const { description, idSchema } = props; + const id = `${idSchema.$id}__title`; + return ( +

+ {title} +

+ ); +} + +render(( + +), document.getElementById("app")); +``` + +You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property. + +```js +const uiSchema = { + "ui:ArrayFieldTitleTemplate": ArrayFieldTitleTemplate +} +``` + +The following props are passed to each `ArrayFieldTitleTemplate`: + +- `title`: The title of the array field being rendered. +- `idSchema`: The idSchema of the array field in the hierarchy. +- `uiSchema`: The uiSchema object for this array field. +- `required`: A boolean value stating if the field is required +- `registry`: The `registry` object. + +## BaseInputTemplate + +The `BaseInputTemplate` is the template to use to render the basic `` component for a theme. +It is used as the template for rendering many of the `` based widgets that differ by `type` and callbacks only. +For example, the `TextWidget` implementation in `core` is simply a wrapper around `BaseInputTemplate` that it gets from the `registry`. +Additionally, each theme implements its own version of `BaseInputTemplate` without needing to provide a different implementation of `TextWidget`. + +If you desire a different implementation for the `` based widgets, you can customize this template. +For instance, say you have a `CustomTextInput` component that you want to integrate: + +```tsx +import { getInputProps, WidgetProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +import CustomTextInput from '../CustomTextInput'; + +const schema = { + type: "string", + title: "My input", + description: "input description" +}; + +function BaseInputTemplate(props: WidgetProps) { + const { + schema, + id, + options, + label, + value, + type, + placeholder, + required, + disabled, + readonly, + autofocus, + onChange, + onBlur, + onFocus, + rawErrors, + hideError, + uiSchema, + registry, + formContext, + ...rest + } = props; + const onTextChange = ({ target: { value: val } }: React.ChangeEvent) => { + // Use the options.emptyValue if it is specified and newVal is also an empty string + onChange(val === '' ? options.emptyValue || '' : val); + }; + const onTextBlur = ({ target: { value: val } }: React.FocusEvent) => onBlur(id, val); + const onTextFocus = ({ target: { value: val } }: React.FocusEvent) => onFocus(id, val); + + const inputProps = { ...rest, ...getInputProps(schema, type, options) }; + const hasError = rawErrors.length > 0 && !hideError; + + return ( + + ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to the `BaseInputTemplate`: + +- `id`: The generated id for this widget; +- `schema`: The JSONSchema subschema object for this widget; +- `uiSchema`: The uiSchema for this widget; +- `value`: The current value for this widget; +- `placeholder`: The placeholder for the widget, if any; +- `required`: The required status of this widget; +- `disabled`: A boolean value stating if the widget is disabled; +- `hideError`: A boolean value stating if the widget is hiding its errors. +- `readonly`: A boolean value stating if the widget is read-only; +- `autofocus`: A boolean value stating if the widget should autofocus; +- `label`: The computed label for this widget, as a string +- `multiple`: A boolean value stating if the widget can accept multiple values; +- `onChange`: The value change event handler; call it with the new value every time it changes; +- `onKeyChange`: The key change event handler (only called for fields with `additionalProperties`); pass the new value every time it changes; +- `onBlur`: The input blur event handler; call it with the widget id and value; +- `onFocus`: The input focus event handler; call it with the widget id and value; +- `options`: A map of options passed as a prop to the component (see [Custom widget options](#custom-widget-options)). +- `options.enumOptions`: For enum fields, this property contains the list of options for the enum as an array of { label, value } objects. If the enum is defined using the oneOf/anyOf syntax, the entire schema object for each option is appended onto the { schema, label, value } object. +- `formContext`: The `formContext` object that you passed to `Form`. +- `rawErrors`: An array of strings listing all generated error messages from encountered errors for this widget. +- `registry`: The `registry` object + +## DescriptionFieldTemplate + +Each theme implements a `DescriptionFieldTemplate` used to render the description of a field. +If you want to customize how descriptions are rendered you can. + +```tsx +import { DescriptionFieldProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string", + title: "My input", + description: "input description" +}; + +function DescriptionFieldTemplate(props: DescriptionFieldProps) { + const { description, id } = props; + return ( +
+ Description + {description} +
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to the `DescriptionFieldTemplate`: + +- `description`: The description of the field being rendered. +- `id`: The id of the field in the hierarchy. +- `registry`: The `registry` object. + +## ErrorListTemplate + +The `ErrorListTemplate` is the template that renders the all the errors associated with the fields in the `Form`, at the top. +Each theme implements a `ErrorListTemplate` used to render its errors using components for the theme's toolkit. +If you want to customize how all the errors are rendered you can. + +```tsx +import { ErrorListProps, RJSFValidationError } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string", + title: "My input", + description: "input description" +}; + +function ErrorListTemplate(props: ErrorListProps) { + const { errors } = props; + return ( +
+ Errors +
    + {errors.map((error: RJSFValidationError, i: number) => { + return ( +
  • + {error.stack} +
  • + ); + })} +
+
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to the `ErrorListTemplate`: + +- `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`. +- `errors`: An array of all errors in this `Form`. +- `errorSchema`: The `ErrorSchema` constructed by `Form` ## FieldTemplate @@ -92,12 +455,15 @@ To take control over the inner organization of each field (each form row), you c 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 +```tsx +import { FieldTemplateProps } from "@rjsf/utils"; +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; -function CustomFieldTemplate(props) { +function CustomFieldTemplate(props: FieldTemplateProps) { const {id, classNames, label, help, required, description, errors, children} = props; return (
@@ -111,8 +477,7 @@ function CustomFieldTemplate(props) { } render(( - + ), document.getElementById("app")); ``` @@ -134,6 +499,7 @@ The following props are passed to a custom field template component: - `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. +- `hideError`: A boolean value stating if the field is hiding its errors. - `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. @@ -144,11 +510,10 @@ The following props are passed to a custom field template component: - `hideError`: A boolean value stating if the field is hiding its errors - `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. - `onChange`: The value change event handler; Can be called with a new value to change the value for this field. -- `formContext`: The `formContext` object that you passed to Form. +- `formContext`: The `formContext` object that you passed to `Form`. - `formData`: The formData for this field. - `registry`: The `registry` object. @@ -156,7 +521,10 @@ The following props are passed to a custom field template component: ## ObjectFieldTemplate -```jsx +```tsx +import { ObjectFieldTemplateProps } from "@rjsf/utils"; +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "object", title: "Object title", @@ -171,7 +539,7 @@ const schema = { } }; -function ObjectFieldTemplate(props) { +function ObjectFieldTemplate(props: ObjectFieldTemplateProps) { return (
{props.title} @@ -182,8 +550,7 @@ function ObjectFieldTemplate(props) { } render(( - + ), document.getElementById("app")); ``` @@ -195,22 +562,21 @@ const uiSchema = { }; ``` -Please see [customObject.js](https://github.com/rjsf-team/react-jsonschema-form/blob/4542cd254ffdc6dfaf55e8c9f6f17dc900d0d041/packages/playground/src/samples/customObject.js) for a better example. +Please see [customObject.js](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/playground/src/samples/customObject.js) for a better example. -The following props are passed to each `ObjectFieldTemplate`: +The following props are passed to each `ObjectFieldTemplate` as defined by the `ObjectFieldTemplateProps` in `@rjsf/utils`: -- `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. -- `onAddClick: (schema: JSONSchema7) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties) +- `properties`: An array of object representing the properties in the object. Each of the properties represent a child with properties described below. +- `onAddClick: (schema: RJSFSchema) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties) - `readonly`: A boolean value stating if the object is read-only. - `required`: A boolean value stating if the object is required. +- `hideError`: A boolean value stating if the field is hiding its errors. - `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. +- `idSchema`: An object containing the id for this object & ids for its properties. - `formData`: The form data for the object. - `formContext`: The `formContext` object that you passed to Form. - `registry`: The `registry` object. @@ -223,4 +589,243 @@ The following props are part of each element in `properties`: - `readonly`: A boolean value stating if the property is read-only. - `hidden`: A boolean value stating if the property should be hidden. -> Note: Array and object field templates are always rendered inside of the FieldTemplate. To fully customize an object field template, you may need to specify both `ui:FieldTemplate` and `ui:ObjectFieldTemplate`. +> Note: Array and object field templates are always rendered inside the FieldTemplate. To fully customize an object field template, you may need to specify both `ui:FieldTemplate` and `ui:ObjectFieldTemplate`. + +## TitleFieldTemplate + +Each theme implements a `TitleFieldTemplate` used to render the title of a field. +If you want to customize how titles are rendered you can. + +```tsx +import { TitleFieldProps } from "@rjsf/utils"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string", + title: "My input", + description: "input description" +}; + +function TitleFieldTemplate(props: TitleFieldProps) { + const { id, required, title } = props; + return ( +
+ {title} + {required && *} +
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to each `TitleFieldTemplate`: + +- `id`: The id of the field in the hierarchy. +- `title`: The title of the field being rendered. +- `uiSchema`: The uiSchema object for this field. +- `required`: A boolean value stating if the field is required +- `registry`: The `registry` object. + +## UnsupportedFieldTemplate + +The `UnsupportedField` component is used to render a field in the schema is one that is not supported by react-jsonschema-form. +If you want to customize how an unsupported field is rendered (perhaps for localization purposes) you can. + +```tsx +import { UnsupportedFieldProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "invalid" +}; + +function UnsupportedFieldTemplate(props: UnsupportedFieldProps) { + const { schema, reason } = props; + return ( +
+ +
{JSON.stringify(schema, null, 2)}
+
+ ); +} + +render(( + +), document.getElementById("app")); +``` + +The following props are passed to each `UnsupportedFieldTemplate`: + +- `schema`: The schema object for this unsupported field. +- `idSchema`: An object containing the id for this unsupported field. +- `reason`: The reason why the schema field has an unsupported type. +- `registry`: The `registry` object. + +## ButtonTemplates + +There are several buttons that are potentially rendered in the `Form`. +Each of these buttons have been customized in the themes, and can be customized by you as well. +All but one of these buttons (i.e. the `SubmitButton`) are rendered currently as icons with title text for a description. + +Each button template (except for the `SubmitButton`) accepts, as props, the standard [HTML button attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button) along with the following: + +- `iconType`: An alternative specification for the type of the icon button. +- `icon`: The name representation or actual react element implementation for the icon. + +### AddButton + +The `AddButton` is used to render an add action on a `Form` for both a new `additionalProperties` element for an object or a new element in an array. +You can customize the `AddButton` to render something other than the icon button that is provided by a theme as follows: + +```tsx +import React from "react"; +import { IconButtonProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string" +}; + +function AddButton(props: IconButtonProps) { + const { icon, iconType, ...btnProps } = props; + return ( + + ); +}; + +render(( + +), document.getElementById("app")); +``` + +### MoveDownButton + +The `MoveDownButton` is used to render a move down action on a `Form` for elements in an array. +You can customize the `MoveDownButton` to render something other than the icon button that is provided by a theme as follows: + +```tsx +import React from "react"; +import { IconButtonProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string" +}; + +function MoveDownButton(props: IconButtonProps) { + const { icon, iconType, ...btnProps } = props; + return ( + + ); +}; + +render(( + +), document.getElementById("app")); +``` + +### MoveUpButton + +The `MoveUpButton` is used to render a move up action on a `Form` for elements in an array. +You can customize the `MoveUpButton` to render something other than the icon button that is provided by a theme as follows: + +```tsx +import React from "react"; +import { IconButtonProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string" +}; + +function MoveUpButton(props: IconButtonProps) { + const { icon, iconType, ...btnProps } = props; + return ( + + ); +}; + +render(( + +), document.getElementById("app")); +``` + +### RemoveButton + +The `RemoveButton` is used to render a remove action on a `Form` for both a existing `additionalProperties` element for an object or an existing element in an array. +You can customize the `RemoveButton` to render something other than the icon button that is provided by a theme as follows: + +```tsx +import React from "react"; +import { IconButtonProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string" +}; + +function RemoveButton(props: IconButtonProps) { + const { icon, iconType, ...btnProps } = props; + return ( + + ); +}; + +render(( + +), document.getElementById("app")); +``` + +### SubmitButton + +The `SubmitButton` is already very customizable via the `UISchemaSubmitButtonOptions` capabilities in the `uiSchema` but it can also be fully customized as you see fit. +> NOTE: However you choose to implement this, making it something other than a `submit` type `button` may result in the `Form` not submitting when pressed. +> You could also choose to provide your own submit button as the [children prop](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/form-props#children) of the `Form` should you so choose. + +```tsx +import React from "react"; +import { getSubmitButtonOptions, SubmitButtonProps } from "@rjsf/utils"; +import { FormattedMessage } from "react-intl"; +import validator from '@rjsf/validator-ajv6'; + +const schema = { + type: "string" +}; + +function SubmitButton(props: SubmitButtonProps) { + const { uiSchema } = props; + const { norender } = getSubmitButtonOptions(uiSchema); + if (norender) { + return null; + } + return ( + + ); +}; + +render(( + +), document.getElementById("app")); +``` + +The following prop is passed to a `SubmitButton`: + +- `uiSchema`: The uiSchema object for this field, used to extract the `UISchemaSubmitButtonOptions`. diff --git a/docs/advanced-customization/custom-themes.md b/docs/advanced-customization/custom-themes.md index a90db5a0e9..ac2e493196 100644 --- a/docs/advanced-customization/custom-themes.md +++ b/docs/advanced-customization/custom-themes.md @@ -1,11 +1,13 @@ # Custom Themes -The `withTheme` component provides an easy way to extend the functionality of react-jsonschema-form by passing in a theme object that defines custom/overridden widgets and fields, as well as any of the other possible properties of the standard rjsf `Form` component. This theme-defining object is passed as the only parameter to the HOC (`withTheme(ThemeObj)`), and the HOC will return a themed-component which you use instead of the standard `Form` component. +The `withTheme` component provides an easy way to extend the functionality of react-jsonschema-form by passing in a theme object that defines custom/overridden widgets and fields, as well as any of the other possible properties of the standard rjsf `Form` component. +This theme-defining object is passed as the only parameter to the HOC (`withTheme(ThemeObj)`), and the HOC will return a themed-component which you use instead of the standard `Form` component. ## Usage ```jsx import React, { Component } from 'react'; +import validator from '@rjsf/validator-ajv6'; import { withTheme } from '@rjsf/core'; const theme = { widgets: {test: () => (
test
) } }; @@ -13,12 +15,17 @@ const theme = { widgets: {test: () => (
test
) } }; const ThemedForm = withTheme(theme); const Demo = () => ( - + ); ``` ## Theme object properties -The Theme object consists of the same properties as the rjsf `Form` component (such as **widgets** and **fields**). The themed-Form component merges together any theme-specific **widgets** and **fields** with the default **widgets** and **fields**. For instance, providing a single widget in **widgets** will merge this widget with all the default widgets of the rjsf `Form` component, but overrides the default if the theme's widget's name matches the default widget's name. Thus, for each default widget or field not specified/overridden, the themed-form will rely on the defaults from the rjsf `Form`. Note that you are not required to pass in either custom **widgets** or **fields** when using the custom-themed HOC component; you can make the essentially redefine the default Form by simply doing `const Form = withTheme({});`. +The Theme object consists of the same properties as the rjsf `Form` component (such as **widgets**, **fields** and **templates**). +The themed-Form component merges together any theme-specific **widgets**, **fields** and **templates** with the default **widgets**, **fields** and **templates**. +For instance, providing a single widget in **widgets** will merge this widget with all the default widgets of the rjsf `Form` component, but overrides the default if the theme's widget's name matches the default widget's name. +Thus, for each default widget or field not specified/overridden, the themed-form will rely on the defaults from the rjsf `Form`. +Note that you are not required to pass in either custom **widgets**, **fields** or **templates** when using the custom-themed HOC component; +you can essentially redefine the default Form by simply doing `const Form = withTheme({});`. ### Widgets and fields **widgets** and **fields** should be in the same format as shown [here](/advanced-customization/#custom-widgets-and-fields). @@ -39,14 +46,14 @@ const myWidgets = { myCustomWidget: MyCustomWidget }; -const ThemeObject = {widgets: myWidgets}; +const ThemeObject = { widgets: myWidgets }; export default ThemeObject; ``` -The above can be similarly done for **fields**. +The above can be similarly done for **fields** and **templates**. ### Templates -Each template should be passed directly into the theme object just as you would into the rjsf Form component. Here is an example of how to use a custom [ArrayFieldTemplate](/advanced-customization/#array-field-template) and [ErrorListTemplate](/advanced-customization/#error-list-template) in the theme object: +Each template should be passed into the theme object via the **templates** object just as you would into the rjsf Form component. Here is an example of how to use a custom [ArrayFieldTemplate](/advanced-customization/#array-field-template) and [ErrorListTemplate](/advanced-customization/#error-list-template) in the theme object: ```jsx function MyArrayFieldTemplate(props) { return ( @@ -71,8 +78,10 @@ function MyErrorListTemplate(props) { } const ThemeObject = { - ArrayFieldTemplate: MyArrayFieldTemplate, - ErrorList: MyErrorListTemplate, + templates: { + ArrayFieldTemplate: MyArrayFieldTemplate, + ErrorListTemplate: MyErrorListTemplate, + }, widgets: myWidgets }; @@ -80,10 +89,12 @@ export default ThemeObject; ``` ## Overriding other Form props -Just as the theme can override **widgets**, **fields**, any of the field templates, and set default values to properties like **showErrorList**, you can do the same with the instance of the withTheme() Form component. +Just as the theme can override **widgets**, **fields**, any of the **templates**, and set default values to properties like **showErrorList**, you can do the same with the instance of the withTheme() Form component. ```jsx const ThemeObject = { - ArrayFieldTemplate: MyArrayFieldTemplate, + templates: { + ArrayFieldTemplate: MyArrayFieldTemplate, + }, fields: myFields, showErrorList: false, widgets: myWidgets diff --git a/docs/advanced-customization/custom-widgets-fields.md b/docs/advanced-customization/custom-widgets-fields.md index 895766557e..df82520a2e 100644 --- a/docs/advanced-customization/custom-widgets-fields.md +++ b/docs/advanced-customization/custom-widgets-fields.md @@ -10,6 +10,8 @@ The API allows to specify your own custom *widget* and *field* components: 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 +import validator from '@rjsf/validator-ajv6'; + const schema = { type: "boolean", default: true @@ -32,9 +34,7 @@ const widgets = { }; render(( - + ), document.getElementById("app")); ``` @@ -97,6 +97,8 @@ You can provide your own custom widgets to a uiSchema for the following json dat - `array` ```jsx +import validator from '@rjsf/validator-ajv6'; + const schema = { type: "string" }; @@ -114,8 +116,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -125,18 +126,20 @@ The following props are passed to custom widget components: - `schema`: The JSONSchema subschema object for this widget; - `uiSchema`: The uiSchema for this widget; - `value`: The current value for this widget; -- `placeholder`: the placeholder for the field, if any; +- `placeholder`: The placeholder for the widget, if any; - `required`: The required status of this widget; -- `disabled`: `true` if the widget is disabled; -- `readonly`: `true` if the widget is read-only; -- `autofocus`: `true` if the widget should autofocus; +- `disabled`: A boolean value stating if the widget is disabled; +- `readonly`: A boolean value stating if the widget is read-only; +- `autofocus`: A boolean value stating if the widget should autofocus; +- `label`: The computed label for this widget, as a string +- `multiple`: A boolean value stating if the widget can accept multiple values; - `onChange`: The value change event handler; call it with the new value every time it changes; - `onKeyChange`: The key change event handler (only called for fields with `additionalProperties`); pass the new value every time 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; +- `onBlur`: The input blur event handler; call it with the widget id and value; +- `onFocus`: The input focus event handler; call it with the widget id and value; - `options`: A map of options passed as a prop to the component (see [Custom widget options](#custom-widget-options)). - `options.enumOptions`: For enum fields, this property contains the list of options for the enum as an array of { label, value } objects. If the enum is defined using the oneOf/anyOf syntax, the entire schema object for each option is appended onto the { schema, label, value } object. -- `formContext`: The `formContext` object that you passed to Form. +- `formContext`: The `formContext` object that you passed to `Form`. - `rawErrors`: An array of strings listing all generated error messages from encountered errors for this widget. - `registry`: A [registry](#the-registry-object) object (read next). @@ -145,6 +148,8 @@ The following props are passed to custom widget components: 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 +import validator from '@rjsf/validator-ajv6'; + const MyCustomWidget = (props) => { return ( + ), document.getElementById("app")); ``` @@ -182,6 +184,8 @@ This is useful if you expose the `uiSchema` as pure JSON, which can't carry func 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 +import validator from '@rjsf/validator-ajv6'; + const schema = { type: "string" }; @@ -207,8 +211,7 @@ const uiSchema = { // renders red on yellow input render(( - + ), document.getElementById("app")); ``` @@ -227,6 +230,8 @@ You can provide your own field components to a uiSchema for basically any json s For example, let's create and register a dumb `geo` component handling a *latitude* and a *longitude*: ```jsx +import validator from '@rjsf/validator-ajv6'; + const schema = { type: "object", required: ["lat", "lon"], @@ -272,10 +277,7 @@ const fields = {geo: GeoPosition}; // Render the form with all the properties we just defined passed // as props render(( - + ), document.getElementById("app")); ``` @@ -285,25 +287,33 @@ render(( A field component will always be passed the following props: - - `schema`: The JSON schema for this field; + - `schema`: The JSON subschema object 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). + - `required`: The required status of this field; + - `disabled`: A boolean value stating if the field is disabled; + - `readonly`: A boolean value stating if the field is read-only; + - `autofocus`: A boolean value stating if the field should autofocus; + - `name`: The unique name of the field, usually derived from the name of the property in the JSONSchema + - `onChange`: The field change event handler; called with the updated form data and an optional `ErrorSchema` + - `onBlur`: The input blur event handler; call it with the field id and value; + - `onFocus`: The input focus event handler; call it with the field id and value; ## The `registry` object -The `registry` is an object containing the registered custom fields and widgets as well as the root schema definitions. +The `registry` is an object containing the registered core, theme and custom fields and widgets as well as the root schema, form context, schema utils. - - `fields`: All fields, including [custom registered fields](#custom-field-components), if any; - - `widgets`: All widgets, including, [custom registered widgets](#custom-widget-components), if any; - - `rootSchema`: The root schema, which can contain referenced [definitions](#schema-definitions-and-references); - - `formContext`: The [formContext](#the-formcontext-object) object; - - `definitions` (deprecated since v2): Equal to `rootSchema.definitions`. + - `fields`: The set of all fields used by the `Form`. Includes fields from `core`, theme-specific fields and any [custom registered fields](#custom-field-components); + - `widgets`: The set of all widgets used by the `Form`. Includes widgets from `core`, theme-specific widgets and any [custom registered widgets](#custom-widget-components), if any; + - `rootSchema`: The root schema, as passed to the `Form`, which can contain referenced [definitions](#schema-definitions-and-references); + - `formContext`: The [formContext](#the-formcontext-object) that was passed to `Form`; + - `schemaUtils`: The current implementation of the `SchemaUtilsType` (from `@rjsf/utils`) in use by the `Form`. Used to call any of the validation-schema-based utility functions. -The registry is passed down the component tree, so you can access it from your custom field, custom widget, and `SchemaField` components. +The registry is passed down the component tree, so you can access it from your custom field, custom widget, custom template and `SchemaField` components. ### Custom SchemaField @@ -314,6 +324,7 @@ You can provide your own implementation of the `SchemaField` base React componen To proceed so, pass a `fields` object having a `SchemaField` property to your `Form` component; here's an example: ```jsx +import validator from '@rjsf/validator-ajv6'; const CustomSchemaField = function(props) { return ( @@ -333,13 +344,10 @@ const schema = { }; 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). - -NOTE: If you are using the `material-ui` theme and are considering customizing a widget or a field, checkout this [guide](material-ui/customizing-material-ui.md). diff --git a/docs/advanced-customization/internals.md b/docs/advanced-customization/internals.md index c0db02427b..ff8b84f448 100644 --- a/docs/advanced-customization/internals.md +++ b/docs/advanced-customization/internals.md @@ -4,7 +4,7 @@ Miscellaneous internals of react-jsonschema-form are listed here. ## JSON Schema supporting status -This component follows [JSON Schema](http://json-schema.org/documentation.html) specs. We currently support JSON Schema-07 by default, but we also support other JSON schema versions through the [custom schema validation](https://react-jsonschema-form.readthedocs.io/en/latest/validation/#custom-schema-validation) feature. Due to the limitation of form widgets, there are some exceptions as follows: +This component follows [JSON Schema](http://json-schema.org/documentation.html) specs. We currently support JSON Schema-07 by default, but we also support other JSON schema versions through the [custom schema validation](https://react-jsonschema-form.readthedocs.io/en/stable/validation/#custom-schema-validation) feature. Due to the limitation of form widgets, there are some exceptions as follows: * `additionalItems` keyword for arrays @@ -62,6 +62,8 @@ You can use the reference to get your `Form` component and call the `submit` met This method will dispatch the `submit` event of the form, and the function, that is passed to `onSubmit` props, will be called. ```jsx +import validator from "@rjsf/validator-ajv6"; + const onSubmit = ({formData}) => console.log("Data submitted: ", formData); let yourForm; @@ -70,8 +72,7 @@ const schema = { }; render(( - {yourForm = form;}}/> + {yourForm = form;}}/> ), document.getElementById("app")); yourForm.submit(); diff --git a/docs/advanced-customization/material-ui/customizing-material-ui.md b/docs/advanced-customization/material-ui/customizing-material-ui.md deleted file mode 100644 index 331616d736..0000000000 --- a/docs/advanced-customization/material-ui/customizing-material-ui.md +++ /dev/null @@ -1,106 +0,0 @@ -# Customizing material-ui fields and widgets - -Unlike most other themes, the `material-ui` theme supports the two distinct version of Material UI (versions 4 and 5) side-by-side. -Material UI version 4 is provided by the scoped packages under `@material-ui` and version 5 is provided by the scoped packages under `@mui`. - -The components used by `@rjsf/material-ui` for Material UI version 4 and version 5 have identical names and props. -As a result, all of the `fields` and `widgets` provided by the theme are identical as well. -The trick to making the two versions function side-by-side, was done by creating a React context, `MuiComponentContext`, that provides the appropriate set of components used by theme, for the particular scoped package. - -In addition to this context, a custom hook, `useMuiComponent()`, is provided to allow quick access to that component set. - -## Example of a custom widget for `@rjsf/material-ui` - -Here is an update to the `MyCustomWidget` for the `material-ui` theme - -```jsx -const schema = { - type: "string" -}; - -import { useMuiComponent } from '@rjsf/material-ui/v4'; - -function MyCustomWidget(props) { - const { options, ...otherProps } = props; - const { color, backgroundColor } = options; - const { TextInput } = useMuiComponent(); - return ; -} - -MyCustomWidget.defaultProps = { - options: { - color: "red" - } -}; - -const uiSchema = { - "ui:widget": MyCustomWidget, - "ui:options": { - backgroundColor: "yellow" - } -}; - -// renders red on yellow input -render(( - -), document.getElementById("app")); -``` - -## Example of a custom field for `@rjsf/material-ui` - -Here is an update to the `GeoPosition` for the `material-ui` theme - -```jsx -const schema = { - type: "object", - required: ["lat", "lon"], - properties: { - lat: { type: "number"}, - lon: { type: "number" } - } -}; - -import { useMuiComponent } from '@rjsf/material-ui/v4'; - -// Define a custom component for handling the root position object -function GeoPosition(props) { - const { lat, lon } = props.formData; - const { Box, TextInput } = useMuiComponent(); - - const onChangeLat = (event) => { - const { target: { value } } = event; - const newData = { ...props.formData, lat: value }; - props.onChange(newData); - }; - - const onChangeLon = (event) => { - const { target: { value } } = event; - const newData = { ...props.formData, lon: value }; - props.onChange(newData); - }; - - 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")); -``` diff --git a/docs/api-reference/form-props.md b/docs/api-reference/form-props.md index 37609169c7..0fac4cc2af 100644 --- a/docs/api-reference/form-props.md +++ b/docs/api-reference/form-props.md @@ -10,14 +10,6 @@ The value of this prop will be passed to the `action` [HTML attribute on the for Note that this just renders the `action` attribute in the HTML markup. There is no real network request being sent to this `action` on submit. Instead, react-jsonschema-form catches the submit event with `event.preventDefault()` and then calls the [`onSubmit`](#onSubmit) function, where you could send a request programmatically with `fetch` or similar. -## additionalMetaSchemas - -This prop allows you to validate the form data against another JSON Schema meta schema, for example, JSON Schema draft-04. See [Validation](../usage/validation.md) for more information. - -## ArrayFieldTemplate - -React component used to customize how alls arrays are rendered on the form. See [Custom Templates](../advanced-customization/custom-templates.md) for more information. - ## autoComplete The value of this prop will be passed to the `autocomplete` [HTML attribute on the form](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-autocomplete). @@ -35,12 +27,14 @@ The value of this prop will be passed to the `class` [HTML attribute on the form You can provide custom buttons to your form via the `Form` component's `children`. Otherwise a default submit button will be rendered. ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; render(( - +
@@ -51,43 +45,47 @@ render(( > **Warning:** There needs to be a button or an input with `type="submit"` to trigger the form submission (and then the form validation). -## customFormats +## customValidate -This prop allows you to define custom formats for validation. See [Validation](../usage/validation.md) for more information. +Formerly the `validate` prop. +The `customValidate` prop requires a function that specifies custom validation rules for the form. +See [Validation](../usage/validation.md) for more information. ## disabled 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 +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; render(( - + ), document.getElementById("app")); ``` -If you just want to disable some of the fields, see the `ui:disabled` parameter in `uiSchema`. +If you just want to disable some fields, see the `ui:disabled` parameter in `uiSchema`. ## readonly It's possible to make the whole form read-only by setting the `readonly` prop. The `readonly` prop is then forwarded down to each field of the form. ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; render(( - + ), document.getElementById("app")); ``` -If you just want to make some of the fields read-only, see the `ui:readonly` parameter in `uiSchema`. +If you just want to make some fields read-only, see the `ui:readonly` parameter in `uiSchema`. ## enctype @@ -105,10 +103,6 @@ You can pass a React component to this prop to customize how form errors are dis Dictionary of registered fields in the form. See [Custom Widgets and Fields](../advanced-customization/custom-widgets-fields.md) for more information. -## FieldTemplate - -React component used to customize each field of the form. See [Custom Templates](../advanced-customization/custom-templates.md) for more information. - ## formContext You can provide a `formContext` object to the Form, which is passed down to all fields and widgets. Useful for implementing context aware fields and widgets. @@ -127,13 +121,14 @@ The value of this prop will be passed to the `id` [HTML attribute on the form](h To avoid collisions with existing ids in the DOM, it is possible to change the prefix used for ids (the default is `root`). ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; render(( - + ), document.getElementById("app")); ``` @@ -144,6 +139,8 @@ This will render `` instead of ` + ), document.getElementById("app")); ``` @@ -181,16 +177,12 @@ The value of this prop will be passed to the `name` [HTML attribute on the form] ## noHtml5Validate -If set to true, turns off HTML5 validation on the form. Set to `false` on default. +If set to true, turns off HTML5 validation on the form. Set to `false` by default. ## noValidate If set to true, turns off all validation. Set to `false` by default. -## ObjectFieldTemplate - -React component used to customize how all objects are rendered in the form. See [Custom Templates](../advanced-customization/custom-templates.md) for more information. - ## omitExtraData If set to true, then extra form data values that are not in any form field will be removed whenever `onSubmit` is called. Set to `false` by default. @@ -208,14 +200,15 @@ If you plan on being notified every time the form data are updated, you can pass To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of encountered errors: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; const onError = (errors) => console.log("I have", errors.length, "errors to fix"); render(( - + ), document.getElementById("app")); ``` @@ -228,14 +221,15 @@ Sometimes you may want to trigger events or modify external state when a field h 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: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "string" }; const onSubmit = ({formData}, e) => console.log("Data submitted: ", formData); render(( - + ), document.getElementById("app")); ``` @@ -243,7 +237,7 @@ render(( ## schema -Form schema. We support JSON schema draft-07 by default. See [Schema Reference](https://json-schema.org/draft-07/json-schema-release-notes.html) for more information. +**Required**! Form schema. We support JSON schema draft-07 by default. See [Schema Reference](https://json-schema.org/draft-07/json-schema-release-notes.html) for more information. ## showErrorList @@ -256,17 +250,18 @@ It's possible to change the default `form` tag name to a different HTML tag, whi ```jsx ``` You can also provide a class/function component. - ```jsx const CustomForm = props => // ... ``` @@ -274,6 +269,10 @@ const CustomForm = props => The value of this prop will be passed to the `target` [HTML attribute on the form](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target). +## templates + +Dictionary of registered templates in the form. See [Custom Templates](../advanced-customization/custom-templates.md) for more information. + ## transformErrors A function can be passed to this prop in order to make modifications to the default errors resulting from JSON Schema validation. See [Validation](../usage/validation.md) for more information. @@ -282,9 +281,10 @@ A function can be passed to this prop in order to make modifications to the defa Form uiSchema. See [uiSchema Reference](uiSchema.md) for more information. -## validate +## validator -The `validate` prop requires a function that specifies custom validation rules for the form. See [Validation](../usage/validation.md) for more information. +**Required**! An implementation of the `ValidatorType` interface that is needed for form validation to work. +`@rjsf/validator-ajv6` exports the implementation of this interface from RJSF version 4. ## widgets diff --git a/docs/api-reference/themes/semantic-ui/uiSchema.md b/docs/api-reference/themes/semantic-ui/uiSchema.md index bc3f8fb257..858583d04a 100644 --- a/docs/api-reference/themes/semantic-ui/uiSchema.md +++ b/docs/api-reference/themes/semantic-ui/uiSchema.md @@ -73,6 +73,8 @@ horizontalButtons: horizontal buttons instead of the default vertical ``` ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -90,7 +92,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` diff --git a/docs/api-reference/uiSchema.md b/docs/api-reference/uiSchema.md index 98a272a310..219826ae8a 100644 --- a/docs/api-reference/uiSchema.md +++ b/docs/api-reference/uiSchema.md @@ -5,22 +5,23 @@ A UI schema is basically an object literal providing information on **how** the The uiSchema object follows the tree structure of the form field hierarchy, and defines how each property should be rendered. -Note that every property within uiSchema can be rendered in one of two ways: `{"ui:options": {[property]: [value]}}`, or `{"ui:[property]": value}`. +Note that almost every property within uiSchema can be rendered in one of two ways: `{"ui:options": {[property]: [value]}}`, or `{"ui:[property]": value}`. -In other words, the following uiSchemas are equivalent: +In other words, the following `uiSchema`s are equivalent: ```json { "ui:title": "Title", "ui:description": "Description", + "ui:classNames": "my-class", "ui:submitButtonOptions": { "props": { "disabled": false, "className": "btn btn-info", }, - "norender": false, - "submitText": "Submit" - }, + "norender": false, + "submitText": "Submit" + } } ``` @@ -29,6 +30,7 @@ In other words, the following uiSchemas are equivalent: "ui:options": { "title": "Title", "description": "Description", + "classNames": "my-class", "submitButtonOptions": { "props": { "disabled": false, @@ -36,19 +38,62 @@ In other words, the following uiSchemas are equivalent: }, "norender": false, "submitText": "Submit" - }, + } } } ``` -## classNames +For a full list of what is supported in the `uiSchema` see the `UiSchema` type in [@rjsf/utils/types.ts](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/utils/src/types.ts). +Be sure to pay attention to the hierarchical intersection to these other types: `UIOptionsBaseType` and `TemplatesType`. + +## Exceptions to the equivalence +There are 3 properties that exist in a `UiSchema` that will not be found in an inner `ui:options` object. + +### ui:rootFieldId + +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" +}; +``` + +This will make all widgets have an id prefixed with `myform`. + +### ui:field + +The `ui:field` property overrides the `Field` implementation used for rendering any field in the form's hierarchy. +Specify either the name of a field that is used to look up an implementation from the `fields` list or an actual one-off `Field` component implementation itself. + +See [Custom Widgets and Fields](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/custom-widgets-fields#custom-field-components) for more information about how to use this property. + +### ui:options + +The `ui:options` property cannot be nested inside itself and thus is the last exception. + +## ui:XXX or ui:options.XXX + +All the properties that follow can be specified in the `uiSchema` in either of the two equivalent ways. + +NOTE: The properties specific to array items can be found [here](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/arrays#array-item-uiSchema-options) + +### widget + +The `ui:field` property overrides the `Widget` implementation used for rendering any field in the form's hierarchy. +Specify either the name of a widget that is used to look up an implementation from the `widgets` list or an actual one-off `Widget` component implementation itself. + +See [Custom Widgets and Fields](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/custom-widgets-fields) for more information about how to use this property. + +### classNames The uiSchema object accepts a `classNames` property for each field of the schema: ```jsx const uiSchema = { title: { - classNames: "task-title foo-bar" + "ui:classNames": "task-title foo-bar" } }; ``` @@ -64,7 +109,19 @@ Will result in:
``` -## autofocus +### autocomplete + +If you want to mark a text input, select or textarea input to use the HTML autocomplete feature, set the `ui:autocomplete` uiSchema directive to a valid [HTML autocomplete value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values). + +```js +const schema = {type: "string"}; +const uiSchema = { + "ui:widget": "textarea", + "ui:autocomplete": "on" +} +``` + +### autofocus If you want to automatically focus on a text input or textarea input, set the `ui:autofocus` uiSchema directive to `true`. @@ -76,7 +133,7 @@ const uiSchema = { } ``` -## description +### description Sometimes it's convenient to change the description of a field. This is the purpose of the `ui:description` uiSchema directive: @@ -88,13 +145,17 @@ const uiSchema = { }; ``` -## disabled +### disabled The `ui:disabled` uiSchema directive will disable all child widgets from a given field. > 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. -## enumDisabled +### emptyValue + +The `ui:emptyValue` uiSchema directive provides the default value to use when an input for a field is empty + +### enumDisabled To disable an option, use the `enumDisabled` property in uiSchema. @@ -109,7 +170,7 @@ const uiSchema={ } ``` -## help +### help 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: @@ -125,7 +186,7 @@ const uiSchema = { 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). -## hideError +### hideError The `ui:hideError` uiSchema directive will, if set to `true`, hide the default error display for the given field AND all of its child fields in the hierarchy. @@ -133,7 +194,7 @@ If you need to enable the default error display of a child in the hierarchy afte This is useful when you have a custom field or widget that utilizes either the `rawErrors` or the `errorSchema` to manipulate and/or show the error(s) for the field/widget itself. -## inputType +### inputType To change the input type (for example, `tel` or `email`) you can specify the `inputType` in the `ui:options` uiSchema directive. @@ -146,11 +207,13 @@ const uiSchema = { }; ``` -## label +### label Field labels are rendered by default. Labels may be omitted by setting the `label` option to `false` in the `ui:options` uiSchema directive. ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = {type: "string"}; const uiSchema = { "ui:options": { @@ -159,79 +222,73 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` -## order +### order This property allows you to reorder the properties that are shown for a particular object. See [Objects](../usage/objects.md) for more information. -## placeholder +### placeholder You can add placeholder text to an input by using the `ui:placeholder` uiSchema directive: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = {type: "string", format: "uri"}; const uiSchema = { "ui:placeholder": "http://" }; render(( - + ), document.getElementById("app")); ``` 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 +import validator from "@rjsf/validator-ajv6"; + const schema = {type: "string", enum: ["First", "Second"]}; const uiSchema = { "ui:placeholder": "Choose an option" }; render(( - + ), document.getElementById("app")); ``` -## readonly +### readonly The `ui:readonly` uiSchema directive will mark all child widgets from a given field as read-only. This is equivalent to setting the `readOnly` property in the schema. > 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. -## rootFieldId - -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" -}; -``` - -This will make all widgets have an id prefixed with `myform`. - -## rows +### rows You can set the initial height of a textarea widget by specifying `rows` option. -```js +```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = {type: "string"}; const uiSchema = { - "ui:widget": "textarea", "ui:options": { + widget: "textarea", rows: 15 } }; render(( - + ), document.getElementById("app")); ``` -## title +### title Sometimes it's convenient to change a field's title. This is the purpose of the `ui:title` uiSchema directive: @@ -243,30 +300,29 @@ const uiSchema = { }; ``` -## submitButtonOptions +### submitButtonOptions Sometimes it's convenient to change the behavior of the submit button for the form. This is the purpose of the `ui:submitButtonOptions` uiSchema directive: You can pass any other prop to the submit button if you want, by default, this library will set the following options / props mentioned below for all submit buttons: -### `norender` option +#### `norender` option You can set this property to `true` to remove the submit button completely from the form. Nice option, if the form is just for viewing purposes. -### `submitText` option +#### `submitText` option You can use this option to change the text of the submit button. Set to "Submit" by default. -### `props` section +#### `props` section You can pass any other prop to the submit button if you want, via this section. - -#### `disabled` prop +##### `disabled` prop You can use this option to disable the submit button. -#### `className` prop +##### `className` prop You can use this option to specify a class name for the submit button. @@ -282,6 +338,12 @@ const uiSchema = { } }; ``` + +## `duplicateKeySuffixSeparator` option + +When using `additionalProperties`, key collision is prevented by appending a unique integer suffix to the duplicate key. For example, when you add a key named `myKey` to a form where `myKey` is already defined, then your new key will become `myKey-1`. +You can use `ui:duplicateKeySuffixSeparator` to override the default separator, `"-"` with a string of your choice. + ## Theme Options [Semantic UI](themes/semantic-ui/uiSchema.md) [Chakra UI](themes/chakra-ui/uiSchema.md) diff --git a/docs/api-reference/utility-functions.md b/docs/api-reference/utility-functions.md new file mode 100644 index 0000000000..371b82de4c --- /dev/null +++ b/docs/api-reference/utility-functions.md @@ -0,0 +1,545 @@ +# RJSF utility functions, constants and types + +In version 5, the utility functions from `@rjsf/core/utils` were refactored into their own library called `@rjsf/utils`. +These utility functions are separated into two distinct groups. +The first, larger, group are the [functions](#non-validator-utility-functions) that do NOT require a `ValidatorType` interface be provided as one of their parameters. +The second, smaller, group are the [functions](#validator-based-utility-functions) that DO require a `ValidatorType` interface be provided as a parameter. +There is also a helper [function](#schema-utils-creation-function) used to create a `SchemaUtilsType` implementation from a `ValidatorType` implementation and `rootSchema` object. + +## Constants +The `@rjsf/utils` package exports a set of constants that represent all the keys into various elements of a RJSFSchema or UiSchema that are used by the various utility functions. +In addition to those keys, there is the special `ADDITIONAL_PROPERTY_FLAG` flag that is added to a schema under certain conditions by the `retrieveSchema()` utility. + +These constants can be found on Github [here](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/utils/src/constants.ts). + +## Types +Additionally, the Typescript types used by the utility functions represent nearly all the types used by RJSF. +Those types are exported for use by `@rjsf/core` and all the themes, as well as any customizations you may build. + +These types can be found on Github [here](https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/utils/src/types.ts). + +## Non-Validator utility functions + +### allowAdditionalItems() +Checks the schema to see if it is allowing additional items, by verifying that `schema.additionalItems` is an object. +The user is warned in the console if `schema.additionalItems` has the value `true`. + +#### Parameters +- schema: RJSFSchema - The schema object to check + +#### Returns +- boolean: True if additional items is allowed, otherwise false + +### asNumber() +Attempts to convert the string into a number. If an empty string is provided, then `undefined` is returned. +If a `null` is provided, it is returned. +If the string ends in a `.` then the string is returned because the user may be in the middle of typing a float number. +If a number ends in a pattern like `.0`, `.20`, `.030`, string is returned because the user may be typing number that will end in a non-zero digit. +Otherwise, the string is wrapped by `Number()` and if that result is not `NaN`, that number will be returned, otherwise the string `value` will be. + +#### Parameters +- value: string | null - The string or null value to convert to a number + +#### Returns +- undefined | null | string | number: The `value` converted to a number when appropriate, otherwise the `value` + +### canExpand() + +Checks whether the field described by `schema`, having the `uiSchema` and `formData` supports expanding. +The UI for the field can expand if it has additional properties, is not forced as non-expandable by the `uiSchema` and the `formData` object doesn't already have `schema.maxProperties` elements. + +#### Parameters +- schema: RJSFSchema - The schema for the field that is being checked +- [uiSchema={}]: UiSchema - The uiSchema for the field +- [formData]: T - The formData for the field + +#### Returns +- boolean: True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit + +### dataURItoBlob() +Given the `FileReader.readAsDataURL()` based `dataURI` extracts that data into an actual Blob along with the name +of that Blob if provided in the URL. If no name is provided, then the name falls back to `unknown`. + +#### Parameters +- dataURI: string - The `DataUrl` potentially containing name and raw data to be converted to a Blob + +#### Returns +- { blob: Blob, name: string }: An object containing a Blob and its name, extracted from the URI + +### deepEquals() +Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that assumes all functions are equivalent. + +#### Parameters +- a: any - The first element to compare +- b: any - The second element to compare + +#### Returns +- boolean: True if the `a` and `b` are deeply equal, false otherwise + +### findSchemaDefinition() +Given the name of a `$ref` from within a schema, using the `rootSchema`, look up and return the sub-schema using the path provided by that reference. +If `#` is not the first character of the reference, or the path does not exist in the schema, then throw an Error. +Otherwise return the sub-schema. Also deals with nested `$ref`s in the sub-schema. + +#### Parameters +- $ref: string - The ref string for which the schema definition is desired +- [rootSchema={}]: RJSFSchema - The root schema in which to search for the definition + +#### Returns +- RJSFSchema: The sub-schema within the `rootSchema` which matches the `$ref` if it exists + +#### Throws +- Error indicating that no schema for that reference exists + +### getInputProps() +Using the `schema`, `defaultType` and `options`, extract out the props for the element that make sense. + +#### Parameters +- schema: RJSFSchema - The schema for the field provided by the widget +- [defaultType]: string - The default type, if any, for the field provided by the widget +- [options={}]: UIOptionsType - The UI Options for the field provided by the widget +- [autoDefaultStepAny=true]: boolean - Determines whether to auto-default step=any when the type is number and no step +#### Returns +- InputPropsType: The extracted `InputPropsType` object + +### getSchemaType() +Gets the type of a given `schema`. +If the type is not explicitly defined, then an attempt is made to infer it from other elements of the schema as follows: +- schema.const: Returns the `guessType()` of that value +- schema.enum: Returns `string` +- schema.properties: Returns `object` +- schema.additionalProperties: Returns `object` +- type is an array with a length of 2 and one type is 'null': Returns the other type + +#### Parameters +- schema: RJSFSchema - The schema for which to get the type + +#### Returns +- string | string[] | undefined: The type of the schema + +### getSubmitButtonOptions() +Extracts any `ui:submitButtonOptions` from the `uiSchema` and merges them onto the `DEFAULT_OPTIONS` + +#### Parameters +- [uiSchema={}]: UiSchema - the UI Schema from which to extract submit button props + +#### Returns +- UISchemaSubmitButtonOptions: The merging of the `DEFAULT_OPTIONS` with any custom ones + +### getUiOptions() +Get all passed options from ui:options, and ui:, returning them in an object with the `ui:` stripped off. + +#### Parameters +- [uiSchema={}]: UiSchema - The UI Schema from which to get any `ui:xxx` options + +#### Returns +- UIOptionsType: An object containing all of the `ui:xxx` options with the stripped off + +### getTemplate, T = any, F = any>() +Returns the template with the given `name` from either the `uiSchema` if it is defined or from the `registry` +otherwise. NOTE, since `ButtonTemplates` are not overridden in `uiSchema` only those in the `registry` are returned. + +#### Parameters +- name: Name - The name of the template to fetch, restricted to the keys of `TemplatesType` +- registry: Registry - The `Registry` from which to read the template +- [uiOptions={}]: UIOptionsType - The `UIOptionsType` from which to read an alternate template + +#### Returns +- TemplatesType[Name] - The template from either the `uiSchema` or `registry` for the `name` + +### getWidget() +Given a schema representing a field to render and either the name or actual `Widget` implementation, returns the +React component that is used to render the widget. If the `widget` is already a React component, then it is wrapped +with a `MergedWidget`. Otherwise an attempt is made to look up the widget inside of the `registeredWidgets` map based +on the schema type and `widget` name. If no widget component can be found an `Error` is thrown. + +#### Parameters +- schema: RJSFSchema - The schema for the field +- widget: Widget | string - Either the name of the widget OR a `Widget` implementation to use +- [registeredWidgets={}]: RegistryWidgetsType - A registry of widget name to `Widget` implementation + +#### Returns +- Widget: The `Widget` component to use + +#### Throws +- An error if there is no `Widget` component that can be returned + +### guessType() +Given a specific `value` attempts to guess the type of a schema element. In the case where we have to implicitly +create a schema, it is useful to know what type to use based on the data we are defining. + +#### Parameters +- value: any - The value from which to guess the type + +#### Returns +- string: The best guess for the object type + +### hasWidget() +Detects whether the `widget` exists for the `schema` with the associated `registryWidgets` and returns true if it does, or false if it doesn't. + +#### Parameters +- schema: RJSFSchema - The schema for the field +- widget: Widget | string - Either the name of the widget OR a `Widget` implementation to use +- [registeredWidgets={}]: RegistryWidgetsType - A registry of widget name to `Widget` implementation + +#### Returns +- boolean: True if the widget exists, false otherwise + +### isConstant() +This function checks if the given `schema` matches a single constant value. +This happens when either the schema has an `enum` array with a single value or there is a `const` defined. + +#### Parameters +- schema: RJSFSchema - The schema for a field + +#### Returns +- boolean: True if the `schema` has a single constant value, false otherwise + +### isCustomWidget() +Checks to see if the `uiSchema` contains the `widget` field and that the widget is not `hidden` + +#### Parameters +- uiSchema: UiSchema - The UI Schema from which to detect if it is customized + +#### Returns +- boolean: True if the `uiSchema` describes a custom widget, false otherwise + +### isFixedItems() +Detects whether the given `schema` contains fixed items. +This is the case when `schema.items` is a non-empty array that only contains objects. + +#### Parameters +- schema: RJSFSchema - The schema in which to check for fixed items + +#### Returns +- boolean: True if there are fixed items in the schema, false otherwise + +### isObject() +Determines whether a `thing` is an object for the purposes of RSJF. +In this case, `thing` is an object if it has the type `object` but is NOT null, an array or a File. + +#### Parameters +- thing: any - The thing to check to see whether it is an object + +#### Returns +- boolean: True if it is a non-null, non-array, non-File object + +### localToUTC() +Converts a local Date string into a UTC date string + +#### Parameters +- dateString: string - The string representation of a date as accepted by the `Date()` constructor + +#### Returns +- string | undefined: A UTC date string if `dateString` is truthy, otherwise undefined + +### mergeDefaultsWithFormData() +Merges the `defaults` object of type `T` into the `formData` of type `T` + +When merging defaults and form data, we want to merge in this specific way: +- objects are deeply merged +- arrays are merged in such a way that: + - when the array is set in form data, only array entries set in form data are deeply merged; additional entries from the defaults are ignored + - when the array is not set in form data, the default is copied over +- scalars are overwritten/set by form data + +#### Parameters +- defaults: T - The defaults to merge +- formData: T - The form data into which the defaults will be merged + +#### Returns +- T: The resulting merged form data with defaults + +### mergeObjects() +Recursively merge deeply nested objects. + +#### Parameters +- obj1: GenericObjectType - The first object to merge +- obj2: GenericObjectType - The second object to merge +- [concatArrays=false]: boolean - Optional flag that, when true, will cause arrays to be concatenated + +#### Returns +@returns - A new object that is the merge of the two given objects + +### mergeSchemas() +Recursively merge deeply nested schemas. +The difference between mergeSchemas and mergeObjects is that mergeSchemas only concats arrays for values under the 'required' keyword, and when it does, it doesn't include duplicate values. + +#### Parameters +- obj1: GenericObjectType - The first object to merge +- obj2: GenericObjectType - The second object to merge + +#### Returns +- GenericObjectType: The merged schema object + +### optionsList() +Gets the list of options from the schema. If the schema has an enum list, then those enum values are returned. +The labels for the options will be extracted from the non-standard `enumNames` if it exists otherwise will be the same as the `value`. +If the schema has a `oneOf` or `anyOf`, then the value is the list of `const` values from the schema and the label is either the `schema.title` or the value. + +NOTE: `enumNames` is deprecated and may be removed in a future major version of RJSF. + +#### Parameters +- schema: RJSFSchema - The schema from which to extract the options list + +#### Returns +- { schema?: RJSFSchema, label: string, value: any }: The list of options from the schema + +### orderProperties() +Given a list of `properties` and an `order` list, returns a list that contains the `properties` ordered correctly. +If `order` is not an array, then the untouched `properties` list is returned. +Otherwise `properties` is ordered per the `order` list. +If `order` contains a '*' then any `properties` that are not mentioned explicity in `order` will be places in the location of the `*`. + +#### Parameters +- properties: string[] - The list of property keys to be ordered +- order: string[] - An array of property keys to be ordered first, with an optional '*' property + +#### Returns +- string[]: A list with the `properties` ordered + +#### Throws +- Error when the properties cannot be ordered correctly + +### pad() +Returns a string representation of the `num` that is padded with leading "0"s if necessary + +#### Parameters +- num: number - The number to pad +- width: number - The width of the string at which no lead padding is necessary + +#### Returns +- string: The number converted to a string with leading zero padding if the number of digits is less than `width` + +### parseDateString() +Parses the `dateString` into a `DateObject`, including the time information when `includeTime` is true + +#### Parameters +- dateString: string - The date string to parse into a DateObject +- [includeTime=true]: boolean - Optional flag, if false, will not include the time data into the object + +#### Returns +- DateObject: The date string converted to a `DateObject` + +#### Throws +- Error when the date cannot be parsed from the string + +### processSelectValue() +Returns the real value for a select widget due to a silly limitation in the DOM which causes option change event values to always be retrieved as strings. +Uses the `schema` to help determine the value's true type. +If the value is an empty string, then the `emptyValue` from the `options` is returned, falling back to undefined. + +#### Parameters +- schema: RJSFSchema - The schema to used to determine the value's true type +- [value]: any - The value to convert +- [options]: UIOptionsType - The UIOptionsType from which to potentially extract the `emptyValue` + +#### Returns +- string | boolean | number | string[] | boolean[] | number[] | undefined: The `value` converted to the proper type + +### rangeSpec() +Extracts the range spec information `{ step?: number, min?: number, max?: number }` that can be spread onto an HTML input from the range analog in the schema `{ multipleOf?: number, minimum?: number, maximum?: number }`. + +#### Parameters +- schema: RJSFSchema - The schema from which to extract the range spec + +#### Returns +- RangeSpecType: A range specification from the schema + +### schemaRequiresTrueValue() +Check to see if a `schema` specifies that a value must be true. This happens when: +- `schema.const` is truthy +- `schema.enum` == `[true]` +- `schema.anyOf` or `schema.oneOf` has a single value which recursively returns true +- `schema.allOf` has at least one value which recursively returns true + +#### Parameters +- schema: RJSFSchema - The schema to check + +#### Returns +- boolean: True if the schema specifies a value that must be true, false otherwise + +### shouldRender() +Determines whether the given `component` should be rerendered by comparing its current set of props and state against the next set. +If either of those two sets are not the same, then the component should be rerendered. + +#### Parameters +- component: React.Component - A React component being checked +- nextProps: any - The next set of props against which to check +- nextState: any - The next set of state against which to check + +#### Returns +- True if boolean: the component should be re-rendered, false otherwise + +### toConstant() +Returns the constant value from the schema when it is either a single value enum or has a const key. +Otherwise throws an error. + +#### Parameters +- schema: RJSFSchema - The schema from which to obtain the constant value + +#### Returns +- string | number | boolean: The constant value for the schema + +#### Throws +- Error when the schema does not have a constant value + +### toDateString() +Returns a UTC date string for the given `dateObject`. +If `time` is false, then the time portion of the string is removed. + +#### Parameters +- dateObject: DateObject - The `DateObject` to convert to a date string +- [time=true]: boolean - Optional flag used to remove the time portion of the date string if false + +#### Returns +- string: The UTC date string + +### utcToLocal() +Converts a UTC date string into a local Date format + +#### Parameters +- jsonDate: string - A UTC date string + +#### Returns +- string: An empty string when `jsonDate` is falsey, otherwise a date string in local format + +## Validator-based utility functions + +### getDefaultFormState() +Returns the superset of `formData` that includes the given set updated to include any missing fields that have computed to have defaults provided in the `schema`. + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- theSchema: RJSFSchema - The schema for which the default state is desired +- [formData]: T - The current formData, if any, onto which to provide any missing defaults +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s +- [includeUndefinedValues=false]: boolean - Optional flag, if true, cause undefined values to be added as defaults + +#### Returns +- T: The resulting `formData` with all the defaults provided + +### getDisplayLabel() +Determines whether the combination of `schema` and `uiSchema` properties indicates that the label for the `schema` should be displayed in a UI. + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- schema: RJSFSchema - The schema for which the display label flag is desired +- [uiSchema={}]: UiSchema - The UI schema from which to derive potentially displayable information +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s + +#### Returns +- boolean: True if the label should be displayed or false if it should not + +### getMatchingOption() +Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data. + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- formData: T | undefined - The current formData, if any, used to figure out a match +- options: RJSFSchema[] - The list of options to find a matching options from +- rootSchema: RJSFSchema - The root schema, used to primarily to look up `$ref`s + +#### Returns +- number: The index of the matched option or 0 if none is available + +### isFilesArray() +Checks to see if the `schema` and `uiSchema` combination represents an array of files + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- schema: RJSFSchema - The schema for which check for array of files flag is desired +- [uiSchema={}]: UiSchema - The UI schema from which to check the widget +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s + +#### Returns +- boolean: True if schema/uiSchema contains an array of files, otherwise false + +### isMultiSelect() +Checks to see if the `schema` combination represents a multi-select + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- schema: RJSFSchema - The schema for which check for a multi-select flag is desired +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s + +#### Returns +- boolean: True if schema contains a multi-select, otherwise false + +### isSelect() +Checks to see if the `schema` combination represents a select + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- theSchema: RJSFSchema - The schema for which check for a select flag is desired +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s + +#### Returns +- boolean: True if schema contains a select, otherwise false + +### mergeValidationData() +Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in the two `ErrorSchema`s and then appending the error list from the `additionalErrorSchema` obtained by calling `validator.toErrorList()` onto the `errors` in the `validationData`. +If no `additionalErrorSchema` is passed, then `validationData` is returned. + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used to convert an ErrorSchema to a list of errors +- validationData: ValidationData - The current `ValidationData` into which to merge the additional errors +- [additionalErrorSchema]: ErrorSchema - The additional set of errors in an `ErrorSchema` + +#### Returns +- ValidationData: The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided. + +### retrieveSchema() +Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies +resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the +potentially recursive resolution. + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs +- schema: RJSFSchema - The schema for which retrieving a schema is desired +- [rootSchema={}]: RJSFSchema - The root schema that will be forwarded to all the APIs +- [rawFormData]: T - The current formData, if any, to assist retrieving a schema + +#### Returns +- RJSFSchema: The schema having its conditions, additional properties, references and dependencies resolved + +### toIdSchema() +Generates an `IdSchema` object for the `schema`, recursively + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- schema: RJSFSchema - The schema for which the `IdSchema` is desired +- [id]: string | null - The base id for the schema +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s +- [formData]: T - The current formData, if any, to assist retrieving a schema +- [idPrefix='root']: string - The prefix to use for the id +- [idSeparator='_']: string - The separator to use for the path segments in the id + +#### Returns +- IDSchema: The `IdSchema` object for the `schema` + +### toPathSchema() +Generates an `PathSchema` object for the `schema`, recursively + +#### Parameters +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- schema: RJSFSchema - The schema for which the `PathSchema` is desired +- [name='']: string - The base name for the schema +- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s +- [formData]: T - The current formData, if any, to assist retrieving a schema + +#### Returns +- PathSchema - The `PathSchema` object for the `schema` + +## Schema utils creation function + +### createSchemaUtils() +Creates a `SchemaUtilsType` interface that is based around the given `validator` and `rootSchema` parameters. +The resulting interface implementation will forward the `validator` and `rootSchema` to all the wrapped APIs. + +#### Parameters +- validator: ValidatorType - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs +- rootSchema: RJSFSchema - The root schema that will be forwarded to all the APIs + +#### Returns +- SchemaUtilsType - An implementation of a `SchemaUtilsType` interface diff --git a/docs/index.md b/docs/index.md index 0229110f35..47982c9e3f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,15 +16,22 @@ react-jsonschema-form also comes with tools such as `uiSchema` and other form pr ## Installation -First install the dependency from npm: +First install the dependencies from npm: ```bash -$ npm install @rjsf/core --save +$ npm install @rjsf/core @rjsf/utils --save ``` +As of version 5, you will also need to select and install a validator implementation (such as `@rjsf/validator-ajv6`): + +```bash +$ npm install @rjsf/validator-ajv6 --save +```` + Then import the dependency as follows: ```js +import validator from "@rjsf/validator-ajv6"; import Form from "@rjsf/core"; ``` @@ -51,6 +58,8 @@ const {default: Form} = JSONSchemaForm; ## Usage ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { title: "Todo", type: "object", @@ -65,6 +74,7 @@ const log = (type) => console.log.bind(console, type); render(( diff --git a/docs/quickstart.md b/docs/quickstart.md index 8e768da844..fec068831c 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,25 +1,30 @@ # Quickstart Let's walk through setup of a form after installing the dependency properly. +NOTE: As of version 5, the `Form` now requires you to provide a `validator` implementation. We recommend the one from `@rjsf/validator-ajv6`. ## Form schema First, specify a schema using the [JSON Schema specification](https://json-schema.org/). The below schema renders a single string field: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { title: "Test form", type: "string" }; render(( - + ), document.getElementById("app")); ``` You can also render an object with multiple fields with the below schema: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { title: "Test form", type: "object", @@ -34,7 +39,7 @@ const schema = { }; render(( - + ), document.getElementById("app")); ``` @@ -47,6 +52,8 @@ attribute of the uiSchema to add a custom CSS class name to the form: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { title: "Test form", type: "string" @@ -57,7 +64,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -66,6 +73,8 @@ uiSchema should be `{key: value}`, where `key` is the property key and `value` i object with the uiSchema configuration for that particular property. For example: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { title: "Test form", type: "object", @@ -89,7 +98,7 @@ const uiSchema = { } render(( - + ), document.getElementById("app")); ``` @@ -98,6 +107,8 @@ render(( Often you'll want to prefill a form with existing data; this is done by passing a `formData` prop object matching the schema: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "object", properties: { @@ -116,8 +127,7 @@ const formData = { }; render(( - + ), document.getElementById("app")); ``` @@ -135,12 +145,15 @@ By default, `` is an [uncontrolled component](https://reactjs.org/docs/u `onChange` and `formData` props as in the below example: ```jsx +import validator from "@rjsf/validator-ajv6"; + const App = () => { const [formData, setFormData] = React.useState(null); return ( setFormData(e.formData)} + validator={validator} />); }; diff --git a/docs/usage/arrays.md b/docs/usage/arrays.md index 964b1fb892..761f03abaf 100644 --- a/docs/usage/arrays.md +++ b/docs/usage/arrays.md @@ -7,6 +7,8 @@ Arrays are defined with a type equal to `array`, and array items' schemas are sp Arrays of a single field type can be specified as follows: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -15,7 +17,7 @@ const schema = { }; render(( - + ), document.getElementById("app")); ``` @@ -24,6 +26,8 @@ render(( Arrays of objects can be specified as follows: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -37,7 +41,7 @@ const schema = { }; render(( - + ), document.getElementById("app")); ``` @@ -46,6 +50,8 @@ render(( To specify a uiSchema that applies to array items, specify the uiSchema value within the `items` property: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -60,7 +66,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -69,6 +75,8 @@ render(( The `additionalItems` keyword allows the user to add additional items of a given schema. For example: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -80,17 +88,19 @@ const schema = { }; render(( - + ), document.getElementById("app")); ``` -## Array item options +## Array item uiSchema options ### `orderable` option Array items are orderable by default, and react-jsonschema-form renders move up/down buttons alongside them. The uiSchema `orderable` options allows you to disable ordering: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -105,7 +115,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -114,6 +124,8 @@ render(( 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 +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -128,7 +140,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -137,6 +149,8 @@ render(( 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 +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", items: { @@ -151,7 +165,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -163,6 +177,8 @@ The default behavior for array fields is a list of text inputs with add/remove b Example: ```jsx +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", title: "A multiple-choice list", @@ -174,13 +190,15 @@ const schema = { }; render(( - + ), document.getElementById("app")); ``` 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 +import validator from "@rjsf/validator-ajv6"; + const schema = { type: "array", title: "A multiple-choice list", @@ -196,7 +214,7 @@ const uiSchema = { }; render(( - + ), document.getElementById("app")); ``` @@ -207,6 +225,8 @@ In addition to [ArrayFieldTemplate](../advanced-customization/custom-templates.m Example: ```jsx +import validator from "@rjsf/validator-ajv6"; + const CustomSelectComponent = props => { return ( - ); -}; - -export default ColorWidget; diff --git a/packages/antd/src/widgets/DateTimeWidget/index.js b/packages/antd/src/widgets/DateTimeWidget/index.js index 78f32a2845..6a3db69c39 100644 --- a/packages/antd/src/widgets/DateTimeWidget/index.js +++ b/packages/antd/src/widgets/DateTimeWidget/index.js @@ -1,10 +1,10 @@ -import React from 'react'; -import dayjs from 'dayjs'; +import React from "react"; +import dayjs from "dayjs"; -import DatePicker from '../../components/DatePicker'; +import DatePicker from "../../components/DatePicker"; const DATE_PICKER_STYLE = { - width: '100%', + width: "100%", }; const DateTimeWidget = ({ diff --git a/packages/antd/src/widgets/DateWidget/index.js b/packages/antd/src/widgets/DateWidget/index.js index 7a87a2a3a3..4252d698be 100644 --- a/packages/antd/src/widgets/DateWidget/index.js +++ b/packages/antd/src/widgets/DateWidget/index.js @@ -1,10 +1,10 @@ -import React from 'react'; -import dayjs from 'dayjs'; +import React from "react"; +import dayjs from "dayjs"; -import DatePicker from '../../components/DatePicker'; +import DatePicker from "../../components/DatePicker"; const DATE_PICKER_STYLE = { - width: '100%', + width: "100%", }; const DateWidget = ({ @@ -26,7 +26,7 @@ const DateWidget = ({ const { readonlyAsDisabled = true } = formContext; const handleChange = (nextValue) => - onChange(nextValue && nextValue.format('YYYY-MM-DD')); + onChange(nextValue && nextValue.format("YYYY-MM-DD")); const handleBlur = () => onBlur(id, value); diff --git a/packages/antd/src/widgets/EmailWidget/index.js b/packages/antd/src/widgets/EmailWidget/index.js deleted file mode 100644 index 0f277b32d3..0000000000 --- a/packages/antd/src/widgets/EmailWidget/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; - -import Input from 'antd/lib/input'; - -const INPUT_STYLE = { - width: '100%', -}; - -const EmailWidget = ({ - // autofocus, - disabled, - formContext, - id, - // label, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - // required, - // schema, - value, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const handleChange = ({ target }) => - onChange(target.value === '' ? options.emptyValue : target.value); - - const handleBlur = ({ target }) => onBlur(id, target.value); - - const handleFocus = ({ target }) => onFocus(id, target.value); - - return ( - - ); -}; - -export default EmailWidget; diff --git a/packages/antd/src/widgets/PasswordWidget/index.js b/packages/antd/src/widgets/PasswordWidget/index.js index 6171832a1c..a207b5d2e6 100644 --- a/packages/antd/src/widgets/PasswordWidget/index.js +++ b/packages/antd/src/widgets/PasswordWidget/index.js @@ -1,6 +1,6 @@ -import React from 'react'; +import React from "react"; -import Input from 'antd/lib/input'; +import Input from "antd/lib/input"; const PasswordWidget = ({ // autofocus, @@ -20,10 +20,10 @@ const PasswordWidget = ({ }) => { const { readonlyAsDisabled = true } = formContext; - const emptyValue = options.emptyValue || ''; + const emptyValue = options.emptyValue || ""; const handleChange = ({ target }) => - onChange(target.value === '' ? emptyValue : target.value); + onChange(target.value === "" ? emptyValue : target.value); const handleBlur = ({ target }) => onBlur(id, target.value); @@ -38,7 +38,7 @@ const PasswordWidget = ({ onChange={!readonly ? handleChange : undefined} onFocus={!readonly ? handleFocus : undefined} placeholder={placeholder} - value={value || ''} + value={value || ""} /> ); }; diff --git a/packages/antd/src/widgets/RadioWidget/index.js b/packages/antd/src/widgets/RadioWidget/index.js index b2b3e135db..44f757fe57 100644 --- a/packages/antd/src/widgets/RadioWidget/index.js +++ b/packages/antd/src/widgets/RadioWidget/index.js @@ -1,7 +1,7 @@ /* eslint-disable no-else-return */ -import React from 'react'; +import React from "react"; -import Radio from 'antd/lib/radio'; +import Radio from "antd/lib/radio"; const RadioWidget = ({ autofocus, @@ -24,7 +24,7 @@ const RadioWidget = ({ const { enumOptions, enumDisabled } = options; const handleChange = ({ target: { value: nextValue } }) => - onChange(schema.type === 'boolean' ? nextValue !== 'false' : nextValue); + onChange(schema.type === "boolean" ? nextValue !== "false" : nextValue); const handleBlur = ({ target }) => onBlur(id, target.value); diff --git a/packages/antd/src/widgets/RangeWidget/index.js b/packages/antd/src/widgets/RangeWidget/index.js index 4b58f7b28c..a6c67e073f 100644 --- a/packages/antd/src/widgets/RangeWidget/index.js +++ b/packages/antd/src/widgets/RangeWidget/index.js @@ -1,10 +1,8 @@ /* eslint-disable no-else-return */ -import React from 'react'; +import React from "react"; -import { utils } from '@rjsf/core'; -import Slider from 'antd/lib/slider'; - -const { rangeSpec } = utils; +import { rangeSpec } from "@rjsf/utils"; +import Slider from "antd/lib/slider"; const RangeWidget = ({ autofocus, @@ -26,10 +24,10 @@ const RangeWidget = ({ const { min, max, step } = rangeSpec(schema); - const emptyValue = options.emptyValue || ''; + const emptyValue = options.emptyValue || ""; const handleChange = (nextValue) => - onChange(nextValue === '' ? emptyValue : nextValue); + onChange(nextValue === "" ? emptyValue : nextValue); const handleBlur = () => onBlur(id, value); diff --git a/packages/antd/src/widgets/SelectWidget/index.js b/packages/antd/src/widgets/SelectWidget/index.js index 40822add04..9b37190710 100644 --- a/packages/antd/src/widgets/SelectWidget/index.js +++ b/packages/antd/src/widgets/SelectWidget/index.js @@ -1,46 +1,11 @@ /* eslint-disable no-else-return */ -import React from 'react'; +import React from "react"; -import { utils } from '@rjsf/core'; -import Select from 'antd/lib/select'; - -const { asNumber, guessType } = utils; +import { processSelectValue } from "@rjsf/utils"; +import Select from "antd/lib/select"; const SELECT_STYLE = { - width: '100%', -}; - -const nums = new Set(['number', 'integer']); - -/** - * This is a silly limitation in the DOM where option change event values are - * always retrieved as strings. - */ -const processValue = (schema, value) => { - // "enum" is a reserved word, so only "type" and "items" can be destructured - const { type, items } = schema; - - if (value === '') { - return undefined; - } else if (type === 'array' && items && nums.has(items.type)) { - return value.map(asNumber); - } else if (type === 'boolean') { - return value === 'true'; - } else if (type === 'number') { - return asNumber(value); - } - - // If type is undefined, but an enum is present, try and infer the type from - // the enum values - if (schema.enum) { - if (schema.enum.every((x) => guessType(x) === 'number')) { - return asNumber(value); - } else if (schema.enum.every((x) => guessType(x) === 'boolean')) { - return value === 'true'; - } - } - - return value; + width: "100%", }; const SelectWidget = ({ @@ -64,11 +29,14 @@ const SelectWidget = ({ const { enumOptions, enumDisabled } = options; - const handleChange = (nextValue) => onChange(processValue(schema, nextValue)); + const handleChange = (nextValue) => + onChange(processSelectValue(schema, nextValue, options)); - const handleBlur = () => onBlur(id, processValue(schema, value)); + const handleBlur = () => + onBlur(id, processSelectValue(schema, value, options)); - const handleFocus = () => onFocus(id, processValue(schema, value)); + const handleFocus = () => + onFocus(id, processSelectValue(schema, value, options)); const getPopupContainer = (node) => node.parentNode; @@ -81,14 +49,14 @@ const SelectWidget = ({ disabled={disabled || (readonlyAsDisabled && readonly)} getPopupContainer={getPopupContainer} id={id} - mode={typeof multiple !== 'undefined' ? 'multiple' : undefined} + mode={typeof multiple !== "undefined" ? "multiple" : undefined} name={id} onBlur={!readonly ? handleBlur : undefined} onChange={!readonly ? handleChange : undefined} onFocus={!readonly ? handleFocus : undefined} placeholder={placeholder} style={SELECT_STYLE} - value={typeof value !== 'undefined' ? stringify(value) : undefined} + value={typeof value !== "undefined" ? stringify(value) : undefined} > {enumOptions.map(({ value: optionValue, label: optionLabel }) => ( { - const { submitText, norender, props: submitButtonProps }= getSubmitButtonOptions(uiSchema); - if (norender) {return null;} - return (); -}; - diff --git a/packages/antd/src/widgets/SubmitButton/index.js b/packages/antd/src/widgets/SubmitButton/index.js deleted file mode 100644 index f676497ba2..0000000000 --- a/packages/antd/src/widgets/SubmitButton/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './SubmitButton'; -export * from './SubmitButton'; diff --git a/packages/antd/src/widgets/TextareaWidget/index.js b/packages/antd/src/widgets/TextareaWidget/index.js index fa0f3c9bcc..8ca98fb7be 100644 --- a/packages/antd/src/widgets/TextareaWidget/index.js +++ b/packages/antd/src/widgets/TextareaWidget/index.js @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; -import Input from 'antd/lib/input'; +import Input from "antd/lib/input"; const INPUT_STYLE = { - width: '100%', + width: "100%", }; const TextareaWidget = ({ @@ -25,7 +25,7 @@ const TextareaWidget = ({ const { readonlyAsDisabled = true } = formContext; const handleChange = ({ target }) => - onChange(target.value === '' ? options.emptyValue : target.value); + onChange(target.value === "" ? options.emptyValue : target.value); const handleBlur = ({ target }) => onBlur(id, target.value); diff --git a/packages/antd/src/widgets/URLWidget/index.js b/packages/antd/src/widgets/URLWidget/index.js deleted file mode 100644 index 3dab394f1f..0000000000 --- a/packages/antd/src/widgets/URLWidget/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; - -import Input from 'antd/lib/input'; - -const INPUT_STYLE = { - width: '100%', -}; - -const URLWidget = ({ - // autofocus, - disabled, - formContext, - id, - // label, - onBlur, - onChange, - onFocus, - options, - placeholder, - readonly, - // required, - // schema, - value, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const handleChange = ({ target }) => - onChange(target.value === '' ? options.emptyValue : target.value); - - const handleBlur = ({ target }) => onBlur(id, target.value); - - const handleFocus = ({ target }) => onFocus(id, target.value); - - return ( - - ); -}; - -export default URLWidget; diff --git a/packages/antd/src/widgets/UpDownWidget/index.js b/packages/antd/src/widgets/UpDownWidget/index.js deleted file mode 100644 index d5ef314d3e..0000000000 --- a/packages/antd/src/widgets/UpDownWidget/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -import InputNumber from 'antd/lib/input-number'; - -const INPUT_STYLE = { - width: '100%', -}; - -const UpDownWidget = ({ - // autofocus, - disabled, - formContext, - id, - onBlur, - onChange, - onFocus, - // options, - placeholder, - readonly, - // required, - // schema, - value, -}) => { - const { readonlyAsDisabled = true } = formContext; - - const handleChange = (nextValue) => onChange(nextValue); - - const handleBlur = ({ target }) => onBlur(id, target.value); - - const handleFocus = ({ target }) => onFocus(id, target.value); - - return ( - - ); -}; - -export default UpDownWidget; diff --git a/packages/antd/test/Array.test.js b/packages/antd/test/Array.test.js index e85519045e..d55abdbe5f 100644 --- a/packages/antd/test/Array.test.js +++ b/packages/antd/test/Array.test.js @@ -1,8 +1,9 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; +import React from "react"; +import renderer from "react-test-renderer"; +import validator from "@rjsf/validator-ajv6"; -import '../__mocks__/matchMedia.mock'; -import Form from '../src'; +import "../__mocks__/matchMedia.mock"; +import Form from "../src"; const { describe, expect, test } = global; @@ -11,11 +12,11 @@ describe("array fields", () => { const schema = { type: "array", items: { - type: "string" - } + type: "string", + }, }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -24,15 +25,15 @@ describe("array fields", () => { type: "array", items: [ { - type: "string" + type: "string", }, { - type: "number" - } - ] + type: "number", + }, + ], }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -41,12 +42,36 @@ describe("array fields", () => { type: "array", items: { type: "string", - enum: ["a", "b", "c"] + enum: ["a", "b", "c"], + }, + uniqueItems: true, + }; + const tree = renderer + .create(, { + createNodeMock: (element) => { + if (element.type === "span" && element.props["aria-hidden"]) { + // the `rc-select` MultipleSelector code expects a ref for this span to exist, so use the feature of + // react-test-renderer to create one + // See: https://reactjs.org/docs/test-renderer.html#ideas + return { scrollWidth: 100 }; + } + return null; + }, + }) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("array icons", () => { + const schema = { + type: "array", + items: { + type: "string", }, - uniqueItems: true }; const tree = renderer - .create() + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/packages/antd/test/Form.test.js b/packages/antd/test/Form.test.js index 1eec6ba51d..3929cc15e7 100644 --- a/packages/antd/test/Form.test.js +++ b/packages/antd/test/Form.test.js @@ -1,8 +1,9 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; +import React from "react"; +import renderer from "react-test-renderer"; +import validator from "@rjsf/validator-ajv6"; -import '../__mocks__/matchMedia.mock'; -import Form from '../src'; +import "../__mocks__/matchMedia.mock"; +import Form from "../src"; const { describe, expect, test } = global; @@ -10,30 +11,30 @@ describe("single fields", () => { describe("string field", () => { test("regular", () => { const schema = { - type: "string" + type: "string", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); test("format email", () => { const schema = { type: "string", - format: "email" + format: "email", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); test("format uri", () => { const schema = { type: "string", - format: "uri" + format: "uri", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -43,35 +44,215 @@ describe("single fields", () => { format: "data-url", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); }); + test("string field with placeholder", () => { + const schema = { + type: "string", + }; + const uiSchema = { + "ui:placeholder": "placeholder", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); test("number field", () => { const schema = { - type: "number" + type: "number", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("number field 0", () => { + const schema = { + type: "number", }; + const formData = 0; const tree = renderer - .create() + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); test("null field", () => { const schema = { - type: "null" + type: "null", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); test("unsupported field", () => { const schema = { - type: undefined + type: undefined, + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("format color", () => { + const schema = { + type: "string", + format: "color", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("format date", () => { + const schema = { + type: "string", + format: "date", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("format datetime", () => { + const schema = { + type: "string", + format: "datetime", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("password field", () => { + const schema = { + type: "string", + }; + const uiSchema = { + "ui:widget": "password", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("up/down field", () => { + const schema = { + type: "number", + }; + const uiSchema = { + "ui:widget": "updown", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("textarea field", () => { + const schema = { + type: "string", + }; + const uiSchema = { + "ui:widget": "textarea", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("select field", () => { + const schema = { + type: "string", + enum: ["foo", "bar"], + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("checkbox field", () => { + const schema = { + type: "boolean", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("checkbox field", () => { + const schema = { + type: "boolean", + }; + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("checkboxes field", () => { + const schema = { + type: "array", + items: { + type: "string", + enum: ["foo", "bar", "fuzz", "qux"], + }, + uniqueItems: true, + }; + const uiSchema = { + "ui:widget": "checkboxes", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("radio field", () => { + const schema = { + type: "boolean", + }; + const uiSchema = { + "ui:widget": "radio", + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("slider field", () => { + const schema = { + type: "integer", + minimum: 42, + maximum: 100, + }; + const uiSchema = { + "ui:widget": "range", }; const tree = renderer - .create() + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -90,7 +271,9 @@ describe("single fields", () => { }, }; const tree = renderer - .create() + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -101,11 +284,11 @@ describe("single fields", () => { "my-field": { type: "string", description: "some description", - } - } + }, + }, }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); @@ -116,8 +299,8 @@ describe("single fields", () => { "my-field": { type: "string", description: "some description", - } - } + }, + }, }; const uiSchema = { "my-field": { @@ -125,16 +308,56 @@ describe("single fields", () => { }, }; const tree = renderer - .create() + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("title field", () => { + const schema = { + type: "object", + properties: { + title: { + type: "string", + }, + }, + }; + const uiSchema = { + "ui:title": "Titre 1", + title: { + "ui:title": "Titre 2", + }, + }; + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("hidden label", () => { + const schema = { + type: "string", + }; + const uiSchema = { + "ui:options": { + label: false, + }, + }; + const tree = renderer + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); test("using custom tagName", () => { const schema = { - type: "string" + type: "string", }; const tree = renderer - .create() + .create() .toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/packages/antd/test/Object.test.js b/packages/antd/test/Object.test.js index 160b96a953..93321dea93 100644 --- a/packages/antd/test/Object.test.js +++ b/packages/antd/test/Object.test.js @@ -1,8 +1,9 @@ -import React from 'react'; +import React from "react"; import renderer from "react-test-renderer"; +import validator from "@rjsf/validator-ajv6"; -import '../__mocks__/matchMedia.mock'; -import Form from '../src'; +import "../__mocks__/matchMedia.mock"; +import Form from "../src"; const { describe, expect, test } = global; @@ -11,12 +12,24 @@ describe("object fields", () => { const schema = { type: "object", properties: { - a: { type: "string" }, - b: { type: "number" } - } + a: { type: "string", title: "A" }, + b: { type: "number", title: "B" }, + }, }; const tree = renderer - .create() + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + test("additionalProperties", () => { + const schema = { + type: "object", + additionalProperties: true, + }; + const tree = renderer + .create( + + ) .toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/packages/antd/test/__snapshots__/Array.test.js.snap b/packages/antd/test/__snapshots__/Array.test.js.snap index ba143a1303..a8a895016f 100644 --- a/packages/antd/test/__snapshots__/Array.test.js.snap +++ b/packages/antd/test/__snapshots__/Array.test.js.snap @@ -22,6 +22,7 @@ exports[`array fields array 1`] = ` } } > +
+
+
+
+
+ + + + +`; + +exports[`array fields array icons 1`] = ` +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+ + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
@@ -99,7 +605,7 @@ exports[`array fields array 1`] = `
+
+`; + +exports[`single fields checkbox field 2`] = ` +
+
+ +
+ +
+`; + +exports[`single fields checkboxes field 1`] = ` +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+`; + exports[`single fields field with description 1`] = `
+
- -
-
+ +
- + + > + + + +
-
-
- - some description - + + some description + +
@@ -96,7 +379,7 @@ exports[`single fields field with description 1`] = ` +
+`; + +exports[`single fields hidden field 1`] = ` +
+
+
+
+ +
+
+
+ +
+`; + +exports[`single fields hidden label 1`] = ` +
+
+ +
+ +
+`; + +exports[`single fields null field 1`] = ` +
+
+ + +`; + +exports[`single fields number field 0 1`] = ` +
+
+
+
+ + + + + + +
+
+
+ +
+`; + +exports[`single fields number field 1`] = ` +
+
+
+
+ + + + + + + + + + +
+
+
+ +`; + +exports[`single fields password field 1`] = ` +
+
+ + + + + + + + +
+ +
+`; + +exports[`single fields radio field 1`] = ` +
+
+
+ + +
+
+ +
+`; + +exports[`single fields select field 1`] = ` +
+
+
+
+ + + + + + +
+ + + + + +
+
+ +
+`; + +exports[`single fields slider field 1`] = ` +
+
+
+
+
+
+
+
+
+ + +`; + +exports[`single fields string field format data-url 1`] = ` +
+
+
+

+ +

+
+
+ +
+`; + +exports[`single fields string field format email 1`] = ` +
+
+ +
+
+ +`; + +exports[`single fields title field 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + + + +
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +`; + +exports[`single fields up/down field 1`] = ` +
+
+
+
+ + + + + + + + + + +
+
+ +
+
+
+
+ aria-label="delete" + className="anticon anticon-delete" + role="img" + > + + + + +
+
+
+ +
+
+
+ + + + +`; + +exports[`object fields object 1`] = ` +
+
+
+
+
- -
-
+ +
-
- - - - - - - - - - -
-
-
+ +
+
+ + + +
+
+
+
+
+
+
+
+
- + +
+
+
+
+
+
+
+ + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+ + + +
@@ -253,7 +587,7 @@ exports[`object fields object 1`] = `
); diff --git a/packages/bootstrap-4/src/AddButton/index.ts b/packages/bootstrap-4/src/AddButton/index.ts index 752d720d32..e419627056 100644 --- a/packages/bootstrap-4/src/AddButton/index.ts +++ b/packages/bootstrap-4/src/AddButton/index.ts @@ -1,2 +1,2 @@ -export { default } from './AddButton'; -export * from './AddButton'; +export { default } from "./AddButton"; +export * from "./AddButton"; diff --git a/packages/bootstrap-4/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx b/packages/bootstrap-4/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx new file mode 100644 index 0000000000..79f55309a2 --- /dev/null +++ b/packages/bootstrap-4/src/ArrayFieldItemTemplate/ArrayFieldItemTemplate.tsx @@ -0,0 +1,73 @@ +import React, { CSSProperties } from "react"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import { ArrayFieldTemplateItemType } from "@rjsf/utils"; + +const ArrayFieldItemTemplate = (props: ArrayFieldTemplateItemType) => { + const { + children, + disabled, + hasToolbar, + hasMoveDown, + hasMoveUp, + hasRemove, + index, + onDropIndexClick, + onReorderClick, + readonly, + registry, + } = props; + const { MoveDownButton, MoveUpButton, RemoveButton } = + registry.templates.ButtonTemplates; + const btnStyle: CSSProperties = { + flex: 1, + paddingLeft: 6, + paddingRight: 6, + fontWeight: "bold", + }; + return ( +
+ + + {children} + + + {hasToolbar && ( +
+ {(hasMoveUp || hasMoveDown) && ( +
+ +
+ )} + {(hasMoveUp || hasMoveDown) && ( +
+ +
+ )} + {hasRemove && ( +
+ +
+ )} +
+ )} + +
+
+ ); +}; + +export default ArrayFieldItemTemplate; diff --git a/packages/bootstrap-4/src/ArrayFieldItemTemplate/index.ts b/packages/bootstrap-4/src/ArrayFieldItemTemplate/index.ts new file mode 100644 index 0000000000..f64828bebf --- /dev/null +++ b/packages/bootstrap-4/src/ArrayFieldItemTemplate/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ArrayFieldItemTemplate"; +export * from "./ArrayFieldItemTemplate"; diff --git a/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx b/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx index 77a4bb705e..df8c216998 100644 --- a/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx +++ b/packages/bootstrap-4/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx @@ -1,207 +1,93 @@ import React from "react"; -import { utils } from "@rjsf/core"; import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import Container from "react-bootstrap/Container"; -import { ArrayFieldTemplateProps, IdSchema } from "@rjsf/core"; - -import AddButton from "../AddButton/AddButton"; -import IconButton from "../IconButton/IconButton"; - -const { isMultiSelect, getDefaultRegistry } = utils; +import { + ArrayFieldTemplateItemType, + ArrayFieldTemplateProps, + getTemplate, + getUiOptions, +} from "@rjsf/utils"; const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { - const { schema, registry = getDefaultRegistry() } = props; - - if (isMultiSelect(schema, registry.rootSchema)) { - return ; - } else { - return ; - } -}; - -type ArrayFieldTitleProps = { - TitleField: any; - idSchema: IdSchema; - title: string; - required: boolean; -}; - -const ArrayFieldTitle = ({ - TitleField, - idSchema, - title, - required, -}: ArrayFieldTitleProps) => { - if (!title) { - return null; - } - - const id = `${idSchema.$id}__title`; - return ; -}; - -type ArrayFieldDescriptionProps = { - DescriptionField: any; - idSchema: IdSchema; - description: string; -}; - -const ArrayFieldDescription = ({ - DescriptionField, - idSchema, - description, -}: ArrayFieldDescriptionProps) => { - if (!description) { - return null; - } - - const id = `${idSchema.$id}__description`; - return ; -}; - -// Used in the two templates -const DefaultArrayItem = (props: any) => { - const btnStyle = { - flex: 1, - paddingLeft: 6, - paddingRight: 6, - fontWeight: "bold", - }; - return ( -
- - {props.children} - - - {props.hasToolbar && ( -
- {(props.hasMoveUp || props.hasMoveDown) && ( -
- -
- )} - - {(props.hasMoveUp || props.hasMoveDown) && ( -
- -
- )} - - {props.hasRemove && ( -
- -
- )} -
- )} - -
-
+ const { + canAdd, + disabled, + idSchema, + uiSchema, + items, + onAddClick, + readonly, + registry, + required, + schema, + title, + } = props; + const uiOptions = getUiOptions(uiSchema); + const ArrayFieldDescriptionTemplate = + getTemplate<"ArrayFieldDescriptionTemplate">( + "ArrayFieldDescriptionTemplate", + registry, + uiOptions + ); + const ArrayFieldItemTemplate = getTemplate<"ArrayFieldItemTemplate">( + "ArrayFieldItemTemplate", + registry, + uiOptions ); -}; - -const DefaultFixedArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { - return ( -
- - - {(props.uiSchema["ui:description"] || props.schema.description) && ( -
- {props.uiSchema["ui:description"] || props.schema.description} -
- )} - -
- {props.items && props.items.map(DefaultArrayItem)} -
- - {props.canAdd && ( - - )} -
+ const ArrayFieldTitleTemplate = getTemplate<"ArrayFieldTitleTemplate">( + "ArrayFieldTitleTemplate", + registry, + uiOptions ); -}; - -const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => { + // Button templates are not overridden in the uiSchema + const { + ButtonTemplates: { AddButton }, + } = registry.templates; return (
- - - {(props.uiSchema["ui:description"] || props.schema.description) && ( - - )} - - - {props.items && props.items.map(p => DefaultArrayItem(p))} - - {props.canAdd && ( - - - - - - - + {(uiOptions.description || schema.description) && ( + )} - - + + {items && + items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( + + ))} + {canAdd && ( + + + + + + + + + )} + +
); diff --git a/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts b/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts index ab908dec2c..29cbde48eb 100644 --- a/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts +++ b/packages/bootstrap-4/src/ArrayFieldTemplate/index.ts @@ -1,2 +1,2 @@ -export { default } from './ArrayFieldTemplate'; -export * from './ArrayFieldTemplate'; +export { default } from "./ArrayFieldTemplate"; +export * from "./ArrayFieldTemplate"; diff --git a/packages/bootstrap-4/src/TextWidget/TextWidget.tsx b/packages/bootstrap-4/src/BaseInputTemplate/BaseInputTemplate.tsx similarity index 74% rename from packages/bootstrap-4/src/TextWidget/TextWidget.tsx rename to packages/bootstrap-4/src/BaseInputTemplate/BaseInputTemplate.tsx index 447de38bea..c859f6e317 100644 --- a/packages/bootstrap-4/src/TextWidget/TextWidget.tsx +++ b/packages/bootstrap-4/src/BaseInputTemplate/BaseInputTemplate.tsx @@ -1,10 +1,8 @@ import React from "react"; - import Form from "react-bootstrap/Form"; +import { getInputProps, getUiOptions, WidgetProps } from "@rjsf/utils"; -import { WidgetProps } from "@rjsf/core"; - -const TextWidget = ({ +const BaseInputTemplate = ({ id, placeholder, required, @@ -21,8 +19,11 @@ const TextWidget = ({ schema, rawErrors = [], uiSchema, - + children, + extraProps, }: WidgetProps) => { + const inputProps = { ...extraProps, ...getInputProps(schema, type, options) }; + const uiOptions = getUiOptions(uiSchema); const _onChange = ({ target: { value }, }: React.ChangeEvent) => @@ -32,14 +33,16 @@ const TextWidget = ({ const _onFocus = ({ target: { value }, }: React.FocusEvent) => onFocus(id, value); - const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}` // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""] return ( - 0 ? "text-danger" : ""}> - {uiSchema["ui:title"] || schema.title || label} - {(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null} + 0 ? "text-danger" : ""} + > + {uiOptions.title || label || schema.title} + {(label || uiOptions.title) && required ? "*" : null} 0 ? "is-invalid" : ""} list={schema.examples ? `examples_${id}` : undefined} - type={inputType} + {...inputProps} value={value || value === 0 ? value : ""} onChange={_onChange} onBlur={_onBlur} onFocus={_onFocus} - /> + {children} {schema.examples ? ( {(schema.examples as string[]) @@ -70,4 +73,4 @@ const TextWidget = ({ ); }; -export default TextWidget; +export default BaseInputTemplate; diff --git a/packages/bootstrap-4/src/BaseInputTemplate/index.ts b/packages/bootstrap-4/src/BaseInputTemplate/index.ts new file mode 100644 index 0000000000..37c3947ed0 --- /dev/null +++ b/packages/bootstrap-4/src/BaseInputTemplate/index.ts @@ -0,0 +1,2 @@ +export { default } from "./BaseInputTemplate"; +export * from "./BaseInputTemplate"; diff --git a/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx b/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx index df4e9db95b..34172f3ae7 100644 --- a/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx +++ b/packages/bootstrap-4/src/CheckboxWidget/CheckboxWidget.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { WidgetProps } from "@rjsf/core"; +import { WidgetProps } from "@rjsf/utils"; import Form from "react-bootstrap/Form"; const CheckboxWidget = (props: WidgetProps) => { @@ -30,20 +30,22 @@ const CheckboxWidget = (props: WidgetProps) => { const desc = label || schema.description; return ( - - - + + + ); }; diff --git a/packages/bootstrap-4/src/CheckboxWidget/index.ts b/packages/bootstrap-4/src/CheckboxWidget/index.ts index b9e3c318ec..9a6515772c 100644 --- a/packages/bootstrap-4/src/CheckboxWidget/index.ts +++ b/packages/bootstrap-4/src/CheckboxWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './CheckboxWidget'; -export * from './CheckboxWidget'; +export { default } from "./CheckboxWidget"; +export * from "./CheckboxWidget"; diff --git a/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx b/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx index fa5894bcd9..fa82df7e9e 100644 --- a/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx +++ b/packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx @@ -1,6 +1,6 @@ import React from "react"; import Form from "react-bootstrap/Form"; -import { WidgetProps } from "@rjsf/core"; +import { WidgetProps } from "@rjsf/utils"; const selectValue = (value: any, selected: any, all: any) => { const at = all.indexOf(value); @@ -31,17 +31,17 @@ const CheckboxesWidget = ({ }: WidgetProps) => { const { enumOptions, enumDisabled, inline } = options; - const _onChange = (option: any) => ({ - target: { checked }, - }: React.ChangeEvent) => { - const all = (enumOptions as any).map(({ value }: any) => value); + const _onChange = + (option: any) => + ({ target: { checked } }: React.ChangeEvent) => { + const all = (enumOptions as any).map(({ value }: any) => value); - if (checked) { - onChange(selectValue(option.value, value, all)); - } else { - onChange(deselectValue(option.value, value)); - } - }; + if (checked) { + onChange(selectValue(option.value, value, all)); + } else { + onChange(deselectValue(option.value, value)); + } + }; const _onBlur = ({ target: { value } }: React.FocusEvent) => onBlur(id, value); diff --git a/packages/bootstrap-4/src/CheckboxesWidget/index.ts b/packages/bootstrap-4/src/CheckboxesWidget/index.ts index 97152004fa..c5acc6aa16 100644 --- a/packages/bootstrap-4/src/CheckboxesWidget/index.ts +++ b/packages/bootstrap-4/src/CheckboxesWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './CheckboxesWidget'; -export * from './CheckboxesWidget'; +export { default } from "./CheckboxesWidget"; +export * from "./CheckboxesWidget"; diff --git a/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx b/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx deleted file mode 100644 index ead3906679..0000000000 --- a/packages/bootstrap-4/src/ColorWidget/ColorWidget.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { WidgetProps } from '@rjsf/core'; - -const ColorWidget = (props: WidgetProps) => { - const { registry } = props; - const { TextWidget } = registry.widgets; - return ; -}; - -export default ColorWidget; diff --git a/packages/bootstrap-4/src/ColorWidget/index.ts b/packages/bootstrap-4/src/ColorWidget/index.ts deleted file mode 100644 index 95e1f017ed..0000000000 --- a/packages/bootstrap-4/src/ColorWidget/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ColorWidget"; -export * from "./ColorWidget"; diff --git a/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx b/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx deleted file mode 100644 index 086adf23e8..0000000000 --- a/packages/bootstrap-4/src/DateTimeWidget/DateTimeWidget.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { utils, WidgetProps } from "@rjsf/core"; - -const { localToUTC, utcToLocal } = utils; - -const DateTimeWidget = (props: WidgetProps) => { - const { registry } = props; - const { TextWidget } = registry.widgets; - const value = utcToLocal(props.value); - const onChange = (value: any) => { - props.onChange(localToUTC(value)); - }; - - return ( - - ); -}; - -export default DateTimeWidget; diff --git a/packages/bootstrap-4/src/DateWidget/DateWidget.tsx b/packages/bootstrap-4/src/DateWidget/DateWidget.tsx deleted file mode 100644 index 62fc811875..0000000000 --- a/packages/bootstrap-4/src/DateWidget/DateWidget.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import { WidgetProps } from '@rjsf/core'; - -const DateWidget = (props: WidgetProps) => { - const { registry } = props; - const { TextWidget } = registry.widgets; - return ( - - ); -}; - -export default DateWidget; diff --git a/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx b/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx index 1dc6a050ec..1e6e0eccba 100644 --- a/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx +++ b/packages/bootstrap-4/src/DescriptionField/DescriptionField.tsx @@ -1,13 +1,15 @@ import React from "react"; -import { FieldProps } from "@rjsf/core"; +import { DescriptionFieldProps } from "@rjsf/utils"; -export interface DescriptionFieldProps extends Partial { - description?: string; -} - -const DescriptionField = ({ description }: Partial) => { +const DescriptionField = ({ id, description }: DescriptionFieldProps) => { if (description) { - return
{description}
; + return ( +
+
+ {description} +
+
+ ); } return null; diff --git a/packages/bootstrap-4/src/DescriptionField/index.ts b/packages/bootstrap-4/src/DescriptionField/index.ts index 401540d99b..899add9fc9 100644 --- a/packages/bootstrap-4/src/DescriptionField/index.ts +++ b/packages/bootstrap-4/src/DescriptionField/index.ts @@ -1,2 +1,2 @@ -export { default } from './DescriptionField'; -export * from './DescriptionField'; +export { default } from "./DescriptionField"; +export * from "./DescriptionField"; diff --git a/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx b/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx deleted file mode 100644 index 372a6b3f93..0000000000 --- a/packages/bootstrap-4/src/EmailWidget/EmailWidget.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { WidgetProps } from '@rjsf/core'; - -const EmailWidget = (props: WidgetProps) => { - const { registry } = props; - const { TextWidget } = registry.widgets; - return ; -}; - -export default EmailWidget; diff --git a/packages/bootstrap-4/src/EmailWidget/index.ts b/packages/bootstrap-4/src/EmailWidget/index.ts deleted file mode 100644 index c48979eea8..0000000000 --- a/packages/bootstrap-4/src/EmailWidget/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './EmailWidget'; -export * from './EmailWidget'; diff --git a/packages/bootstrap-4/src/ErrorList/ErrorList.tsx b/packages/bootstrap-4/src/ErrorList/ErrorList.tsx index f30f1320d3..61dd663125 100644 --- a/packages/bootstrap-4/src/ErrorList/ErrorList.tsx +++ b/packages/bootstrap-4/src/ErrorList/ErrorList.tsx @@ -3,13 +3,13 @@ import React from "react"; import Card from "react-bootstrap/Card"; import ListGroup from "react-bootstrap/ListGroup"; -import { ErrorListProps } from "@rjsf/core"; +import { ErrorListProps } from "@rjsf/utils"; const ErrorList = ({ errors }: ErrorListProps) => ( Errors - + {errors.map((error, i: number) => { return ( diff --git a/packages/bootstrap-4/src/ErrorList/index.ts b/packages/bootstrap-4/src/ErrorList/index.ts index 79376ace11..af63a176fd 100644 --- a/packages/bootstrap-4/src/ErrorList/index.ts +++ b/packages/bootstrap-4/src/ErrorList/index.ts @@ -1,2 +1,2 @@ -export { default } from './ErrorList'; -export * from './ErrorList'; +export { default } from "./ErrorList"; +export * from "./ErrorList"; diff --git a/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx b/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx index e0191b182b..d686a3dd46 100644 --- a/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx +++ b/packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { FieldTemplateProps } from "@rjsf/core"; +import { FieldTemplateProps } from "@rjsf/utils"; import Form from "react-bootstrap/Form"; import ListGroup from "react-bootstrap/ListGroup"; @@ -22,6 +22,7 @@ const FieldTemplate = ({ readonly, required, schema, + registry, }: FieldTemplateProps) => { return ( + schema={schema} + registry={registry} + > {children} {displayLabel && rawDescription && ( - 0 ? "text-danger" : "text-muted"}> + 0 ? "text-danger" : "text-muted"} + > {rawDescription} )} @@ -45,10 +50,12 @@ const FieldTemplate = ({ {rawErrors.map((error: string) => { return ( - - - {error} - + + {error} ); })} @@ -57,7 +64,8 @@ const FieldTemplate = ({ {rawHelp && ( 0 ? "text-danger" : "text-muted"} - id={id}> + id={id} + > {rawHelp} )} diff --git a/packages/bootstrap-4/src/FieldTemplate/WrapIfAdditional.tsx b/packages/bootstrap-4/src/FieldTemplate/WrapIfAdditional.tsx index 13a742e090..f585d250f7 100644 --- a/packages/bootstrap-4/src/FieldTemplate/WrapIfAdditional.tsx +++ b/packages/bootstrap-4/src/FieldTemplate/WrapIfAdditional.tsx @@ -1,31 +1,27 @@ - import React from "react"; -import { utils } from "@rjsf/core"; -import { JSONSchema7 } from "json-schema"; +import { ADDITIONAL_PROPERTY_FLAG, FieldTemplateProps } from "@rjsf/utils"; import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import Form from "react-bootstrap/Form"; -import IconButton from "../IconButton/IconButton"; - -const { ADDITIONAL_PROPERTY_FLAG } = utils; - -type WrapIfAdditionalProps = { - children: React.ReactElement; - classNames: string; - disabled: boolean; - id: string; - label: string; - onDropPropertyClick: (index: string) => (event?: any) => void; - onKeyChange: (index: string) => (event?: any) => void; - readonly: boolean; - required: boolean; - schema: JSONSchema7; -}; +type WrapIfAdditionalProps = { children: React.ReactElement } & Pick< + FieldTemplateProps, + | "classNames" + | "disabled" + | "id" + | "label" + | "onDropPropertyClick" + | "onKeyChange" + | "readonly" + | "required" + | "schema" + | "registry" +>; const WrapIfAdditional = ({ + classNames, children, disabled, id, @@ -35,43 +31,41 @@ const WrapIfAdditional = ({ readonly, required, schema, + registry, }: WrapIfAdditionalProps) => { + const { RemoveButton } = registry.templates.ButtonTemplates; const keyLabel = `${label} Key`; // i18n ? - const additional = schema.hasOwnProperty(ADDITIONAL_PROPERTY_FLAG); + const additional = ADDITIONAL_PROPERTY_FLAG in schema; if (!additional) { - return children; + return
{children}
; } const handleBlur = ({ target }: React.FocusEvent) => onKeyChange(target.value); + const keyId = `${id}-key`; return ( - + - {keyLabel} + {keyLabel} - - {children} - + {children} - diff --git a/packages/bootstrap-4/src/FieldTemplate/index.ts b/packages/bootstrap-4/src/FieldTemplate/index.ts index 6f7dc3861c..290c188da5 100644 --- a/packages/bootstrap-4/src/FieldTemplate/index.ts +++ b/packages/bootstrap-4/src/FieldTemplate/index.ts @@ -1,2 +1,2 @@ -export { default } from './FieldTemplate'; -export * from './FieldTemplate'; +export { default } from "./FieldTemplate"; +export * from "./FieldTemplate"; diff --git a/packages/bootstrap-4/src/Fields/Fields.ts b/packages/bootstrap-4/src/Fields/Fields.ts deleted file mode 100644 index a1871ac4a8..0000000000 --- a/packages/bootstrap-4/src/Fields/Fields.ts +++ /dev/null @@ -1,7 +0,0 @@ -import DescriptionField from '../DescriptionField/DescriptionField'; -import TitleField from '../TitleField/TitleField'; - -export default { - DescriptionField, - TitleField, -}; diff --git a/packages/bootstrap-4/src/Fields/index.ts b/packages/bootstrap-4/src/Fields/index.ts deleted file mode 100644 index c65ffe072d..0000000000 --- a/packages/bootstrap-4/src/Fields/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Fields'; -export * from './Fields'; diff --git a/packages/bootstrap-4/src/FileWidget/FileWidget.tsx b/packages/bootstrap-4/src/FileWidget/FileWidget.tsx index 8ec8ba28fd..4bd934b4bb 100644 --- a/packages/bootstrap-4/src/FileWidget/FileWidget.tsx +++ b/packages/bootstrap-4/src/FileWidget/FileWidget.tsx @@ -1,10 +1,14 @@ import React from "react"; -import { WidgetProps } from '@rjsf/core'; +import { getTemplate, WidgetProps } from "@rjsf/utils"; const FileWidget = (props: WidgetProps) => { - const { registry } = props; - const { TextWidget } = registry.widgets; - return ; + const { options, registry } = props; + const BaseInputTemplate = getTemplate<"BaseInputTemplate">( + "BaseInputTemplate", + registry, + options + ); + return ; }; export default FileWidget; diff --git a/packages/bootstrap-4/src/FileWidget/index.ts b/packages/bootstrap-4/src/FileWidget/index.ts index 6e364e5c94..e70174ced2 100644 --- a/packages/bootstrap-4/src/FileWidget/index.ts +++ b/packages/bootstrap-4/src/FileWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './FileWidget'; -export * from './FileWidget'; +export { default } from "./FileWidget"; +export * from "./FileWidget"; diff --git a/packages/bootstrap-4/src/Form/Form.tsx b/packages/bootstrap-4/src/Form/Form.tsx index c24d85da93..e193619249 100644 --- a/packages/bootstrap-4/src/Form/Form.tsx +++ b/packages/bootstrap-4/src/Form/Form.tsx @@ -1,10 +1,7 @@ import { withTheme, FormProps } from "@rjsf/core"; import Theme from "../Theme"; -import { StatelessComponent } from "react"; -const Form: - | React.ComponentClass> - | StatelessComponent> = withTheme(Theme); +const Form: React.ComponentType = withTheme(Theme); export default Form; diff --git a/packages/bootstrap-4/src/IconButton/IconButton.tsx b/packages/bootstrap-4/src/IconButton/IconButton.tsx index 4f92fcb205..e1a842bc92 100644 --- a/packages/bootstrap-4/src/IconButton/IconButton.tsx +++ b/packages/bootstrap-4/src/IconButton/IconButton.tsx @@ -1,33 +1,42 @@ import React from "react"; +import { IconButtonProps } from "@rjsf/utils"; import Button, { ButtonProps } from "react-bootstrap/Button"; import { IoIosRemove } from "react-icons/io"; -import { GrAdd } from "react-icons/gr"; import { AiOutlineArrowUp, AiOutlineArrowDown } from "react-icons/ai"; -const mappings: any = { - remove: , - plus: , - "arrow-up": , - "arrow-down": , -}; - -type IconButtonProps = ButtonProps & { - icon: string; - variant?: ButtonProps['variant']; - className?: string; - tabIndex?: number; - style?: any; - disabled?: any; - onClick?: any; -}; - -const IconButton = (props: IconButtonProps) => { - const { icon, className, ...otherProps } = props; +const IconButton = (props: IconButtonProps & ButtonProps) => { + const { icon, iconType, className, ...otherProps } = props; return ( - ); }; export default IconButton; + +export function MoveDownButton(props: IconButtonProps) { + return ( + } /> + ); +} + +export function MoveUpButton(props: IconButtonProps) { + return } />; +} + +export function RemoveButton(props: IconButtonProps) { + return ( + } + /> + ); +} diff --git a/packages/bootstrap-4/src/IconButton/index.ts b/packages/bootstrap-4/src/IconButton/index.ts index 655ec4c488..55447475bc 100644 --- a/packages/bootstrap-4/src/IconButton/index.ts +++ b/packages/bootstrap-4/src/IconButton/index.ts @@ -1,2 +1,2 @@ -export { default } from './IconButton'; -export * from './IconButton'; +export { default } from "./IconButton"; +export * from "./IconButton"; diff --git a/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx b/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx index 352aff2758..6bfd786f5f 100644 --- a/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx +++ b/packages/bootstrap-4/src/ObjectFieldTemplate/ObjectFieldTemplate.tsx @@ -4,17 +4,15 @@ import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import Container from "react-bootstrap/Container"; -import { ObjectFieldTemplateProps } from "@rjsf/core"; -import { utils } from '@rjsf/core'; - -import AddButton from "../AddButton/AddButton"; - -const { canExpand } = utils; +import { + canExpand, + getTemplate, + getUiOptions, + ObjectFieldTemplateProps, +} from "@rjsf/utils"; const ObjectFieldTemplate = ({ - DescriptionField, description, - TitleField, title, properties, required, @@ -24,21 +22,40 @@ const ObjectFieldTemplate = ({ formData, onAddClick, disabled, - readonly + readonly, + registry, }: ObjectFieldTemplateProps) => { + const uiOptions = getUiOptions(uiSchema); + const TitleFieldTemplate = getTemplate<"TitleFieldTemplate">( + "TitleFieldTemplate", + registry, + uiOptions + ); + const DescriptionFieldTemplate = getTemplate<"DescriptionFieldTemplate">( + "DescriptionFieldTemplate", + registry, + uiOptions + ); + // Button templates are not overridden in the uiSchema + const { + ButtonTemplates: { AddButton }, + } = registry.templates; return ( <> - {(uiSchema["ui:title"] || title) && ( - )} - {description && ( - )} @@ -46,7 +63,8 @@ const ObjectFieldTemplate = ({ + className={element.hidden ? "d-none" : undefined} + > {element.content} ))} @@ -60,8 +78,8 @@ const ObjectFieldTemplate = ({ /> - ) : null } - + ) : null} + ); }; diff --git a/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts b/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts index 77c68a9a40..b7e3ae6245 100644 --- a/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts +++ b/packages/bootstrap-4/src/ObjectFieldTemplate/index.ts @@ -1,2 +1,2 @@ -export { default } from './ObjectFieldTemplate'; -export * from './ObjectFieldTemplate'; +export { default } from "./ObjectFieldTemplate"; +export * from "./ObjectFieldTemplate"; diff --git a/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx b/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx deleted file mode 100644 index 2f5002421f..0000000000 --- a/packages/bootstrap-4/src/PasswordWidget/PasswordWidget.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; - -import Form from "react-bootstrap/Form"; - -import { WidgetProps } from "@rjsf/core"; - -const PasswordWidget = ({ - id, - required, - readonly, - disabled, - value, - label, - onFocus, - onBlur, - onChange, - options, - autofocus, - schema, - rawErrors = [], -}: WidgetProps) => { - const _onChange = ({ - target: { value }, - }: React.ChangeEvent) => - onChange(value === "" ? options.emptyValue : value); - const _onBlur = ({ target: { value } }: React.FocusEvent) => - onBlur(id, value); - const _onFocus = ({ - target: { value }, - }: React.FocusEvent) => onFocus(id, value); - - return ( - - 0 ? "text-danger" : ""}> - {label || schema.title} - {(label || schema.title) && required ? "*" : null} - - 0 ? "is-invalid" : ""} - required={required} - disabled={disabled} - readOnly={readonly} - type="password" - value={value ? value : ""} - onFocus={_onFocus} - onBlur={_onBlur} - onChange={_onChange} - /> - - ); -}; - -export default PasswordWidget; diff --git a/packages/bootstrap-4/src/PasswordWidget/index.ts b/packages/bootstrap-4/src/PasswordWidget/index.ts deleted file mode 100644 index 84fd5e9026..0000000000 --- a/packages/bootstrap-4/src/PasswordWidget/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './PasswordWidget'; -export * from './PasswordWidget'; diff --git a/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx b/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx index 0518dabdeb..3940ec61ce 100644 --- a/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx +++ b/packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx @@ -2,7 +2,7 @@ import React from "react"; import Form from "react-bootstrap/Form"; -import { WidgetProps } from "@rjsf/core"; +import { WidgetProps, getUiOptions } from "@rjsf/utils"; const RadioWidget = ({ id, @@ -19,6 +19,7 @@ const RadioWidget = ({ uiSchema, }: WidgetProps) => { const { enumOptions, enumDisabled } = options; + const uiOptions = getUiOptions(uiSchema); const _onChange = ({ target: { value }, @@ -35,8 +36,8 @@ const RadioWidget = ({ return ( - {uiSchema["ui:title"] || schema.title || label} - {(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null} + {uiOptions.title || schema.title || label} + {(label || uiOptions.title || schema.title) && required ? "*" : null} {(enumOptions as any).map((option: any, i: number) => { const itemDisabled = diff --git a/packages/bootstrap-4/src/RadioWidget/index.ts b/packages/bootstrap-4/src/RadioWidget/index.ts index 10292dc565..f0568f0d81 100644 --- a/packages/bootstrap-4/src/RadioWidget/index.ts +++ b/packages/bootstrap-4/src/RadioWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './RadioWidget'; -export * from './RadioWidget'; +export { default } from "./RadioWidget"; +export * from "./RadioWidget"; diff --git a/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx b/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx index 206b487825..f75d3756c9 100644 --- a/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx +++ b/packages/bootstrap-4/src/RangeWidget/RangeWidget.tsx @@ -1,56 +1,17 @@ import React from "react"; +import { getTemplate, WidgetProps } from "@rjsf/utils"; -import Form from "react-bootstrap/Form"; - -import { utils } from "@rjsf/core"; -import { WidgetProps } from "@rjsf/core"; - -const { rangeSpec } = utils; - -const RangeWidget = ({ - value, - readonly, - disabled, - onBlur, - onFocus, - options, - schema, - onChange, - required, - label, - id, - uiSchema, -}: WidgetProps) => { - let sliderProps = { value, label, id, ...rangeSpec(schema) }; - - const _onChange = ({ - target: { value }, - }: React.ChangeEvent) => - onChange(value === "" ? options.emptyValue : value); - const _onBlur = ({ target: { value } }: React.FocusEvent) => - onBlur(id, value); - const _onFocus = ({ - target: { value }, - }: React.FocusEvent) => onFocus(id, value); - +const RangeWidget = (props: WidgetProps) => { + const { value, label, options, registry } = props; + const BaseInputTemplate = getTemplate<"BaseInputTemplate">( + "BaseInputTemplate", + registry, + options + ); return ( - - - {uiSchema["ui:title"] || schema.title || label} - {(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null} - - + {value} - + ); }; diff --git a/packages/bootstrap-4/src/RangeWidget/index.ts b/packages/bootstrap-4/src/RangeWidget/index.ts index d8c49226c6..70a8e7601a 100644 --- a/packages/bootstrap-4/src/RangeWidget/index.ts +++ b/packages/bootstrap-4/src/RangeWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './RangeWidget'; -export * from './RangeWidget'; +export { default } from "./RangeWidget"; +export * from "./RangeWidget"; diff --git a/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx b/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx index 907dc4d39e..cd96122fda 100644 --- a/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx +++ b/packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx @@ -2,42 +2,7 @@ import React from "react"; import Form from "react-bootstrap/Form"; -import { WidgetProps } from "@rjsf/core"; -import { utils } from "@rjsf/core"; - -const { asNumber, guessType } = utils; - -const nums = new Set(["number", "integer"]); - -/** - * This is a silly limitation in the DOM where option change event values are - * always retrieved as strings. - */ -const processValue = (schema: any, value: any) => { - // "enum" is a reserved word, so only "type" and "items" can be destructured - const { type, items } = schema; - if (value === "") { - return undefined; - } else if (type === "array" && items && nums.has(items.type)) { - return value.map(asNumber); - } else if (type === "boolean") { - return value === "true"; - } else if (type === "number") { - return asNumber(value); - } - - // If type is undefined, but an enum is present, try and infer the type from - // the enum values - if (schema.enum) { - if (schema.enum.every((x: any) => guessType(x) === "number")) { - return asNumber(value); - } else if (schema.enum.every((x: any) => guessType(x) === "boolean")) { - return value === "true"; - } - } - - return value; -}; +import { processSelectValue, WidgetProps } from "@rjsf/utils"; const SelectWidget = ({ schema, @@ -62,7 +27,7 @@ const SelectWidget = ({ function getValue( event: React.FocusEvent | React.ChangeEvent | any, - multiple: Boolean + multiple?: boolean ) { if (multiple) { return [].slice @@ -76,7 +41,10 @@ const SelectWidget = ({ return ( - 0 ? "text-danger" : ""}> + 0 ? "text-danger" : ""} + htmlFor={id} + > {label || schema.title} {(label || schema.title) && required ? "*" : null} @@ -87,28 +55,28 @@ const SelectWidget = ({ value={typeof value === "undefined" ? emptyValue : value} required={required} multiple={multiple} - disabled={disabled} - readOnly={readonly} + disabled={disabled || readonly} autoFocus={autofocus} className={rawErrors.length > 0 ? "is-invalid" : ""} onBlur={ onBlur && ((event: React.FocusEvent) => { const newValue = getValue(event, multiple); - onBlur(id, processValue(schema, newValue)); + onBlur(id, processSelectValue(schema, newValue, options)); }) } onFocus={ onFocus && ((event: React.FocusEvent) => { const newValue = getValue(event, multiple); - onFocus(id, processValue(schema, newValue)); + onFocus(id, processSelectValue(schema, newValue, options)); }) } onChange={(event: React.ChangeEvent) => { const newValue = getValue(event, multiple); - onChange(processValue(schema, newValue)); - }}> + onChange(processSelectValue(schema, newValue, options)); + }} + > {!multiple && schema.default === undefined && ( )} diff --git a/packages/bootstrap-4/src/SelectWidget/index.ts b/packages/bootstrap-4/src/SelectWidget/index.ts index e37ea725b8..91a604a36c 100644 --- a/packages/bootstrap-4/src/SelectWidget/index.ts +++ b/packages/bootstrap-4/src/SelectWidget/index.ts @@ -1,2 +1,2 @@ -export { default } from './SelectWidget'; -export * from './SelectWidget'; +export { default } from "./SelectWidget"; +export * from "./SelectWidget"; diff --git a/packages/bootstrap-4/src/SubmitButton/SubmitButton.tsx b/packages/bootstrap-4/src/SubmitButton/SubmitButton.tsx index 97ad68ed3a..fc88cc93f0 100644 --- a/packages/bootstrap-4/src/SubmitButton/SubmitButton.tsx +++ b/packages/bootstrap-4/src/SubmitButton/SubmitButton.tsx @@ -1,12 +1,19 @@ -import { utils, WidgetProps } from "@rjsf/core"; import React from "react"; import Button from "react-bootstrap/Button"; -const { getSubmitButtonOptions } = utils; -const SubmitButton: React.FC = props => { - const { submitText, norender, props: submitButtonProps }= getSubmitButtonOptions(props.uiSchema); - if(norender) return null; - return (
-
diff --git a/packages/bootstrap-4/src/SubmitButton/index.ts b/packages/bootstrap-4/src/SubmitButton/index.ts index f676497ba2..467dba2499 100644 --- a/packages/bootstrap-4/src/SubmitButton/index.ts +++ b/packages/bootstrap-4/src/SubmitButton/index.ts @@ -1,2 +1,2 @@ -export { default } from './SubmitButton'; -export * from './SubmitButton'; +export { default } from "./SubmitButton"; +export * from "./SubmitButton"; diff --git a/packages/bootstrap-4/src/Templates/Templates.ts b/packages/bootstrap-4/src/Templates/Templates.ts new file mode 100644 index 0000000000..42fc7cf9d5 --- /dev/null +++ b/packages/bootstrap-4/src/Templates/Templates.ts @@ -0,0 +1,29 @@ +import AddButton from "../AddButton"; +import ArrayFieldItemTemplate from "../ArrayFieldItemTemplate"; +import ArrayFieldTemplate from "../ArrayFieldTemplate"; +import BaseInputTemplate from "../BaseInputTemplate/BaseInputTemplate"; +import DescriptionField from "../DescriptionField"; +import ErrorList from "../ErrorList"; +import { MoveDownButton, MoveUpButton, RemoveButton } from "../IconButton"; +import FieldTemplate from "../FieldTemplate"; +import ObjectFieldTemplate from "../ObjectFieldTemplate"; +import SubmitButton from "../SubmitButton"; +import TitleField from "../TitleField"; + +export default { + ArrayFieldItemTemplate, + ArrayFieldTemplate, + BaseInputTemplate, + ButtonTemplates: { + AddButton, + MoveDownButton, + MoveUpButton, + RemoveButton, + SubmitButton, + }, + DescriptionFieldTemplate: DescriptionField, + ErrorListTemplate: ErrorList, + FieldTemplate, + ObjectFieldTemplate, + TitleFieldTemplate: TitleField, +}; diff --git a/packages/bootstrap-4/src/Templates/index.ts b/packages/bootstrap-4/src/Templates/index.ts new file mode 100644 index 0000000000..0c3e935ca4 --- /dev/null +++ b/packages/bootstrap-4/src/Templates/index.ts @@ -0,0 +1,2 @@ +export { default } from "./Templates"; +export * from "./Templates"; diff --git a/packages/bootstrap-4/src/TextWidget/index.ts b/packages/bootstrap-4/src/TextWidget/index.ts deleted file mode 100644 index fc1bc51a03..0000000000 --- a/packages/bootstrap-4/src/TextWidget/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './TextWidget'; -export * from './TextWidget'; diff --git a/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx b/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx index 9bea10d758..eca882cd31 100644 --- a/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx +++ b/packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { WidgetProps } from "@rjsf/core"; +import { getUiOptions, WidgetProps } from "@rjsf/utils"; import FormControl from "react-bootstrap/FormControl"; import InputGroup from "react-bootstrap/InputGroup"; @@ -25,6 +25,7 @@ const TextareaWidget = ({ rawErrors = [], uiSchema, }: CustomWidgetProps) => { + const uiOptions = getUiOptions(uiSchema); const _onChange = ({ target: { value }, }: React.ChangeEvent) => @@ -39,11 +40,12 @@ const TextareaWidget = ({ return ( <>