diff --git a/.eslintrc.yml b/.eslintrc.yml index 2adb6f0..37308f8 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,8 +1,7 @@ extends: + - cheminfo-typescript - cheminfo-react - plugin:storybook/recommended rules: react/jsx-no-bind: off consistent-return: off -env: - jest: true diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 2e2007c..71e0af2 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -10,3 +10,5 @@ jobs: nodejs: # Documentation: https://github.com/zakodium/workflows#nodejs-ci uses: zakodium/workflows/.github/workflows/nodejs.yml@nodejs-v1 + with: + lint-check-types: true diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml deleted file mode 100644 index d8815c9..0000000 --- a/.github/workflows/storybook.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Deploy storybook on GitHub pages - -on: - push: - branches: - - main - -env: - NODE_VERSION: 20.x - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - name: Install dependencies - run: npm install - - name: Build storybook - run: npm run build-storybook - - name: Deploy to GitHub pages - uses: JamesIves/github-pages-deploy-action@releases/v4 - with: - token: ${{ secrets.BOT_TOKEN }} - branch: gh-pages - folder: storybook-static - clean: true diff --git a/.storybook/main.js b/.storybook/main.mjs similarity index 56% rename from .storybook/main.js rename to .storybook/main.mjs index 6f9613e..ceeeece 100644 --- a/.storybook/main.js +++ b/.storybook/main.mjs @@ -1,15 +1,21 @@ -module.exports = { - stories: ['../stories/**/*.stories.js'], +export default { + stories: ['../stories/**/*.stories.tsx'], + addons: [ '@storybook/addon-storysource', '@storybook/addon-links', '@storybook/addon-essentials', ], + framework: { - name: '@storybook/react-webpack5', + name: '@storybook/react-vite', options: { fastRefresh: true, strictMode: true, }, }, + + typescript: { + reactDocgen: 'react-docgen-typescript', + }, }; diff --git a/README.md b/README.md index 0d90d41..023f862 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This library is available in three versions, like openchemlib: `react-ocl/minima ## Documentation -See https://zakodium-oss.github.io/react-ocl/ for detailed usage examples. +See https://react-ocl.pages.dev for detailed usage examples. [npm-image]: https://img.shields.io/npm/v/react-ocl.svg [npm-url]: https://npmjs.org/package/react-ocl diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 7064ce3..0000000 --- a/babel.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const config = { - presets: ['@babel/react'], - plugins: [], -}; - -if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'cjs') { - config.plugins.push('@babel/transform-modules-commonjs'); -} - -module.exports = config; diff --git a/base.cjs b/base.cjs new file mode 100644 index 0000000..f5922f2 --- /dev/null +++ b/base.cjs @@ -0,0 +1,14 @@ +'use strict'; + +const BaseIdcodeSvgRenderer = + require('./lib-cjs/components/IdcodeSvgRenderer.js').default; +const BaseMolfileSvgRenderer = + require('./lib-cjs/components/MolfileSvgRenderer.js').default; +const BaseSmilesSvgRenderer = + require('./lib-cjs/components/SmilesSvgRenderer.js').default; + +module.exports = { + SmilesSvgRenderer: BaseSmilesSvgRenderer, + MolfileSvgRenderer: BaseMolfileSvgRenderer, + IdcodeSvgRenderer: BaseIdcodeSvgRenderer, +}; diff --git a/base.d.ts b/base.d.ts index a4b6222..f008f0f 100644 --- a/base.d.ts +++ b/base.d.ts @@ -1,24 +1,12 @@ -import { - IIdcodeSvgRendererProps as IdcodeProps, - IMolfileSvgRendererProps as MolfileProps, - ISmilesSvgRendererProps as SmilesProps, -} from './types'; - -export interface IIdcodeSvgRendererProps extends IdcodeProps { - OCL: any; -} -export function IdcodeSvgRenderer( - props: IIdcodeSvgRendererProps, -): JSX.Element; - -export interface IMolfileSvgRendererProps extends MolfileProps { - OCL: any; -} -export function MolfileSvgRenderer( - props: IMolfileSvgRendererProps, -): JSX.Element; - -export interface ISmilesSvgRendererProps extends SmilesProps { - OCL: any; -} -export function SmilesSvgRenderer(props: ISmilesSvgRendererProps): JSX.Element; +export { + type BaseIdcodeSvgRendererProps, + default as BaseIdcodeSvgRenderer, +} from './lib/components/IdcodeSvgRenderer.d.ts'; +export { + type BaseMolfileSvgRendererProps, + default as BaseMolfileSvgRenderer, +} from './lib/components/MolfileSvgRenderer.d.ts'; +export { + type BaseSmilesSvgRendererProps, + default as BaseSmilesSvgRenderer, +} from './lib/components/SmilesSvgRenderer.d.ts'; diff --git a/base.js b/base.js deleted file mode 100644 index 6980f27..0000000 --- a/base.js +++ /dev/null @@ -1,12 +0,0 @@ -const BaseIdcodeSvgRenderer = require('./lib-cjs/components/IdcodeSvgRenderer') - .default; -const BaseMolfileSvgRenderer = require('./lib-cjs/components/MolfileSvgRenderer') - .default; -const BaseSmilesSvgRenderer = require('./lib-cjs/components/SmilesSvgRenderer') - .default; - -module.exports = { - SmilesSvgRenderer: BaseSmilesSvgRenderer, - MolfileSvgRenderer: BaseMolfileSvgRenderer, - IdcodeSvgRenderer: BaseIdcodeSvgRenderer, -}; diff --git a/base.mjs b/base.mjs index 68cc934..973285e 100644 --- a/base.mjs +++ b/base.mjs @@ -1,6 +1,6 @@ -import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer'; -import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer'; -import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer'; +import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer.js'; +import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer.js'; +import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer.js'; export { BaseSmilesSvgRenderer as SmilesSvgRenderer, diff --git a/core.js b/core.cjs similarity index 62% rename from core.js rename to core.cjs index 47fb784..c1853eb 100644 --- a/core.js +++ b/core.cjs @@ -1,12 +1,14 @@ +'use strict'; + const OCL = require('openchemlib/core'); const React = require('react'); -const BaseIdcodeSvgRenderer = require('./lib-cjs/components/IdcodeSvgRenderer') - .default; -const BaseMolfileSvgRenderer = require('./lib-cjs/components/MolfileSvgRenderer') - .default; -const BaseSmilesSvgRenderer = require('./lib-cjs/components/SmilesSvgRenderer') - .default; +const BaseIdcodeSvgRenderer = + require('./lib-cjs/components/IdcodeSvgRenderer.js').default; +const BaseMolfileSvgRenderer = + require('./lib-cjs/components/MolfileSvgRenderer.js').default; +const BaseSmilesSvgRenderer = + require('./lib-cjs/components/SmilesSvgRenderer.js').default; function SmilesSvgRenderer(props) { return React.createElement(BaseSmilesSvgRenderer, { OCL, ...props }); diff --git a/core.d.ts b/core.d.ts index d37caaf..216f28a 100644 --- a/core.d.ts +++ b/core.d.ts @@ -1 +1 @@ -export * from './minimal'; +export * from './minimal.d.ts'; diff --git a/core.mjs b/core.mjs index e482bdd..1545693 100644 --- a/core.mjs +++ b/core.mjs @@ -1,9 +1,9 @@ import OCL from 'openchemlib/core'; import React from 'react'; -import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer'; -import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer'; -import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer'; +import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer.js'; +import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer.js'; +import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer.js'; export function SmilesSvgRenderer(props) { return React.createElement(BaseSmilesSvgRenderer, { OCL, ...props }); diff --git a/full.js b/full.cjs similarity index 57% rename from full.js rename to full.cjs index 9d39c25..90ba222 100644 --- a/full.js +++ b/full.cjs @@ -1,14 +1,16 @@ +'use strict'; + const OCL = require('openchemlib/full'); const React = require('react'); -const BaseIdcodeSvgRenderer = require('./lib-cjs/components/IdcodeSvgRenderer') - .default; -const BaseMolfileSvgRenderer = require('./lib-cjs/components/MolfileSvgRenderer') - .default; -const BaseSmilesSvgRenderer = require('./lib-cjs/components/SmilesSvgRenderer') - .default; -const BaseStructureEditor = require('./lib-cjs/components/StructureEditor') - .default; +const BaseIdcodeSvgRenderer = + require('./lib-cjs/components/IdcodeSvgRenderer.js').default; +const BaseMolfileSvgRenderer = + require('./lib-cjs/components/MolfileSvgRenderer.js').default; +const BaseSmilesSvgRenderer = + require('./lib-cjs/components/SmilesSvgRenderer.js').default; +const BaseStructureEditor = + require('./lib-cjs/components/StructureEditor.js').default; function SmilesSvgRenderer(props) { return React.createElement(BaseSmilesSvgRenderer, { OCL, ...props }); diff --git a/full.d.ts b/full.d.ts index 9dbf2ab..5a3257b 100644 --- a/full.d.ts +++ b/full.d.ts @@ -1,2 +1,5 @@ export * from './core'; -export { IStructureEditorProps, StructureEditor } from './types'; +export { + type StructureEditorProps, + default as StructureEditor, +} from './src/components/StructureEditor'; diff --git a/full.mjs b/full.mjs index f0325d8..f2576c5 100644 --- a/full.mjs +++ b/full.mjs @@ -1,11 +1,11 @@ import OCL from 'openchemlib/full'; import React from 'react'; -import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer'; -import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer'; -import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer'; +import BaseIdcodeSvgRenderer from './lib/components/IdcodeSvgRenderer.js'; +import BaseMolfileSvgRenderer from './lib/components/MolfileSvgRenderer.js'; +import BaseSmilesSvgRenderer from './lib/components/SmilesSvgRenderer.js'; -export { default as StructureEditor } from './lib/components/StructureEditor'; +export { default as StructureEditor } from './lib/components/StructureEditor.js'; export function SmilesSvgRenderer(props) { return React.createElement(BaseSmilesSvgRenderer, { OCL, ...props }); diff --git a/minimal.cjs b/minimal.cjs new file mode 100644 index 0000000..f998911 --- /dev/null +++ b/minimal.cjs @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib-cjs/index.js'); diff --git a/minimal.d.ts b/minimal.d.ts index 9d393ee..d6f6646 100644 --- a/minimal.d.ts +++ b/minimal.d.ts @@ -1,9 +1,9 @@ export { - IBaseSvgRendererProps, - IIdcodeSvgRendererProps, - IMolfileSvgRendererProps, - ISmilesSvgRendererProps, + type BaseSvgRendererProps, + type IdcodeSvgRendererProps, IdcodeSvgRenderer, + type MolfileSvgRendererProps, MolfileSvgRenderer, + type SmilesSvgRendererProps, SmilesSvgRenderer, -} from './types'; +} from './lib/index.d.ts'; diff --git a/minimal.js b/minimal.js deleted file mode 100644 index a1e679c..0000000 --- a/minimal.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib-cjs/index'); diff --git a/minimal.mjs b/minimal.mjs index 11aece6..09c185e 100644 --- a/minimal.mjs +++ b/minimal.mjs @@ -1 +1 @@ -export * from './lib/index'; +export * from './lib/index.js'; diff --git a/package.json b/package.json index 4b1e7c3..eb5fad3 100644 --- a/package.json +++ b/package.json @@ -8,26 +8,53 @@ "ocl", "openchemlib" ], + "type": "module", "files": [ "lib", "lib-cjs", "base.d.ts", "base.mjs", - "base.js", + "base.cjs", "core.d.ts", "core.mjs", - "core.js", + "core.cjs", "full.d.ts", "full.mjs", - "full.js", + "full.cjs", "minimal.d.ts", "minimal.mjs", - "minimal.js", - "types.d.ts" + "minimal.cjs" ], - "main": "minimal.js", + "main": "minimal.cjs", "module": "minimal.mjs", "types": "minimal.d.ts", + "exports": { + ".": { + "types": "./minimal.d.ts", + "require": "./minimal.cjs", + "default": "./minimal.mjs" + }, + "./base": { + "types": "./base.d.ts", + "require": "./base.cjs", + "default": "./base.mjs" + }, + "./minimal": { + "types": "./minimal.d.ts", + "require": "./minimal.cjs", + "default": "./minimal.mjs" + }, + "./core": { + "types": "./core.d.ts", + "require": "./core.cjs", + "default": "./core.mjs" + }, + "./full": { + "types": "./full.d.ts", + "require": "./full.cjs", + "default": "./full.mjs" + } + }, "repository": { "type": "git", "url": "https://github.com/zakodium-oss/react-ocl.git" @@ -37,16 +64,18 @@ "author": "Michaƫl Zasso ", "scripts": { "build-storybook": "storybook build", - "compile": "babel src --out-dir lib --ignore src/**/__tests__ --quiet --source-maps", - "compile-cjs": "cross-env NODE_ENV=cjs babel src --out-dir lib-cjs --ignore src/**/__tests__ --quiet --source-maps", + "check-types": "tsc --noEmit", + "compile": "tsc -p tsconfig.esm.json", + "compile-cjs": "tsc -p tsconfig.cjs.json", + "postcompile-cjs": "echo '{\"type\":\"commonjs\"}' > lib-cjs/package.json", "dev": "storybook dev -p 6006", "eslint": "eslint src stories", "eslint-fix": "npm run eslint -- --fix", "prepack": "npm run compile && npm run compile-cjs", "prettier": "prettier --check src", "prettier-write": "prettier --write src", - "test": "npm run test-only && npm run eslint && npm run prettier", - "test-only": "cross-env NODE_ENV=test jest" + "test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier", + "test-only": "vitest run" }, "prettier": { "arrowParens": "always", @@ -62,27 +91,25 @@ "react-dom": ">=18" }, "devDependencies": { - "@babel/cli": "^7.23.9", - "@babel/core": "^7.23.9", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/preset-react": "^7.23.3", - "@storybook/addon-essentials": "^7.6.12", - "@storybook/addon-links": "^7.6.12", - "@storybook/addon-storysource": "^7.6.12", - "@storybook/react": "^7.6.12", - "@storybook/react-webpack5": "^7.6.12", - "babel-jest": "^29.7.0", - "babel-loader": "^9.1.3", - "cross-env": "^7.0.3", + "@storybook/addon-essentials": "^8.3.5", + "@storybook/addon-links": "^8.3.5", + "@storybook/addon-storysource": "^8.3.5", + "@storybook/react": "^8.3.5", + "@storybook/react-vite": "^8.3.5", + "@types/react-test-renderer": "^18.3.0", + "@vitejs/plugin-react": "^4.3.2", "eslint": "^8.56.0", "eslint-config-cheminfo-react": "^10.1.0", - "eslint-plugin-storybook": "^0.6.15", - "jest": "^29.7.0", - "openchemlib": "^8.8.1", - "prettier": "^3.2.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-test-renderer": "^18.2.0", - "storybook": "^7.6.12" + "eslint-config-cheminfo-typescript": "^13.0.0", + "eslint-plugin-storybook": "^0.9.0", + "openchemlib": "^8.15.0", + "prettier": "^3.3.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-test-renderer": "^18.3.1", + "storybook": "^8.3.5", + "typescript": "^5.6.3", + "vite": "^5.4.8", + "vitest": "^2.1.2" } } diff --git a/src/components/ErrorRenderer.js b/src/components/ErrorRenderer.tsx similarity index 61% rename from src/components/ErrorRenderer.js rename to src/components/ErrorRenderer.tsx index aa27894..4e609a0 100644 --- a/src/components/ErrorRenderer.js +++ b/src/components/ErrorRenderer.tsx @@ -1,6 +1,16 @@ -import React from 'react'; +import type { ComponentType } from 'react'; -export function ErrorRenderer(props) { +import type { ErrorComponentProps } from './types.js'; + +interface ErrorRendererProps { + width?: number; + height?: number; + value: string; + error: Error; + ErrorComponent: ComponentType; +} + +export function ErrorRenderer(props: ErrorRendererProps) { const { width = 300, height = 150, value, error, ErrorComponent } = props; return (
@@ -14,7 +24,11 @@ export function ErrorRenderer(props) { ); } -export function DefaultErrorRenderer(props) { +export function DefaultErrorRenderer(props: { + width: number; + height: number; + message: string; +}) { return ( OCL.Molecule.fromIDCode(idcode, coordinates), - [OCL, idcode, coordinates], - ); - if (error) { - return ( - - ); - } - return ; -} - -export default memo(IdcodeSvgRenderer); - -function DefaultIdcodeErrorComponent(props) { - return ( - - ); -} diff --git a/src/components/IdcodeSvgRenderer.tsx b/src/components/IdcodeSvgRenderer.tsx new file mode 100644 index 0000000..ba44d85 --- /dev/null +++ b/src/components/IdcodeSvgRenderer.tsx @@ -0,0 +1,55 @@ +import type OCL from 'openchemlib/minimal'; +import { memo, type ReactElement } from 'react'; + +import { useHandleMemoError } from '../hooks/useHandleMemoError.js'; + +import { DefaultErrorRenderer, ErrorRenderer } from './ErrorRenderer.js'; +import SvgRenderer from './SvgRenderer.js'; +import type { BaseSvgRendererProps } from './types.js'; + +export interface IdcodeSvgRendererProps extends BaseSvgRendererProps { + idcode: string; + coordinates?: string; +} + +export interface BaseIdcodeSvgRendererProps extends IdcodeSvgRendererProps { + OCL: typeof OCL; +} + +function IdcodeSvgRenderer(props: BaseIdcodeSvgRendererProps): ReactElement { + let { + OCL, + idcode, + coordinates, + ErrorComponent = DefaultIdcodeErrorComponent, + ...otherProps + } = props; + const [error, mol] = useHandleMemoError( + () => OCL.Molecule.fromIDCode(idcode, coordinates), + [OCL, idcode, coordinates], + ); + if (error) { + return ( + + ); + } + return ; +} + +export default memo(IdcodeSvgRenderer); + +function DefaultIdcodeErrorComponent(props: { width: number; height: number }) { + return ( + + ); +} diff --git a/src/components/MolfileSvgRenderer.js b/src/components/MolfileSvgRenderer.js deleted file mode 100644 index 3600f24..0000000 --- a/src/components/MolfileSvgRenderer.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { memo } from 'react'; - -import { useHandleMemoError } from '../hooks/useHandleMemoError'; - -import { ErrorRenderer, DefaultErrorRenderer } from './ErrorRenderer'; -import SvgRenderer from './SvgRenderer'; - -function MolfileSvgRenderer(props) { - const { - OCL, - molfile, - ErrorComponent = DefaultMolfileErrorComponent, - ...otherProps - } = props; - const [error, mol] = useHandleMemoError( - () => OCL.Molecule.fromMolfile(molfile), - [OCL, molfile], - ); - if (error) { - return ( - - ); - } - return ; -} - -export default memo(MolfileSvgRenderer); - -function DefaultMolfileErrorComponent(props) { - return ( - - ); -} diff --git a/src/components/MolfileSvgRenderer.tsx b/src/components/MolfileSvgRenderer.tsx new file mode 100644 index 0000000..9d6ca81 --- /dev/null +++ b/src/components/MolfileSvgRenderer.tsx @@ -0,0 +1,56 @@ +import type OCL from 'openchemlib/minimal'; +import { memo, type ReactElement } from 'react'; + +import { useHandleMemoError } from '../hooks/useHandleMemoError.js'; + +import { DefaultErrorRenderer, ErrorRenderer } from './ErrorRenderer.js'; +import SvgRenderer from './SvgRenderer.js'; +import type { BaseSvgRendererProps } from './types.js'; + +export interface MolfileSvgRendererProps extends BaseSvgRendererProps { + molfile: string; +} + +export interface BaseMolfileSvgRendererProps extends MolfileSvgRendererProps { + OCL: typeof OCL; +} + +function MolfileSvgRenderer(props: BaseMolfileSvgRendererProps): ReactElement { + const { + OCL, + molfile, + ErrorComponent = DefaultMolfileErrorComponent, + ...otherProps + } = props; + const [error, mol] = useHandleMemoError( + () => OCL.Molecule.fromMolfile(molfile), + [OCL, molfile], + ); + if (error) { + return ( + + ); + } + return ; +} + +export default memo(MolfileSvgRenderer); + +function DefaultMolfileErrorComponent(props: { + width: number; + height: number; +}) { + return ( + + ); +} diff --git a/src/components/SmilesSvgRenderer.js b/src/components/SmilesSvgRenderer.js deleted file mode 100644 index b52bfc7..0000000 --- a/src/components/SmilesSvgRenderer.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { memo } from 'react'; - -import { useHandleMemoError } from '../hooks/useHandleMemoError'; - -import { ErrorRenderer, DefaultErrorRenderer } from './ErrorRenderer'; -import SvgRenderer from './SvgRenderer'; - -function SmilesSvgRenderer(props) { - const { - OCL, - smiles, - ErrorComponent = DefaultSmilesErrorComponent, - ...otherProps - } = props; - const [error, mol] = useHandleMemoError( - () => OCL.Molecule.fromSmiles(smiles), - [OCL, smiles], - ); - if (error) { - return ( - - ); - } - return ; -} - -export default memo(SmilesSvgRenderer); - -function DefaultSmilesErrorComponent(props) { - return ( - - ); -} diff --git a/src/components/SmilesSvgRenderer.tsx b/src/components/SmilesSvgRenderer.tsx new file mode 100644 index 0000000..d0a6bf0 --- /dev/null +++ b/src/components/SmilesSvgRenderer.tsx @@ -0,0 +1,55 @@ +import type OCL from 'openchemlib/minimal'; +import { memo, type ReactElement } from 'react'; + +import { useHandleMemoError } from '../hooks/useHandleMemoError.js'; + +import { DefaultErrorRenderer, ErrorRenderer } from './ErrorRenderer.js'; +import SvgRenderer from './SvgRenderer.js'; +import type { BaseSvgRendererProps } from './types.js'; + +export interface SmilesSvgRendererProps extends BaseSvgRendererProps { + smiles: string; +} + +export interface BaseSmilesSvgRendererProps extends SmilesSvgRendererProps { + OCL: typeof OCL; +} + +function BaseSmilesSvgRenderer( + props: BaseSmilesSvgRendererProps, +): ReactElement { + const { + OCL, + smiles, + ErrorComponent = DefaultSmilesErrorComponent, + ...otherProps + } = props; + const [error, mol] = useHandleMemoError( + () => OCL.Molecule.fromSmiles(smiles), + [OCL, smiles], + ); + if (error) { + return ( + + ); + } + return ; +} + +export default memo(BaseSmilesSvgRenderer); + +function DefaultSmilesErrorComponent(props: { width: number; height: number }) { + return ( + + ); +} diff --git a/src/components/StructureEditor.js b/src/components/StructureEditor.tsx similarity index 69% rename from src/components/StructureEditor.js rename to src/components/StructureEditor.tsx index 877ef09..c1e3906 100644 --- a/src/components/StructureEditor.js +++ b/src/components/StructureEditor.tsx @@ -1,7 +1,29 @@ import OCL from 'openchemlib/full'; -import React, { useRef, useEffect } from 'react'; +import { type ReactElement, useEffect, useRef } from 'react'; -function StructureEditor(props) { +export interface StructureEditorProps { + width?: number; + height?: number; + initialMolfile?: string; + initialIDCode?: string; + fragment?: boolean; + svgMenu?: boolean; + onChange?: (molfile: string, molecule: OCL.Molecule, idCode: string) => void; + onAtomEnter?: (atomId: number) => void; + onAtomLeave?: (atomId: number) => void; + onBondEnter?: (bondId: number) => void; + onBondLeave?: (bondId: number) => void; +} + +interface CallbacksRef { + onChange?: OCL.ChangeListenerCallback; + onAtomHighlight?: OCL.AtomHighlightCallback; + onBondHighlight?: OCL.BondHighlightCallback; +} + +export default function StructureEditor( + props: StructureEditorProps, +): ReactElement { const { width = 675, height = 450, @@ -16,18 +38,24 @@ function StructureEditor(props) { onBondLeave, } = props; - const domRef = useRef(); - const editorRef = useRef({ editor: null, hadFirstChange: false }); - const callbacksRef = useRef({}); + const domRef = useRef(null); + const editorRef = useRef<{ + editor: OCL.StructureEditor | null; + hadFirstChange: boolean; + }>({ editor: null, hadFirstChange: false }); + const callbacksRef = useRef({}); useEffect(() => { + if (!domRef.current) return; + domRef.current.innerHTML = ''; // GWT doesn't play well with the shadow DOM. This hack allows to load an // OCL editor inside a shadow root. const root = domRef.current.getRootNode(); - let originalGetElementById; + let originalGetElementById: typeof document.getElementById | undefined; if (root instanceof ShadowRoot) { + // eslint-disable-next-line @typescript-eslint/unbound-method originalGetElementById = document.getElementById; document.getElementById = root.getElementById.bind(root); } @@ -35,7 +63,7 @@ function StructureEditor(props) { try { editor = new OCL.StructureEditor(domRef.current, svgMenu, 1); } finally { - if (root instanceof ShadowRoot) { + if (originalGetElementById) { document.getElementById = originalGetElementById; } } @@ -69,7 +97,7 @@ function StructureEditor(props) { callbacksRef.current.onChange = () => { if (!editorRef.current.hadFirstChange) { editorRef.current.hadFirstChange = true; - } else if (onChange) { + } else if (onChange && editorRef.current.editor) { const molfile = editorRef.current.editor.getMolFileV3(); const molecule = editorRef.current.editor.getMolecule(); const idCode = editorRef.current.editor.getIDCode(); @@ -100,5 +128,3 @@ function StructureEditor(props) { return
; } - -export default StructureEditor; diff --git a/src/components/SvgRenderer.js b/src/components/SvgRenderer.tsx similarity index 66% rename from src/components/SvgRenderer.js rename to src/components/SvgRenderer.tsx index 25d5e0b..ec5bea5 100644 --- a/src/components/SvgRenderer.js +++ b/src/components/SvgRenderer.tsx @@ -1,6 +1,20 @@ -import React, { useRef, useEffect, useMemo, useId } from 'react'; +import type OCL from 'openchemlib/minimal'; +import { + type MouseEvent as ReactMouseEvent, + type RefObject, + useEffect, + useId, + useMemo, + useRef, +} from 'react'; + +import type { BaseSvgRendererProps } from './types.js'; + +interface SvgRendererProps extends BaseSvgRendererProps { + mol: OCL.Molecule; +} -export default function SvgRenderer(props) { +export default function SvgRenderer(props: SvgRendererProps) { const { width = 300, height = 150, @@ -33,7 +47,7 @@ export default function SvgRenderer(props) { const reactId = useId().replace(/:/g, '-'); const internalId = `react-ocl${reactId}`; - const ref = useRef(null); + const ref = useRef(null); const id = idFromProps || internalId; @@ -96,29 +110,47 @@ export default function SvgRenderer(props) { ); } -function useEvents(ref, start, onEnter, onLeave, onClick) { +function useEvents( + ref: RefObject, + start: string, + onEnter: BaseSvgRendererProps['onAtomEnter'], + onLeave: BaseSvgRendererProps['onAtomLeave'], + onClick: BaseSvgRendererProps['onAtomClick'], +) { useEffect(() => { const svg = ref.current; if (!svg) return; - const handleEnter = (event) => { + const handleEnter = (event: MouseEvent) => { if (!onEnter) return; - const { target } = event; + const target = event.target as SVGElement; if (target.className.baseVal === 'event' && target.id.startsWith(start)) { - onEnter(Number(target.id.replace(start, '')), event); + // TODO: This is wrong and should be fixed. + onEnter( + Number(target.id.replace(start, '')), + event as unknown as ReactMouseEvent, + ); } }; - const handleLeave = (event) => { + const handleLeave = (event: MouseEvent) => { if (!onLeave) return; - const { target } = event; + const target = event.target as SVGElement; if (target.className.baseVal === 'event' && target.id.startsWith(start)) { - onLeave(Number(target.id.replace(start, '')), event); + // TODO: This is wrong and should be fixed. + onLeave( + Number(target.id.replace(start, '')), + event as unknown as ReactMouseEvent, + ); } }; - const handleClick = (event) => { + const handleClick = (event: MouseEvent) => { if (!onClick) return; - const { target } = event; + const target = event.target as SVGElement; if (target.className.baseVal === 'event' && target.id.startsWith(start)) { - onClick(Number(target.id.replace(start, '')), event); + // TODO: This is wrong and should be fixed. + onClick( + Number(target.id.replace(start, '')), + event as unknown as ReactMouseEvent, + ); } }; svg.addEventListener('mouseover', handleEnter); @@ -133,30 +165,36 @@ function useEvents(ref, start, onEnter, onLeave, onClick) { } function useHighlight( - ref, - start, - highlight, - highlightColor, - highlightOpacity, - attribute, + ref: RefObject, + start: string, + highlight: number[] | undefined, + highlightColor: string, + highlightOpacity: number, + attribute: string, ) { useEffect(() => { const svg = ref.current; if (!svg) return; const elements = svg.querySelectorAll(`[id^="${start}"]`); - for (const element of elements) { + elements.forEach((element) => { const elementId = Number(element.id.replace(start, '')); - if (highlight && highlight.includes(elementId)) { - element.setAttribute('opacity', highlightOpacity); + if (highlight?.includes(elementId)) { + element.setAttribute('opacity', String(highlightOpacity)); element.setAttribute(attribute, highlightColor); } else { - element.setAttribute('opacity', 0); + element.setAttribute('opacity', '0'); } - } + }); }); } -function getSVG(mol, width, height, id, serializedOptions) { +function getSVG( + mol: OCL.Molecule, + width: number, + height: number, + id: string, + serializedOptions: string, +) { const options = JSON.parse(serializedOptions); const { @@ -170,6 +208,7 @@ function getSVG(mol, width, height, id, serializedOptions) { let svg = mol.toSVG(width, height, id, svgOptions); if (label) { + // @ts-expect-error We know it will match. const [minX, minY, realWidth, realHeight] = svg .match(/viewBox="([^"]*)"/)[1] .split(' ') diff --git a/src/components/__tests__/SvgRenderer.js b/src/components/__tests__/SvgRenderer.test.tsx similarity index 93% rename from src/components/__tests__/SvgRenderer.js rename to src/components/__tests__/SvgRenderer.test.tsx index 01a3e5c..66b913d 100644 --- a/src/components/__tests__/SvgRenderer.js +++ b/src/components/__tests__/SvgRenderer.test.tsx @@ -1,9 +1,10 @@ import OCL from 'openchemlib/minimal'; -import React from 'react'; import renderer from 'react-test-renderer'; +import { expect, test } from 'vitest'; -import MolfileSvgRenderer from '../MolfileSvgRenderer'; -import SmilesSvgRenderer from '../SmilesSvgRenderer'; +import MolfileSvgRenderer from '../MolfileSvgRenderer.js'; +import SmilesSvgRenderer from '../SmilesSvgRenderer.js'; +import type { ErrorComponentProps } from '../types.js'; test('Molecule renders smiles with custom id', () => { const component = renderer.create( @@ -100,7 +101,7 @@ test('Syntax error in SMILES - custom renderer', () => { ( + ErrorComponent={(props: ErrorComponentProps) => (
{props.value} {props.error.message} diff --git a/src/components/__tests__/__snapshots__/SvgRenderer.js.snap b/src/components/__tests__/__snapshots__/SvgRenderer.test.jsx.snap similarity index 98% rename from src/components/__tests__/__snapshots__/SvgRenderer.js.snap rename to src/components/__tests__/__snapshots__/SvgRenderer.test.jsx.snap index b80e10e..106af49 100644 --- a/src/components/__tests__/__snapshots__/SvgRenderer.js.snap +++ b/src/components/__tests__/__snapshots__/SvgRenderer.test.jsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Molecule renders molfile with default id 1`] = ` - Class$S13: SmilesParser: unknown element label found + Class$S13: SmilesParser: unknown element label found. Position:1
diff --git a/src/components/__tests__/__snapshots__/SvgRenderer.test.tsx.snap b/src/components/__tests__/__snapshots__/SvgRenderer.test.tsx.snap new file mode 100644 index 0000000..f818cfc --- /dev/null +++ b/src/components/__tests__/__snapshots__/SvgRenderer.test.tsx.snap @@ -0,0 +1,205 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Molecule renders molfile with default id 1`] = ` + text {font-family: sans-serif;} #mol1 { pointer-events:none; } #mol1 .event { pointer-events:all; } line { stroke-linecap:round; } polygon { stroke-linejoin:round; } + N + O + O + O + O + H + H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +", + } + } + height="800px" + id="mol1" + style={ + { + "userSelect": "none", + } + } + version="1.1" + viewBox="0 0 1200 800" + width="1200px" + xmlns="http://www.w3.org/2000/svg" +/> +`; + +exports[`Molecule renders smiles with custom id 1`] = ` + text {font-family: sans-serif;} #mol1 { pointer-events:none; } #mol1 .event { pointer-events:all; } line { stroke-linecap:round; } polygon { stroke-linejoin:round; } + O + + + + + + + + + + +", + } + } + height="800px" + id="mol1" + style={ + { + "userSelect": "none", + } + } + version="1.1" + viewBox="0 0 1200 800" + width="1200px" + xmlns="http://www.w3.org/2000/svg" +/> +`; + +exports[`Syntax error in SMILES - custom renderer 1`] = ` +
+
+ + BAD + + + Class$S13: SmilesParser: unknown element label found. Position:1 + +
+
+`; + +exports[`Syntax error in SMILES - default renderer 1`] = ` +
+ + + + Invalid SMILES + + + +
+`; diff --git a/src/components/types.ts b/src/components/types.ts new file mode 100644 index 0000000..4387bb8 --- /dev/null +++ b/src/components/types.ts @@ -0,0 +1,39 @@ +import type { IMoleculeToSVGOptions } from 'openchemlib/minimal'; +import type { ComponentType, MouseEvent } from 'react'; + +export interface ErrorComponentProps { + width: number; + height: number; + value: string; + error: Error; +} + +export interface BaseSvgRendererProps extends IMoleculeToSVGOptions { + width?: number; + height?: number; + id?: string; + + ErrorComponent?: ComponentType; + + atomHighlight?: number[]; + atomHighlightOpacity?: number; + atomHighlightColor?: string; + onAtomEnter?: (atomId: number, event: MouseEvent) => void; + onAtomLeave?: (atomId: number, event: MouseEvent) => void; + onAtomClick?: (atomId: number, event: MouseEvent) => void; + + bondHighlight?: number[]; + bondHighlightOpacity?: number; + bondHighlightColor?: string; + onBondEnter?: (bondId: number, event: MouseEvent) => void; + onBondLeave?: (bondId: number, event: MouseEvent) => void; + onBondClick?: (bondId: number, event: MouseEvent) => void; + + autoCrop?: boolean; + autoCropMargin?: number; + + labelFontFamily?: string; + labelFontSize?: number; + labelColor?: string; + label?: string; +} diff --git a/src/hooks/useHandleMemoError.js b/src/hooks/useHandleMemoError.ts similarity index 51% rename from src/hooks/useHandleMemoError.js rename to src/hooks/useHandleMemoError.ts index a34784a..a7ae26e 100644 --- a/src/hooks/useHandleMemoError.js +++ b/src/hooks/useHandleMemoError.ts @@ -1,11 +1,14 @@ import { useMemo } from 'react'; -export function useHandleMemoError(cb, deps) { - const [hasError, result] = useMemo(() => { +export function useHandleMemoError( + cb: () => T, + deps: unknown[], +): [Error, null] | [null, T] { + const [hasError, result] = useMemo<[false, T] | [true, Error]>(() => { try { return [false, cb()]; } catch (error) { - return [true, error]; + return [true, error as Error]; } // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 12a00df..0000000 --- a/src/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import OCL from 'openchemlib/minimal'; -import React from 'react'; - -import BaseIdcodeSvgRenderer from './components/IdcodeSvgRenderer'; -import BaseMolfileSvgRenderer from './components/MolfileSvgRenderer'; -import BaseSmilesSvgRenderer from './components/SmilesSvgRenderer'; - -export function SmilesSvgRenderer(props) { - return ; -} - -export function MolfileSvgRenderer(props) { - return ; -} - -export function IdcodeSvgRenderer(props) { - return ; -} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..d099444 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,31 @@ +import OCL from 'openchemlib/minimal'; + +import BaseIdcodeSvgRenderer, { + type IdcodeSvgRendererProps, +} from './components/IdcodeSvgRenderer.js'; +import BaseMolfileSvgRenderer, { + type MolfileSvgRendererProps, +} from './components/MolfileSvgRenderer.js'; +import BaseSmilesSvgRenderer, { + type SmilesSvgRendererProps, +} from './components/SmilesSvgRenderer.js'; + +export type { + IdcodeSvgRendererProps, + MolfileSvgRendererProps, + SmilesSvgRendererProps, +}; + +export type { BaseSvgRendererProps } from './components/types.js'; + +export function SmilesSvgRenderer(props: SmilesSvgRendererProps) { + return ; +} + +export function MolfileSvgRenderer(props: MolfileSvgRendererProps) { + return ; +} + +export function IdcodeSvgRenderer(props: IdcodeSvgRendererProps) { + return ; +} diff --git a/stories/.eslintrc.yml b/stories/.eslintrc.yml deleted file mode 100644 index 78dc437..0000000 --- a/stories/.eslintrc.yml +++ /dev/null @@ -1,2 +0,0 @@ -rules: - no-undef: off diff --git a/stories/data.js b/stories/data.ts similarity index 100% rename from stories/data.js rename to stories/data.ts diff --git a/stories/highlight.stories.js b/stories/highlight.stories.tsx similarity index 90% rename from stories/highlight.stories.js rename to stories/highlight.stories.tsx index 2aaa1ec..d3de923 100644 --- a/stories/highlight.stories.js +++ b/stories/highlight.stories.tsx @@ -1,8 +1,8 @@ import { useState } from 'react'; -import { MolfileSvgRenderer } from '../src/index'; +import { MolfileSvgRenderer } from '../src/index.js'; -import { molfileV2000 } from './data'; +import { molfileV2000 } from './data.js'; export default { title: 'Highlighting', @@ -33,7 +33,7 @@ export default { }, }; -export function Fixed(args) { +export function Fixed(args: any) { return ; } Fixed.storyName = 'Fixed highlight'; @@ -52,7 +52,7 @@ Fixed.argTypes = { }, }; -export function Hover(args) { +export function Hover(args: any) { const [currentAtom, setCurrentAtom] = useState(null); const [currentBond, setCurrentBond] = useState(null); return ( diff --git a/stories/structure-editor.stories.js b/stories/structure-editor.stories.tsx similarity index 80% rename from stories/structure-editor.stories.js rename to stories/structure-editor.stories.tsx index a7daef6..f32677a 100644 --- a/stories/structure-editor.stories.js +++ b/stories/structure-editor.stories.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -import StructureEditor from '../src/components/StructureEditor'; +import StructureEditor from '../src/components/StructureEditor.js'; export default { title: 'StructureEditor', @@ -40,11 +40,21 @@ Actelion Java MolfileCreator 1.0 M END `; -export function FromMolfile({ svgMenu, fragment, width, height }) { +export function FromMolfile({ + svgMenu, + fragment, + width, + height, +}: { + svgMenu: boolean; + fragment: boolean; + width: number; + height: number; +}) { const [molfile, setMolfile] = useState(initialMolfile); - const [previous, setPrevious] = useState(null); + const [previous, setPrevious] = useState(null); const cb = useCallback( - (newMolfile) => { + (newMolfile: string) => { setMolfile(newMolfile); setPrevious(molfile); }, @@ -73,11 +83,21 @@ export function FromMolfile({ svgMenu, fragment, width, height }) { ); } -export function FromIDCode({ svgMenu, fragment, width, height }) { +export function FromIDCode({ + svgMenu, + fragment, + width, + height, +}: { + svgMenu: boolean; + fragment: boolean; + width: number; + height: number; +}) { const [idCode, setIDCode] = useState(initialIDCode); - const [previous, setPrevious] = useState(null); + const [previous, setPrevious] = useState(null); const cb = useCallback( - (netMolfile, molecule, newIDCode) => { + (netMolfile: unknown, molecule: unknown, newIDCode: string) => { setIDCode(newIDCode); setPrevious(idCode); }, diff --git a/stories/svg-renderers/common-args.js b/stories/svg-renderers/common-args.ts similarity index 100% rename from stories/svg-renderers/common-args.js rename to stories/svg-renderers/common-args.ts diff --git a/stories/svg-renderers/idcode.stories.js b/stories/svg-renderers/idcode.stories.tsx similarity index 67% rename from stories/svg-renderers/idcode.stories.js rename to stories/svg-renderers/idcode.stories.tsx index 9baa373..d88ae1a 100644 --- a/stories/svg-renderers/idcode.stories.js +++ b/stories/svg-renderers/idcode.stories.tsx @@ -1,7 +1,7 @@ -import { IdcodeSvgRenderer } from '../../src/index'; -import { idcode } from '../data'; +import { IdcodeSvgRenderer } from '../../src/index.js'; +import { idcode } from '../data.js'; -import { commonArgs, commonArgTypes } from './common-args'; +import { commonArgs, commonArgTypes } from './common-args.js'; export default { title: 'SVG renderers/IdcodeSvgRenderer', @@ -14,7 +14,7 @@ export default { }, }; -export function Idcode(args) { +export function Idcode(args: any) { return ; } Idcode.storyName = 'ID code'; @@ -22,7 +22,7 @@ Idcode.args = { idcode: idcode.idCode, }; -export function WithCoordinates(args) { +export function WithCoordinates(args: any) { return ; } WithCoordinates.storyName = 'ID code (with coordinates)'; diff --git a/stories/svg-renderers/molfile.stories.js b/stories/svg-renderers/molfile.stories.tsx similarity index 71% rename from stories/svg-renderers/molfile.stories.js rename to stories/svg-renderers/molfile.stories.tsx index 84d8a7f..3356298 100644 --- a/stories/svg-renderers/molfile.stories.js +++ b/stories/svg-renderers/molfile.stories.tsx @@ -1,7 +1,7 @@ -import { MolfileSvgRenderer } from '../../src/index'; -import { molfileV2000, molfileV3000 } from '../data'; +import { MolfileSvgRenderer } from '../../src/index.js'; +import { molfileV2000, molfileV3000 } from '../data.js'; -import { commonArgs, commonArgTypes } from './common-args'; +import { commonArgs, commonArgTypes } from './common-args.js'; export default { title: 'SVG renderers/MolfileSvgRenderer', @@ -22,7 +22,7 @@ export default { }, }; -export function V2000(args) { +export function V2000(args: any) { return ; } V2000.storyName = 'Molfile (V2000)'; @@ -30,7 +30,7 @@ V2000.args = { molfile: molfileV2000, }; -export function V3000(args) { +export function V3000(args: any) { return ; } V3000.storyName = 'Molfile (V3000)'; diff --git a/stories/svg-renderers/smiles.stories.js b/stories/svg-renderers/smiles.stories.tsx similarity index 82% rename from stories/svg-renderers/smiles.stories.js rename to stories/svg-renderers/smiles.stories.tsx index 16862d6..9cfdfa2 100644 --- a/stories/svg-renderers/smiles.stories.js +++ b/stories/svg-renderers/smiles.stories.tsx @@ -1,6 +1,6 @@ -import { SmilesSvgRenderer } from '../../src/index'; +import { SmilesSvgRenderer } from '../../src/index.js'; -import { commonArgs, commonArgTypes } from './common-args'; +import { commonArgs, commonArgTypes } from './common-args.js'; export default { title: 'SVG renderers/SmilesSvgRenderer', @@ -22,12 +22,12 @@ export default { }, }; -export function Smiles(args) { +export function Smiles(args: any) { return ; } Smiles.storyName = 'SMILES'; -function ErrorComponent(props) { +function ErrorComponent(props: any) { return (
{props.value}
diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..2ceb092 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.esm.json", + "compilerOptions": { + "outDir": "lib-cjs", + "module": "CommonJS", + "moduleResolution": "Node", + "verbatimModuleSyntax": false, + "esModuleInterop": true, + "declaration": false, + "declarationMap": false + } +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..0cd9220 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["stories", "**/__tests__", "*.d.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8059d7a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "declaration": true, + "jsx": "react-jsx", + "lib": ["dom", "esnext"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "outDir": "lib", + "removeComments": false, + "sourceMap": true, + "declarationMap": true, + "strict": true, + "target": "es2022", + "skipLibCheck": true + }, + "include": ["src/**/*", "stories/**/*", "*.d.ts"], + "exclude": ["node_modules"] +} diff --git a/types.d.ts b/types.d.ts deleted file mode 100644 index cb9b380..0000000 --- a/types.d.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { IMoleculeToSVGOptions, Molecule } from 'openchemlib/minimal'; -import { ComponentType, MouseEvent } from 'react'; - -// Minimal and core APIs - -export interface IErrorComponentProps { - value: string; - error: Error; -} - -export interface IBaseSvgRendererProps extends IMoleculeToSVGOptions { - width?: number; - height?: number; - id?: string; - - ErrorComponent?: ComponentType; - - atomHighlight?: number[]; - atomHighlightOpacity?: number; - atomHighlightColor?: string; - onAtomEnter?: (atomId: number, event: MouseEvent) => void; - onAtomLeave?: (atomId: number, event: MouseEvent) => void; - onAtomClick?: (atomId: number, event: MouseEvent) => void; - - bondHighlight?: number[]; - bondHighlightOpacity?: number; - bondHighlightColor?: string; - onBondEnter?: (bondId: number, event: MouseEvent) => void; - onBondLeave?: (bondId: number, event: MouseEvent) => void; - onBondClick?: (bondId: number, event: MouseEvent) => void; - - autoCrop?: boolean; - autoCropMargin?: number; - - labelFontFamily?: string; - labelFontSize?: number; - labelColor?: string; - label?: string; -} - -export interface ISmilesSvgRendererProps extends IBaseSvgRendererProps { - smiles: string; -} -export function SmilesSvgRenderer(props: ISmilesSvgRendererProps): JSX.Element; - -export interface IMolfileSvgRendererProps extends IBaseSvgRendererProps { - molfile: string; -} -export function MolfileSvgRenderer( - props: IMolfileSvgRendererProps, -): JSX.Element; - -export interface IIdcodeSvgRendererProps extends IBaseSvgRendererProps { - idcode: string; - coordinates?: string; -} -export function IdcodeSvgRenderer(props: IIdcodeSvgRendererProps): JSX.Element; - -// Full API - -export interface IStructureEditorProps { - width?: number; - height?: number; - initialMolfile?: string; - initialIDCode?: string; - fragment?: boolean; - svgMenu?: boolean; - onChange?: (molfile: string, molecule: Molecule, idCode: string) => void; - onAtomEnter?: (atomId: number) => void; - onAtomLeave?: (atomId: number) => void; - onBondEnter?: (bondId: number) => void; - onBondLeave?: (bondId: number) => void; -} -export function StructureEditor(props: IStructureEditorProps): JSX.Element; diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..fabde1a --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,6 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], +});