diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts index 821ffb0e81..8a31574806 100644 --- a/packages/core/index.d.ts +++ b/packages/core/index.d.ts @@ -274,7 +274,7 @@ declare module '@rjsf/core' { export function withTheme( themeProps: ThemeProps, - ): React.ComponentClass> | React.StatelessComponent>; + ): React.ForwardRefExoticComponent> & React.RefAttributes>>; export type AddButtonProps = { className: string; diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 9cfd5112ac..4a8decf83e 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -473,6 +473,12 @@ "dev": true, "optional": true }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -4010,7 +4016,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -4047,7 +4053,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -5023,7 +5029,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -5036,7 +5042,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -5451,7 +5457,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -6842,6 +6848,12 @@ "dev": true, "optional": true }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, @@ -7773,13 +7785,6 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true, - "optional": true - }, "inquirer": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", @@ -10620,7 +10625,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -12557,7 +12562,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -13448,6 +13453,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", + "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "dev": true + }, "ua-parser-js": { "version": "0.7.18", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index d5bdcb10f7..c1b1b92ea5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,9 +15,10 @@ "publish-to-npm": "npm run build && npm publish", "start": "concurrently \"npm:build:* -- --watch\"", "tdd": "cross-env NODE_ENV=test mocha --require @babel/register --watch --require ./test/setup-jsdom.js test/**/*_test.js", - "test": "cross-env BABEL_ENV=test NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js test/**/*_test.js", + "test": "concurrently \"cross-env BABEL_ENV=test NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js test/**/*_test.js\" \"npm:test-type\"", "test-coverage": "cross-env NODE_ENV=test nyc --reporter=lcov mocha --require @babel/register --require ./test/setup-jsdom.js test/**/*_test.js", - "test-debug": "cross-env NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js --debug-brk --inspect test/Form_test.js" + "test-debug": "cross-env NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js --debug-brk --inspect test/Form_test.js", + "test-type": "tsc" }, "lint-staged": { "{src,test}/**/*.js": [ @@ -105,6 +106,7 @@ "rimraf": "^2.5.4", "sinon": "^9.0.2", "style-loader": "^0.13.1", + "typescript": "^4.2.3", "webpack": "^4.42.1", "webpack-cli": "^3.1.2", "webpack-dev-middleware": "^3.4.0", diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000000..471f698dae --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react" + }, + "files": [ + "index.d.ts", + "typing_test.tsx" + ] +} diff --git a/packages/core/typing_test.tsx b/packages/core/typing_test.tsx new file mode 100644 index 0000000000..6fc883b77d --- /dev/null +++ b/packages/core/typing_test.tsx @@ -0,0 +1,296 @@ +// Originally from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/@rjsf/core/react-jsonschema-form-tests.tsx + +import * as React from 'react'; +import Form, { + UiSchema, + ErrorListProps, + FieldProps, + WidgetProps, + ErrorSchema, + withTheme, + FieldTemplateProps, + ArrayFieldTemplateProps, + ObjectFieldTemplateProps, + IdSchema, + PathSchema, + utils +} from '@rjsf/core'; +import SchemaField, { SchemaFieldProps } from '@rjsf/core/lib/components/fields/SchemaField'; +import validateFormData from '@rjsf/core/lib/validate'; +import { JSONSchema7 } from 'json-schema'; + +const { + ADDITIONAL_PROPERTY_FLAG, + allowAdditionalItems, + isFixedItems, + stubExistingAdditionalProperties, + retrieveSchema, +} = utils; + +// example taken from the react-jsonschema-form playground: +// https://github.com/mozilla-services/react-jsonschema-form/blob/fedd830294417969d88e38fb9f6b3a85e6ad105e/playground/samples/simple.js + +const schema: JSONSchema7 = { + title: 'A registration form', + type: 'object', + required: ['firstName', 'lastName'], + properties: { + firstName: { + type: 'string', + title: 'First name', + }, + lastName: { + type: 'string', + title: 'Last name', + }, + age: { + type: 'integer', + title: 'Age', + }, + bio: { + type: 'string', + title: 'Bio', + }, + password: { + type: 'string', + title: 'Password', + minLength: 3, + }, + }, +}; + +const ExampleFieldTemplate = (_props: FieldTemplateProps) => null; + +const ExampleArrayFieldTemplate = ({ items }: ArrayFieldTemplateProps) => ( +
+ {items.map(element => ( +
{element.children}
+ ))} +
+); + +const ExampleObjectFieldTemplate = (_props: ObjectFieldTemplateProps) => null; + +const uiSchema: UiSchema = { + age: { + 'ui:widget': 'updown', + }, + bio: { + 'ui:widget': 'textarea', + }, + password: { + 'ui:widget': 'password', + 'ui:help': 'Hint: Make it strong!', + }, + date: { + 'ui:widget': 'alt-datetime', + }, + 'ui:FieldTemplate': ExampleFieldTemplate, + 'ui:ArrayFieldTemplate': ExampleArrayFieldTemplate, + 'ui:ObjectFieldTemplate': ExampleObjectFieldTemplate, +}; + +interface IExampleState { + formData: any; +} + +export default function ErrorListExample(props: ErrorListProps) { + const { errors } = props; + return ( +
+
+

Errors

+
+
    + {errors.map((error, i) => { + return ( +
  • + {error.stack} +
  • + ); + })} +
+
+ ); +} + +export class Example extends React.Component { + public state: IExampleState = { + formData: { + firstName: 'Chuck', + lastName: 'Norris', + age: 75, + bio: 'Roundhouse kicking asses since 1940', + password: 'noneed', + }, + }; + + constructor(props: any) { + super(props); + } + + public render() { + return ( +
+ { +
this.setState({ formData })} + customFormats={{ + 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/, + }} + /> + } +
+ ); + } +} + +export class ExampleSchemaField extends React.Component { + constructor(props: SchemaFieldProps) { + super(props); + } + + public render() { + return ; + } +} + +interface FuncExampleProps { + formData: object; + onError: (e: ErrorSchema) => void; + onChange: (e: any) => void; +} + +export const FuncExample = (props: FuncExampleProps) => { + const { formData, onChange, onError } = props; + return ( + { + onChange(formData); + errorSchema && onError(errorSchema); + }} + /> + ); +}; + +export const BooleanCustomWidget: React.SFC = props => ( + props.onFocus('id', true)} onBlur={() => props.onFocus('id', true)} /> +); + +export const NumberCustomWidget: React.SFC = props => ( + props.onFocus('id', 0)} onBlur={() => props.onFocus('id', 0)} /> +); + +export const StringCustomWidget: React.SFC = props => ( + props.onFocus('id', 'value')} onBlur={() => props.onFocus('id', 'value')} /> +); + +export const NullCustomWidget: React.SFC = props => ( + props.onFocus('id', null)} onBlur={() => props.onFocus('id', null)} /> +); + +export const withThemeExample = () => { + const Form = withTheme({ + showErrorList: false, + noValidate: false, + noHtml5Validate: false, + }); + const forwardedRef = React.useRef>(null); + + return ; +}; + +export const additionalPropertyFlagExample = () => { + return ADDITIONAL_PROPERTY_FLAG; +}; + +export const ExternalFormSubmissionExample = () => { + const formRef = React.useRef>(null); + + return ( + + + + ); +}; + +export const allowAdditionalItemsExample = (schema: JSONSchema7) => { + return allowAdditionalItems(schema); +}; + +export const isFixedItemsExample = (schema: JSONSchema7) => { + return isFixedItems(schema); +}; + +export const stubExistingAdditionalPropertiesExample = ( + schema: JSONSchema7, + definitions: { [name: string]: any }, + formData: any, +) => { + return stubExistingAdditionalProperties(schema, definitions, formData); +}; + +export const retrieveSchemaExample = (schema: JSONSchema7) => { + return retrieveSchema(schema); +}; + +export const getValidationDataExample = (formData: any, schema: JSONSchema7) => { + return validateFormData(formData, schema); +}; + +export const customFieldExample = (props: FieldProps) => { + const customProps: Pick = { + onBlur: (id, value) => { + return props.onBlur(id, value); + }, + onChange: (formData, errorSchema) => { + return props.onChange(formData, errorSchema); + }, + }; + return ; +}; + +export const omitExtraDataExample = (schema: JSONSchema7) => { + return
; +}; + +export const customTagName = (schema: JSONSchema7) => { + return ; +}; + +const TestForm = (props: React.ComponentProps<'form'>) => ; +export const customTagNameUsingComponent = (schema: JSONSchema7) => { + return ; +}; + +const idSchema: IdSchema<{ test: {} }> = { + $id: 'test', + test: { + $id: 'test', + }, +}; +void idSchema.$id; +void idSchema.test.$id; + +const pathSchema: PathSchema<{ test: {} }> = { + $name: 'test', + test: { + $name: 'test', + }, +}; +void pathSchema.$name; +void pathSchema.test.$name;