From 17656c149a3832ef27ff9609c9df7931ee0da658 Mon Sep 17 00:00:00 2001 From: Divlo Date: Thu, 25 Aug 2022 23:24:40 +0200 Subject: [PATCH] feat: add form validation --- README.md | 52 ++++++- example/package-lock.json | 8 ++ package-lock.json | 59 +++++--- package.json | 9 +- src/{index.tsx => components/Form.tsx} | 2 +- src/hooks/useFetchState.ts | 15 ++ src/hooks/useForm.ts | 144 +++++++++++++++++++ src/index.ts | 4 + src/utils/ajv.ts | 24 ++++ src/utils/handleCheckboxBoolean.ts | 25 ++++ src/utils/handleOptionalEmptyStringToNull.ts | 17 +++ src/utils/types.ts | 3 + tsup.config.js | 2 +- 13 files changed, 333 insertions(+), 31 deletions(-) rename src/{index.tsx => components/Form.tsx} (97%) create mode 100644 src/hooks/useFetchState.ts create mode 100644 src/hooks/useForm.ts create mode 100644 src/index.ts create mode 100644 src/utils/ajv.ts create mode 100644 src/utils/handleCheckboxBoolean.ts create mode 100644 src/utils/handleOptionalEmptyStringToNull.ts create mode 100644 src/utils/types.ts diff --git a/README.md b/README.md index 1453acd..cc98645 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ **react-component-form** is a lightweight form component for [React.js](https://reactjs.org/), it allows you to get the inputs values without state thanks to `onChange` or `onSubmit` props. -Demo : [https://divlo.github.io/react-component-form/](https://divlo.github.io/react-component-form/). +There is also a [React Hooks](https://reactjs.org/docs/hooks-intro.html) to be used in combination with the `
` component to validate the data with [Ajv JSON schema validator](https://ajv.js.org/), see [advanced usage](#%EF%B8%8F-advanced-usage). + +Demo: [https://divlo.github.io/react-component-form/](https://divlo.github.io/react-component-form/). ## 💾 Install @@ -32,9 +34,12 @@ npm install --save react-component-form ## ⚙️ Usage +_Note : The examples use TypeScript, but obviously you can use JavaScript. Be aware that `HandleForm` is the type definition for the `onChange` and `onSubmit` props._ + ```tsx import React from 'react' -import { Form, HandleForm } from 'react-component-form' +import { Form } from 'react-component-form' +import type { HandleForm } from 'react-component-form' const Example = () => { const handleSubmit: HandleForm = (formData, formElement) => { @@ -51,15 +56,52 @@ const Example = () => { } ``` -_Note : The example use TypeScript, but obviously you can use JavaScript. Be aware that `HandleForm` is the type definition for the `onChange` and `onSubmit` props._ - Basically you have access to the same props of the HTML `form` tag in React, but the onSubmit and the onChange props are differents. -Instead to get the `event` param you get `formData` and `formElement` params : +Instead to get the `event` param you get `formData` and `formElement` parameters: - `formData`: It's an object where the keys are the name of your inputs and the current value. Behind the scene, it uses the [FormData](https://developer.mozilla.org/docs/Web/API/FormData) constructor. - `formElement`: It's the actual HTML form element in the DOM so for example you can access the `.reset()` method on a [HTMLFormElement](https://developer.mozilla.org/docs/Web/API/HTMLFormElement). +## ⚙️ Advanced Usage + +This example shows how to use the `` component with `useForm` hook to validate the data with [Ajv JSON schema validator](https://ajv.js.org/). + +You can see a more detailled example in the [./example](./example) folder. + +```tsx +import React from 'react' +import { Form, useForm } from 'react-component-form' +import type { HandleSubmitCallback } from 'react-component-form' + +const schema = { + inputName: { + type: 'string', + required: true, + minLength: 3, + maxLength: 10 + } +} + +const Example = () => { + const { errors, handleSubmit } = useForm(schema) + + const onSubmit: HandleSubmitCallback = (formData, formElement) => { + console.log(formData) // { inputName: 'value of the input' } + formElement.reset() + } + + return ( + + + {errors.inputName != null &&

{errors.inputName[0].message}

} + + + + ) +} +``` + ## 💡 Contributing Anyone can help to improve the project, submit a Feature Request, a bug report or diff --git a/example/package-lock.json b/example/package-lock.json index 9287e35..f52c618 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -21,6 +21,11 @@ "..": { "version": "0.0.0-development", "license": "MIT", + "dependencies": { + "@sinclair/typebox": "0.24.28", + "ajv": "8.11.0", + "ajv-formats": "2.1.1" + }, "devDependencies": { "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", @@ -4356,12 +4361,15 @@ "requires": { "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", + "@sinclair/typebox": "0.24.28", "@testing-library/react": "13.3.0", "@types/jest": "28.1.8", "@types/react": "18.0.17", "@types/react-dom": "18.0.6", "@typescript-eslint/eslint-plugin": "5.35.1", "@typescript-eslint/parser": "5.35.1", + "ajv": "8.11.0", + "ajv-formats": "2.1.1", "editorconfig-checker": "4.0.2", "esbuild": "0.15.5", "esbuild-jest": "0.5.0", diff --git a/package-lock.json b/package-lock.json index 0c04ba4..0bccbcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,11 @@ "name": "react-component-form", "version": "0.0.0-development", "license": "MIT", + "dependencies": { + "@sinclair/typebox": "0.24.28", + "ajv": "8.11.0", + "ajv-formats": "2.1.1" + }, "devDependencies": { "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", @@ -2602,8 +2607,7 @@ "node_modules/@sinclair/typebox": { "version": "0.24.28", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", - "dev": true + "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==" }, "node_modules/@sinonjs/commons": { "version": "1.8.3", @@ -3217,7 +3221,6 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3229,6 +3232,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -6122,8 +6141,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.2.0", @@ -9897,8 +9915,7 @@ "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -14081,7 +14098,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -14462,7 +14478,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -16621,7 +16636,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -19041,8 +19055,7 @@ "@sinclair/typebox": { "version": "0.24.28", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", - "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==", - "dev": true + "integrity": "sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==" }, "@sinonjs/commons": { "version": "1.8.3", @@ -19523,7 +19536,6 @@ "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -19531,6 +19543,14 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -21631,8 +21651,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -24585,8 +24604,7 @@ "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -27565,8 +27583,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -27855,8 +27872,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "requires-port": { "version": "1.0.0", @@ -29543,7 +29559,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index fd6f3fa..b4b1525 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,11 @@ "peerDependencies": { "react": ">=16" }, + "dependencies": { + "@sinclair/typebox": "0.24.28", + "ajv": "8.11.0", + "ajv-formats": "2.1.1" + }, "devDependencies": { "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", @@ -46,6 +51,8 @@ "@typescript-eslint/eslint-plugin": "5.35.1", "@typescript-eslint/parser": "5.35.1", "editorconfig-checker": "4.0.2", + "esbuild": "0.15.5", + "esbuild-jest": "0.5.0", "eslint": "8.22.0", "eslint-config-conventions": "3.0.0", "eslint-config-prettier": "8.5.0", @@ -53,8 +60,6 @@ "eslint-plugin-prettier": "4.2.1", "eslint-plugin-promise": "6.0.1", "eslint-plugin-unicorn": "43.0.2", - "esbuild": "0.15.5", - "esbuild-jest": "0.5.0", "jest": "29.0.0", "jest-environment-jsdom": "29.0.0", "markdownlint-cli2": "0.5.1", diff --git a/src/index.tsx b/src/components/Form.tsx similarity index 97% rename from src/index.tsx rename to src/components/Form.tsx index f5c0b81..5ae4e36 100644 --- a/src/index.tsx +++ b/src/components/Form.tsx @@ -9,7 +9,7 @@ export type HandleForm = ( formElement: HTMLFormElement ) => void | Promise -export interface ReactFormProps +interface ReactFormProps extends Omit, 'onSubmit' | 'onChange'> {} export interface FormProps extends ReactFormProps { diff --git a/src/hooks/useFetchState.ts b/src/hooks/useFetchState.ts new file mode 100644 index 0000000..b65ea29 --- /dev/null +++ b/src/hooks/useFetchState.ts @@ -0,0 +1,15 @@ +import { useState } from 'react' + +export const fetchState = ['idle', 'loading', 'error', 'success'] as const + +export type FetchState = typeof fetchState[number] + +export const useFetchState = ( + initialFetchState: FetchState = 'idle' +): [ + fetchState: FetchState, + setFetchState: React.Dispatch> +] => { + const [fetchState, setFetchState] = useState(initialFetchState) + return [fetchState, setFetchState] +} diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts new file mode 100644 index 0000000..2613c23 --- /dev/null +++ b/src/hooks/useForm.ts @@ -0,0 +1,144 @@ +import { useMemo, useState } from 'react' +import { Static, TObject, TProperties, Type } from '@sinclair/typebox' +import type { ErrorObject } from 'ajv' + +import type { HandleForm } from '../components/Form' +import { FetchState, useFetchState } from './useFetchState' +import { ajv } from '../utils/ajv' +import { handleCheckboxBoolean } from '../utils/handleCheckboxBoolean' +import { handleOptionalEmptyStringToNull } from '../utils/handleOptionalEmptyStringToNull' + +export type Error = ErrorObject + +export type ErrorsObject = { + [key in keyof Partial]: Error[] | undefined +} + +export type HandleSubmitCallback = ( + formData: Static>, + formElement: HTMLFormElement +) => Promise | null> + +export type HandleSubmit = ( + callback: HandleSubmitCallback +) => HandleForm + +export interface GlobalMessage { + type: 'error' | 'success' + value?: string + properties?: undefined +} + +export interface PropertiesMessage { + type: 'error' + value?: string + properties: { [key in keyof Partial]: string } +} + +export type Message = + | GlobalMessage + | PropertiesMessage + +export interface UseFormResult { + handleSubmit: HandleSubmit + + readonly fetchState: FetchState + setFetchState: React.Dispatch> + + /** + * Global message of the form (not specific to a property). + */ + readonly message: string | null + setMessage: React.Dispatch> + + /** + * Errors for each property. + * + * The array will always have at least one element (never empty) in case of errors. + * + * `undefined` means no errors. + */ + readonly errors: ErrorsObject +} + +export const useForm = ( + validationSchema: K +): UseFormResult => { + const validationSchemaObject = useMemo(() => { + return Type.Object(validationSchema) + }, [validationSchema]) + + const [fetchState, setFetchState] = useFetchState() + const [message, setMessage] = useState(null) + const [errors, setErrors] = useState>( + {} as any + ) + + const validate = useMemo(() => { + return ajv.compile(validationSchemaObject) + }, [validationSchemaObject]) + + const handleSubmit: HandleSubmit = (callback) => { + return async (formData, formElement) => { + setErrors({} as any) + setMessage(null) + formData = handleOptionalEmptyStringToNull( + formData, + validationSchemaObject.required + ) + formData = handleCheckboxBoolean(formData, validationSchemaObject) + const isValid = validate(formData) + if (!isValid) { + setFetchState('error') + const errors: ErrorsObject = {} as any + for (const property in validationSchemaObject.properties) { + const errorsForProperty = validate.errors?.filter((error) => { + return error.instancePath === `/${property}` + }) + errors[property as keyof typeof validationSchema] = + errorsForProperty != null && errorsForProperty.length > 0 + ? errorsForProperty + : undefined + } + setErrors(errors) + } else { + setErrors({} as any) + setFetchState('loading') + const message = await callback( + formData as Static>, + formElement + ) + if (message != null) { + const { value = null, type, properties } = message + setMessage(value) + setFetchState(type) + if (type === 'error') { + const propertiesErrors: ErrorsObject = + {} as any + for (const property in properties) { + propertiesErrors[property] = [ + { + keyword: 'message', + message: properties[property], + instancePath: `/${property}`, + schemaPath: `#/properties/${property}/message`, + params: {} + } + ] + } + setErrors(propertiesErrors) + } + } + } + } + } + + return { + handleSubmit, + fetchState, + setFetchState, + message, + setMessage, + errors + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..62c763e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +export * from './components/Form' +export * from './hooks/useFetchState' +export * from './hooks/useForm' +export * from './utils/ajv' diff --git a/src/utils/ajv.ts b/src/utils/ajv.ts new file mode 100644 index 0000000..e97a6a6 --- /dev/null +++ b/src/utils/ajv.ts @@ -0,0 +1,24 @@ +import addFormats from 'ajv-formats' +import Ajv from 'ajv' + +export const ajv = addFormats( + new Ajv({ + allErrors: true + }), + [ + 'date-time', + 'time', + 'date', + 'email', + 'hostname', + 'ipv4', + 'ipv6', + 'uri', + 'uri-reference', + 'uuid', + 'uri-template', + 'json-pointer', + 'relative-json-pointer', + 'regex' + ] +) diff --git a/src/utils/handleCheckboxBoolean.ts b/src/utils/handleCheckboxBoolean.ts new file mode 100644 index 0000000..e9d8715 --- /dev/null +++ b/src/utils/handleCheckboxBoolean.ts @@ -0,0 +1,25 @@ +import type { TObject } from '@sinclair/typebox' + +import type { ObjectAny } from './types' + +export const handleCheckboxBoolean = ( + object: ObjectAny, + validateSchemaObject: TObject +): ObjectAny => { + const booleanProperties: string[] = [] + for (const property in validateSchemaObject.properties) { + const rule = validateSchemaObject.properties[property] + if (rule.type === 'boolean') { + booleanProperties.push(property) + } + } + for (const booleanProperty of booleanProperties) { + if (object[booleanProperty] == null) { + object[booleanProperty] = + validateSchemaObject.properties[booleanProperty].default + } else { + object[booleanProperty] = object[booleanProperty] === 'on' + } + } + return object +} diff --git a/src/utils/handleOptionalEmptyStringToNull.ts b/src/utils/handleOptionalEmptyStringToNull.ts new file mode 100644 index 0000000..278a0c4 --- /dev/null +++ b/src/utils/handleOptionalEmptyStringToNull.ts @@ -0,0 +1,17 @@ +export const handleOptionalEmptyStringToNull = ( + object: K, + required: string[] = [] +): K => { + return Object.fromEntries( + Object.entries(object).map(([key, value]) => { + if ( + typeof value === 'string' && + value.length === 0 && + !required.includes(key) + ) { + return [key, null] + } + return [key, value] + }) + ) as K +} diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..bd2c728 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,3 @@ +export interface ObjectAny { + [key: string]: any +} diff --git a/tsup.config.js b/tsup.config.js index 7658762..a4d26f7 100644 --- a/tsup.config.js +++ b/tsup.config.js @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup' export default defineConfig({ - entry: ['src/index.tsx'], + entry: ['src/index.ts'], sourcemap: true, clean: true, platform: 'browser',