├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── new-version.yml
│ └── release.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .nvmrc
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── SECURITY.md
├── __mocks__
└── styleMock.js
├── eslint.config.mjs
├── examples
├── app.css
├── app.tsx
├── components
│ ├── Form.css
│ └── Form.tsx
├── index.html
└── webpack.config.ts
├── index.d.ts
├── jest.config.ts
├── package-lock.json
├── package.json
├── src
├── JoditEditor.tsx
├── include.jodit.ts
└── index.ts
├── tests
├── __snapshots__
│ └── smoke.test.tsx.snap
├── onchange.test.tsx
├── ref.test.tsx
├── smoke.test.tsx
└── theme.test.tsx
├── tsconfig.json
├── tsconfig.types.json
└── webpack.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = tab
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{LICENSE,.gitattributes,.gitignore,.npmignore,.eslintignore}]
12 | indent_style = space
13 |
14 | [*.{json,yml,md,yaspellerrc,bowerrc,babelrc,snakeskinrc,eslintrc,tsconfig,pzlrrc}]
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | **Jodit Version:** 3.4.xxxxx
5 |
6 | **Browser:**
7 | **OS:**
8 | **Is React App:**
9 |
10 | **Code**
11 |
12 | ```js
13 | // A *self-contained* demonstration of the problem follows...
14 | ```
15 |
16 | **Expected behavior:**
17 |
18 | **Actual behavior:**
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | Fixes #
15 |
--------------------------------------------------------------------------------
/.github/workflows/new-version.yml:
--------------------------------------------------------------------------------
1 | name: New version
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | repository_dispatch:
7 | types: [newversion]
8 |
9 | jobs:
10 | newversion:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
16 | fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
17 | - uses: actions/setup-node@v4 #Setup Node
18 | with:
19 | node-version-file: '.nvmrc'
20 | cache: 'npm'
21 | - name: Create local changes
22 | run: |
23 | npm ci
24 | npm i jodit
25 |
26 | git config --local user.email "github-actions[bot]@users.noreply.github.com"
27 | git config --local user.name "github-actions[bot]"
28 |
29 | npm version patch --no-git-tag-version
30 | npm run git
31 |
32 | - name: Push changes
33 | uses: ad-m/github-push-action@master
34 | with:
35 | github_token: ${{ secrets.GITHUB_TOKEN }}
36 | branch: ${{ github.ref }}
37 | tags: true
38 |
39 | - name: Trigger release action
40 | run: |
41 | curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.GITHUB_TOKEN}}" -H "Accept:application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/jodit/jodit-react/dispatches --data '{"event_type": "release" }'
42 |
43 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | repository_dispatch:
7 | types: [ release ]
8 |
9 | push:
10 | tags: ["*"]
11 |
12 | jobs:
13 | release:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: actions/setup-node@v4 #Setup Node
20 | with:
21 | node-version-file: '.nvmrc'
22 | cache: 'npm'
23 |
24 | - name: Install dependencies
25 | run: |
26 | npm ci
27 |
28 | - name: Lint
29 | run: |
30 | npm run lint
31 |
32 | - name: Build
33 | run: |
34 | npm run build
35 |
36 | - name: Publish
37 | run: |
38 | NPM_TOKEN=${{ secrets.NPM_TOKEN }} npm publish ./ --access public
39 |
40 | - name: Get tag
41 | id: get_version
42 | run: |
43 | VERSION=$(node -p "require('./package.json').version")
44 | echo "VERSION=$VERSION" >> $GITHUB_ENV
45 | echo "version=$VERSION" >> $GITHUB_OUTPUT
46 |
47 | - name: Show new version
48 | run: |
49 | VERSION=${{ steps.get_version.outputs.version }}
50 | PACKAGE_NAME="jodit-react"
51 | echo "[New version npm](https://www.npmjs.com/package/${PACKAGE_NAME}/v/${VERSION})" >> $GITHUB_STEP_SUMMARY
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .idea/
3 | /node_modules/
4 | /examples/build/
5 | build
6 | /coverage/
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | eslint.config.mjs
3 | .editorconfig
4 | .gitignore
5 | .prettierrc.json
6 | tsconfig.types.json
7 | .github
8 | webpack.config.ts
9 | /coverage/
10 | __mocks__
11 | tests
12 | jest.config.ts
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN}
2 | registry=https://registry.npmjs.org/
3 | always-auth=true
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 4,
4 | "useTabs": true,
5 | "semi": true,
6 | "singleQuote": true,
7 | "endOfLine": "lf",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | > **Tags:**
4 | >
5 | > - :boom: [Breaking Change]
6 | > - :rocket: [New Feature]
7 | > - :bug: [Bug Fix]
8 | > - :memo: [Documentation]
9 | > - :house: [Internal]
10 | > - :nail_care: [Polish]
11 |
12 | ## 5.0.9
13 |
14 | - Fixed ref forwarding issue
15 |
16 | ## 5.0.7
17 |
18 | - [Fix: Avoid "Abort async" error by utilizing waitForReady API in Jodit destruct handling #287](https://github.com/jodit/jodit-react/pull/287)
19 | - Fixed Config type issue
20 | - Support React 19
21 |
22 |
23 | ## 4.0.1
24 |
25 | -
26 |
27 | ## 1.3.19
28 |
29 | #### :rocket: New Feature
30 |
31 | - The package now re-exports imperative Jodit, so you can write plugins and use all Jodit helpers
32 |
33 | ```js
34 | import JoditEditor, { Jodit } from '../../src/';
35 |
36 | /**
37 | * @param {Jodit} jodit
38 | */
39 | function preparePaste(jodit) {
40 | jodit.e.on(
41 | 'paste',
42 | e => {
43 | if (confirm('Change pasted content?')) {
44 | jodit.e.stopPropagation('paste');
45 | jodit.s.insertHTML(
46 | Jodit.modules.Helpers.getDataTransfer(e)
47 | .getData(Jodit.constants.TEXT_HTML)
48 | .replace(/a/g, 'b')
49 | );
50 | return false;
51 | }
52 | },
53 | { top: true }
54 | );
55 | }
56 | Jodit.plugins.add('preparePaste', preparePaste);
57 |
58 | //...
59 | return ;
60 | ```
61 |
62 | #### :house: Internal
63 |
64 | - Update
65 |
66 | ```
67 | eslint-plugin-react-hooks ^4.5.0 → ^4.6.0
68 | @babel/core ^7.16.0 → ^7.19.0
69 | @babel/eslint-parser ^7.17.0 → ^7.18.9
70 | @babel/preset-env ^7.16.0 → ^7.19.0
71 | @babel/preset-react ^7.16.0 → ^7.18.6
72 | @types/react ^16.14.2 → ^18.0.18
73 | babel-loader ^8.2.2 → ^8.2.5
74 | css-loader ^3.6.0 → ^6.7.1
75 | eslint ^8.9.0 → ^8.23.0
76 | eslint-config-prettier ^8.4.0 → ^8.5.0
77 | eslint-plugin-prettier ^4.0.0 → ^4.2.1
78 | eslint-plugin-react ^7.28.0 → ^7.31.8
79 | husky ^7.0.4 → ^8.0.1
80 | lint-staged ^12.3.4 → ^13.0.3
81 | prettier ^2.5.1 → ^2.7.1
82 | style-loader ^0.20.3 → ^3.3.1
83 | webpack ^4.44.2 → ^5.74.0
84 | webpack-cli ^3.3.12 → ^4.10.0
85 | webpack-dev-server ^3.11.0 → ^4.11.0
86 | ```
87 |
88 | ## 1.3.18
89 |
90 | #### :bug: Bug Fix
91 |
92 | - [Jodit not cleaning up after unmount #196](https://github.com/jodit/jodit-react/issues/196)
93 |
94 | ## 1.2.1
95 |
96 | #### :bug: Bug Fix
97 |
98 | - [Editor duplicates after re-render (state change) #172](https://github.com/jodit/jodit-react/issues/172)
99 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 It can be easy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Jodit WYSIWYG Editor
2 |
3 | [](https://www.npmjs.com/package/jodit-react)
4 | [](https://www.npmjs.com/package/jodit-react)
5 | [](https://www.npmjs.com/package/jodit-react)
6 |
7 | React wrapper for [Jodit](https://xdsoft.net/jodit/)
8 |
9 | > [Jodit React PRO](https://xdsoft.net/jodit/pro/) it is an extended version of Jodit React with the same API, but with a lot more features.
10 |
11 | ## Installation
12 |
13 | ```bash
14 | npm install jodit-react --save
15 | ```
16 |
17 | ## Update editor version
18 |
19 | ```bash
20 | npm update jodit-react
21 | ```
22 |
23 | ## Run demo
24 |
25 | ```bash
26 | npm install --dev
27 | npm run demo
28 | ```
29 |
30 | and open
31 |
32 | ```
33 | http://localhost:4000/
34 | ```
35 |
36 | ## Usage
37 |
38 | ### 1. Require and use Jodit-react component inside your application.
39 |
40 | ```jsx
41 | import React, { useState, useRef, useMemo } from 'react';
42 | import JoditEditor from 'jodit-react';
43 |
44 | const Example = ({ placeholder }) => {
45 | const editor = useRef(null);
46 | const [content, setContent] = useState('');
47 |
48 | const config = useMemo(() => ({
49 | readonly: false, // all options from https://xdsoft.net/jodit/docs/,
50 | placeholder: placeholder || 'Start typings...'
51 | }),
52 | [placeholder]
53 | );
54 |
55 | return (
56 | setContent(newContent)} // preferred to use only this option to update the content for performance reasons
62 | onChange={newContent => {}}
63 | />
64 | );
65 | };
66 | ```
67 |
68 | ### Jodit plugins
69 |
70 | You can use all Jodit features and write your [own plugin](https://xdsoft.net/jodit/docs/modules/plugin.html) for example.
71 |
72 | ```js
73 | import JoditEditor, { Jodit } from 'jodit-react';
74 |
75 | /**
76 | * @param {Jodit} jodit
77 | */
78 | function preparePaste(jodit) {
79 | jodit.e.on(
80 | 'paste',
81 | e => {
82 | if (confirm('Change pasted content?')) {
83 | jodit.e.stopPropagation('paste');
84 | jodit.s.insertHTML(
85 | Jodit.modules.Helpers.getDataTransfer(e)
86 | .getData(Jodit.constants.TEXT_HTML)
87 | .replace(/a/g, 'b')
88 | );
89 | return false;
90 | }
91 | },
92 | { top: true }
93 | );
94 | }
95 | Jodit.plugins.add('preparePaste', preparePaste);
96 |
97 | //...
98 | return ;
99 | ```
100 |
101 | You can see how to write plugins [in the documentation](https://xdsoft.net/jodit/pro/docs/how-to/create-plugin.md) or [on the stand](https://xdsoft.net/jodit/pro/docs/getting-started/examples.md#jodit-example-paste-link)
102 |
103 | ## Use with Jodit PRO
104 |
105 | You can connect any Jodit constructor and set it as the `JoditConstructor` property of the component.
106 |
107 | ```jsx
108 | import React from 'react';
109 | import JoditEditor from 'jodit-react';
110 | import {Jodit} from 'jodit-pro';
111 | import 'jodit-pro/es5/jodit.min.css';
112 | // ...
113 |
114 | function App() {
115 | return ;
116 | }
117 |
118 | ```
119 |
120 |
121 | ## License
122 |
123 | This package is available under `MIT` License.
124 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting security issues
2 |
3 | If you believe you have found a security issue in the Jodit software, please contact us immediately.
4 |
5 | When reporting a suspected security problem, please bear this in mind:
6 |
7 | * Make sure to provide as many details as possible about the vulnerability.
8 | * Please do not disclose publicly any security issues until we fix them and publish security releases.
9 |
10 | Contact the security team at security@xdsoft.net. As soon as we receive the security report, we'll work promptly to confirm the issue and then to provide a security fix.
11 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-undef
2 | module.exports = {};
3 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { fixupConfigRules } from '@eslint/compat';
2 | import path from 'node:path';
3 | import { fileURLToPath } from 'node:url';
4 | import js from '@eslint/js';
5 | import { FlatCompat } from '@eslint/eslintrc';
6 | import jest from 'eslint-plugin-jest';
7 |
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 | const compat = new FlatCompat({
11 | baseDirectory: __dirname,
12 | recommendedConfig: js.configs.recommended,
13 | allConfig: js.configs.all
14 | });
15 |
16 | export default [
17 | {
18 | ignores: ['node_modules/*', '**/build', 'types/*']
19 | },
20 | ...fixupConfigRules(
21 | compat.extends(
22 | 'eslint:recommended',
23 | 'plugin:react/recommended',
24 | 'plugin:prettier/recommended',
25 | 'plugin:react-hooks/recommended',
26 | 'plugin:@typescript-eslint/eslint-recommended',
27 | 'plugin:@typescript-eslint/recommended'
28 | ),
29 | jest.configs.recommended
30 | ),
31 | {
32 | settings: {
33 | react: {
34 | version: 'detect'
35 | }
36 | }
37 | }
38 | ];
39 |
--------------------------------------------------------------------------------
/examples/app.css:
--------------------------------------------------------------------------------
1 | html {
2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
3 | }
4 |
5 | html, body {
6 | height: 100%;
7 | margin: 0;
8 | }
9 |
10 | body {
11 | font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
12 | font-size: 16px;
13 | -webkit-font-smoothing: antialiased;
14 | text-rendering: optimizelegibility;
15 | line-height: 1.5;
16 | font-weight: 300;
17 | }
18 |
19 | body {
20 | display: flex;
21 | flex-direction: column;
22 | }
23 |
24 | header,
25 | #main_container {
26 | flex: 1 0 auto;
27 | }
28 |
29 | .jodit_wysiwyg {
30 | color: #000;
31 | padding: 10px;
32 | overflow-x: auto;
33 | min-height: 40px;
34 | }
35 |
36 | * {
37 | -webkit-box-sizing: border-box;
38 | -moz-box-sizing: border-box;
39 | box-sizing: border-box;
40 | }
41 |
42 | .table, .p {
43 | margin: 0 0 16px 0;
44 | }
45 |
46 | .table {
47 | border: 0;
48 | border-collapse: collapse;
49 | empty-cells: show;
50 | max-width: 100%;
51 | border-spacing: 0;
52 | *border-collapse: collapse;
53 | }
54 |
55 | . table tr {
56 | user-select: none;
57 | -o-user-select: none;
58 | -moz-user-select: none;
59 | -khtml-user-select: none;
60 | -webkit-user-select: none;
61 | -ms-user-select: none;
62 | }
63 |
64 | . table td, . table th {
65 | user-select: text;
66 | -o-user-select: text;
67 | -moz-user-select: text;
68 | -khtml-user-select: text;
69 | -webkit-user-select: text;
70 | -ms-user-select: text;
71 | }
72 |
73 | . table th, . table td {
74 | box-sizing: border-box;
75 | padding: 2px 5px;
76 | vertical-align: top;
77 | }
78 |
79 | .table td, . table th {
80 | border: 1px solid #ddd;
81 | }
82 |
83 | .container {
84 | width: 1000px;
85 | margin: 0 auto;
86 | }
87 |
88 | nav {
89 | height: 30px;
90 | background: linear-gradient(to left, #28a5f5, #1e87f0);
91 | padding: 0;
92 | overflow: hidden;
93 | }
94 |
95 | footer nav {
96 | background: #f9f9f9;
97 | margin-top: 20px;
98 | }
99 |
100 | nav > ul {
101 | padding: 0;
102 | margin: 0;
103 | }
104 |
105 | nav > ul > li {
106 | list-style: none;
107 | display: inline-block;
108 | padding: 0;
109 | margin: 0;
110 | }
111 |
112 | nav > ul > li + li {
113 | margin-left: 23px;
114 | }
115 |
116 | nav ul li a {
117 | color: #ddd;
118 | text-decoration: none;
119 | text-transform: uppercase;
120 | font-size: 0.625rem;
121 | line-height: 30px;
122 | }
123 |
124 | nav ul li a:hover {
125 | color: #aaa;
126 | text-decoration: underline;
127 | }
128 |
129 | footer nav ul li a {
130 | color: #3f3f3f;
131 | }
132 |
133 | nav ul li ul {
134 | position: absolute;
135 | margin: 0;
136 | padding: 0;
137 | background-color: #208bf1;
138 | display: none;
139 | }
140 |
141 | nav > ul > li:hover > ul {
142 | display: block;
143 | }
144 |
145 | nav ul li ul li {
146 | list-style: none;
147 | display: block;
148 | padding: 0;
149 | margin: 0;
150 | }
151 |
152 | nav ul li ul li a {
153 | padding: 5px;
154 | }
155 |
156 | .layout {
157 | display: flex;
158 | flex-direction: row;
159 | }
160 |
161 | .layout > * {
162 |
163 | }
164 |
165 | .leftside {
166 | width: 20%;
167 | padding: 10px 10px 10px 0;
168 | }
169 |
170 | .rightside {
171 | width: 80%;
172 | padding: 10px 0 10px 10px;
173 |
174 | }
175 |
176 | pre {
177 | white-space: pre-wrap;
178 | background-color: #3f3f3f;
179 | color: #fff;
180 | padding: 10px;
181 | }
182 |
183 | h1, h2, h3, h4, h5, h6 {
184 | font-weight: 500;
185 | }
186 |
--------------------------------------------------------------------------------
/examples/app.tsx:
--------------------------------------------------------------------------------
1 | import './app.css';
2 |
3 | import React, { StrictMode } from 'react';
4 | import Form from './components/Form';
5 |
6 | // For React < 18
7 | // import ReactDOM from 'react-dom';
8 | // ReactDOM.render(, document.getElementById('editor'));
9 |
10 | import { createRoot } from 'react-dom/client';
11 | const container = document.getElementById('editor')!;
12 | const root = createRoot(container);
13 | root.render(
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/examples/components/Form.css:
--------------------------------------------------------------------------------
1 | .simple-textarea {
2 | display: block;
3 | width: 100%;
4 | min-height: 100px;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/components/Form.tsx:
--------------------------------------------------------------------------------
1 | import React, { type ChangeEvent, useCallback, useState } from 'react';
2 |
3 | import JoditEditor, { Jodit } from '../../src/';
4 | import './Form.css';
5 | import type { IJodit } from 'jodit/types/types/jodit';
6 |
7 | /**
8 | * @param {Jodit} jodit
9 | */
10 | function preparePaste(jodit: IJodit) {
11 | jodit.e.on(
12 | 'paste',
13 | e => {
14 | if (confirm('Change pasted content?')) {
15 | jodit.e.stopPropagation('paste');
16 | jodit.s.insertHTML(
17 | Jodit.modules.Helpers.getDataTransfer(e)!
18 | .getData(Jodit.constants.TEXT_HTML)
19 | ?.replace(/a/g, 'b') ?? ''
20 | );
21 | return false;
22 | }
23 | },
24 | { top: true }
25 | );
26 | }
27 | Jodit.plugins.add('preparePaste', preparePaste);
28 |
29 | const Form = () => {
30 | const [isSource, setSource] = useState(false);
31 |
32 | const [config, setConfig] = useState({
33 | toolbarAdaptive: false,
34 | readonly: false,
35 | toolbar: true
36 | });
37 |
38 | const [textAreaValue, setTextAreaValue] = useState('Test');
39 |
40 | const [inputValue, setInputValue] = useState('');
41 |
42 | const [spin, setSpin] = useState(1);
43 |
44 | const toggleToolbar = useCallback(
45 | () =>
46 | setConfig(config => ({
47 | ...config,
48 | toolbar: !config.toolbar
49 | })),
50 | []
51 | );
52 |
53 | const toggleReadOnly = useCallback(
54 | () =>
55 | setConfig(config => ({
56 | ...config,
57 | readonly: !config.readonly
58 | })),
59 | []
60 | );
61 |
62 | const handleBlurAreaChange = useCallback(
63 | (textAreaValue: string, event: MouseEvent) => {
64 | console.log('handleBlurAreaChange', textAreaValue, event);
65 | },
66 | []
67 | );
68 |
69 | const handleWYSIWYGChange = useCallback((newTextAreaValue: string) => {
70 | console.log('handleWYSIWYGChange', newTextAreaValue);
71 |
72 | setTextAreaValue(newTextAreaValue);
73 | setInputValue(newTextAreaValue);
74 |
75 | return setTextAreaValue(() => newTextAreaValue);
76 | }, []);
77 |
78 | const handleNativeTextAreaChange = useCallback((e: ChangeEvent) => {
79 | const value = (e.target as HTMLTextAreaElement).value;
80 | console.log('handleNativeTextAreaChange', value);
81 | setTextAreaValue(value);
82 | setInputValue(value);
83 | }, []);
84 |
85 | const handleInputChange = useCallback(
86 | (e: ChangeEvent) => {
87 | const { value } = e.target as HTMLInputElement;
88 | setInputValue(value);
89 | handleWYSIWYGChange(value);
90 | },
91 | [handleWYSIWYGChange]
92 | );
93 |
94 | const handleSpin = useCallback(() => setSpin(spin => ++spin), []);
95 |
96 | const onSourceChange = useCallback((e: ChangeEvent) => {
97 | setSource((e.target as HTMLInputElement).checked);
98 | }, []);
99 |
100 | return (
101 |
102 |
110 |
111 | {!isSource ? (
112 |
118 | ) : (
119 |
124 | )}
125 |
126 |
132 |
133 |
136 |
137 |
140 |
141 |
144 |
145 | );
146 | };
147 |
148 | export default Form;
149 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
12 |
13 | Jodit React Example
14 |
15 |
16 |
27 |
28 |
29 |
JavaScript
30 |
31 | import './app.css';
32 |
33 | import React from 'react';
34 | import { createRoot } from 'react-dom/client';
35 |
36 | import JoditEditor from "../src/JoditEditor";
37 |
38 | const container = document.getElementById('editor')!;
39 | const root = createRoot(container);
40 | root.render(
41 | <StrictMode>
42 | <Form />
43 | </StrictMode>
44 | );
45 |
46 |
47 |
50 |
51 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/examples/webpack.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path';
2 | import process from 'node:process';
3 |
4 | export default {
5 | entry: './app.tsx',
6 | context: path.join(process.cwd(), './examples/'),
7 | devtool: 'eval',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.(js|jsx|ts|tsx)$/,
12 | use: {
13 | loader: 'swc-loader'
14 | }
15 | },
16 | {
17 | test: /\.css$/,
18 | use: ['style-loader', 'css-loader']
19 | }
20 | ]
21 | },
22 |
23 | resolve: {
24 | extensions: ['.js', '.jsx', '.ts', '.tsx']
25 | },
26 |
27 | output: {
28 | path: path.join(process.cwd(), './examples/build/'),
29 | filename: 'app.js'
30 | },
31 |
32 | devServer: {
33 | static: './examples',
34 | open: true,
35 | allowedHosts: 'all',
36 | client: {
37 | progress: true,
38 | overlay: true
39 | },
40 | port: 4000,
41 | hot: true
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import JoditEditor from './build/types/JoditEditor';
2 | import { Jodit } from './build/types/include.jodit';
3 | export default JoditEditor;
4 | export { Jodit };
5 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * For a detailed explanation regarding each configuration property, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | import type { Config } from 'jest';
7 |
8 | const config: Config = {
9 | // All imported modules in your tests should be mocked automatically
10 | // automock: false,
11 |
12 | // Stop running tests after `n` failures
13 | // bail: 0,
14 |
15 | // The directory where Jest should store its cached dependency information
16 | // cacheDirectory: "/private/var/folders/8l/p68024d92vv46q4x6txcn21n2frs00/T/jest_j4gfsw",
17 |
18 | // Automatically clear mock calls, instances, contexts and results before every test
19 | clearMocks: true,
20 |
21 | // Indicates whether the coverage information should be collected while executing the test
22 | collectCoverage: true,
23 |
24 | // An array of glob patterns indicating a set of files for which coverage information should be collected
25 | // collectCoverageFrom: undefined,
26 |
27 | // The directory where Jest should output its coverage files
28 | coverageDirectory: 'coverage',
29 |
30 | // An array of regexp pattern strings used to skip coverage collection
31 | // coveragePathIgnorePatterns: [
32 | // "/node_modules/"
33 | // ],
34 |
35 | // Indicates which provider should be used to instrument code for coverage
36 | coverageProvider: 'v8',
37 |
38 | // A list of reporter names that Jest uses when writing coverage reports
39 | // coverageReporters: [
40 | // "json",
41 | // "text",
42 | // "lcov",
43 | // "clover"
44 | // ],
45 |
46 | // An object that configures minimum threshold enforcement for coverage results
47 | // coverageThreshold: undefined,
48 |
49 | // A path to a custom dependency extractor
50 | // dependencyExtractor: undefined,
51 |
52 | // Make calling deprecated APIs throw helpful error messages
53 | // errorOnDeprecated: false,
54 |
55 | // The default configuration for fake timers
56 | // fakeTimers: {
57 | // "enableGlobally": false
58 | // },
59 |
60 | // Force coverage collection from ignored files using an array of glob patterns
61 | // forceCoverageMatch: [],
62 |
63 | // A path to a module which exports an async function that is triggered once before all test suites
64 | // globalSetup: undefined,
65 |
66 | // A path to a module which exports an async function that is triggered once after all test suites
67 | // globalTeardown: undefined,
68 |
69 | // A set of global variables that need to be available in all test environments
70 | // globals: {},
71 |
72 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
73 | // maxWorkers: "50%",
74 |
75 | // An array of directory names to be searched recursively up from the requiring module's location
76 | // moduleDirectories: [
77 | // "node_modules"
78 | // ],
79 |
80 | // An array of file extensions your modules use
81 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'css'],
82 |
83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
84 | moduleNameMapper: {
85 | '\\.(css|less)$': '/__mocks__/styleMock.js'
86 | },
87 |
88 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
89 | // modulePathIgnorePatterns: [],
90 |
91 | // Activates notifications for test results
92 | // notify: false,
93 |
94 | // An enum that specifies notification mode. Requires { notify: true }
95 | // notifyMode: "failure-change",
96 |
97 | // A preset that is used as a base for Jest's configuration
98 | // preset: undefined,
99 |
100 | // Run tests from one or more projects
101 | // projects: undefined,
102 |
103 | // Use this configuration option to add custom reporters to Jest
104 | // reporters: undefined,
105 |
106 | // Automatically reset mock state before every test
107 | // resetMocks: false,
108 |
109 | // Reset the module registry before running each individual test
110 | // resetModules: false,
111 |
112 | // A path to a custom resolver
113 | // resolver: undefined,
114 |
115 | // Automatically restore mock state and implementation before every test
116 | // restoreMocks: false,
117 |
118 | // The root directory that Jest should scan for tests and modules within
119 | // rootDir: undefined,
120 |
121 | // A list of paths to directories that Jest should use to search for files in
122 | // roots: [
123 | // ""
124 | // ],
125 |
126 | // Allows you to use a custom runner instead of Jest's default test runner
127 | // runner: "jest-runner",
128 |
129 | // The paths to modules that run some code to configure or set up the testing environment before each test
130 | // setupFiles: [],
131 |
132 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
133 | // setupFilesAfterEnv: [],
134 |
135 | // The number of seconds after which a test is considered as slow and reported as such in the results.
136 | // slowTestThreshold: 5,
137 |
138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
139 | // snapshotSerializers: [],
140 |
141 | // The test environment that will be used for testing
142 | testEnvironment: 'jsdom',
143 |
144 | // Options that will be passed to the testEnvironment
145 | // testEnvironmentOptions: {},
146 |
147 | // Adds a location field to test results
148 | // testLocationInResults: false,
149 |
150 | // The glob patterns Jest uses to detect test files
151 | // testMatch: [
152 | // "**/__tests__/**/*.[jt]s?(x)",
153 | // "**/?(*.)+(spec|test).[tj]s?(x)"
154 | // ],
155 |
156 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
157 | // testPathIgnorePatterns: [
158 | // "/node_modules/"
159 | // ],
160 |
161 | // The regexp pattern or array of patterns that Jest uses to detect test files
162 | // testRegex: [],
163 |
164 | // This option allows the use of a custom results processor
165 | // testResultsProcessor: undefined,
166 |
167 | // This option allows use of a custom test runner
168 | // testRunner: "jest-circus/runner",
169 |
170 | // A map from regular expressions to paths to transformers
171 | transform: {
172 | '^.+.tsx?$': ['ts-jest', {}]
173 | }
174 |
175 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
176 | // transformIgnorePatterns: ['/node_modules/', '\\.css$']
177 |
178 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
179 | // unmockedModulePathPatterns: undefined,
180 |
181 | // Indicates whether each individual test should be reported during the run
182 | // verbose: undefined,
183 |
184 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
185 | // watchPathIgnorePatterns: [],
186 |
187 | // Whether to use watchman for file crawling
188 | // watchman: true,
189 | };
190 |
191 | export default config;
192 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jodit-react",
3 | "version": "5.2.19",
4 | "description": "Jodit is awesome and usefully wysiwyg editor with filebrowser",
5 | "main": "build/jodit-react.js",
6 | "author": "Chupurnov (https://xdsoft.net/)",
7 | "keywords": [
8 | "react",
9 | "jodit",
10 | "html",
11 | "text",
12 | "editor",
13 | "wysisyg",
14 | "rich editor",
15 | "rich text editor",
16 | "rte"
17 | ],
18 | "dependencies": {
19 | "jodit": "^4.6.2"
20 | },
21 | "peerDependencies": {
22 | "react": "~0.14 || ^15 || ^16 || ^17 || ^18 || ^19",
23 | "react-dom": "~0.14 || ^15 || ^16 || ^17 || ^18 || ^19"
24 | },
25 | "devDependencies": {
26 | "@eslint/compat": "^1.2.5",
27 | "@jest/globals": "^29.7.0",
28 | "@swc-node/register": "^1.10.9",
29 | "@swc/core": "^1.10.7",
30 | "@testing-library/dom": "^10.4.0",
31 | "@testing-library/jest-dom": "^6.6.3",
32 | "@testing-library/react": "^16.2.0",
33 | "@testing-library/user-event": "^14.6.0",
34 | "@types/jest": "^29.5.14",
35 | "@types/react": "^19.0.7",
36 | "@types/react-dom": "^19.0.3",
37 | "@typescript-eslint/eslint-plugin": "^8.20.0",
38 | "css-loader": "^7.1.2",
39 | "eslint": "9.18.0",
40 | "eslint-config-prettier": "^10.0.1",
41 | "eslint-plugin-jest": "^28.11.0",
42 | "eslint-plugin-prettier": "^5.2.3",
43 | "eslint-plugin-react": "^7.37.4",
44 | "eslint-plugin-react-hooks": "^5.1.0",
45 | "jest": "^29.7.0",
46 | "jest-environment-jsdom": "^29.7.0",
47 | "prettier": "^3.4.2",
48 | "react": "^19.0.0",
49 | "react-dom": "^19.0.0",
50 | "replace": "^1.2.2",
51 | "style-loader": "^4.0.0",
52 | "swc-loader": "^0.2.6",
53 | "ts-jest": "^29.2.5",
54 | "ts-node": "^10.9.2",
55 | "typescript": "^5.7.3",
56 | "webpack": "^5.97.1",
57 | "webpack-cli": "^6.0.1",
58 | "webpack-dev-server": "^5.2.0"
59 | },
60 | "scripts": {
61 | "newversion": "npm version patch --no-git-tag-version && npm run github",
62 | "lint": "npm run lint:ts && npm run lint:eslint",
63 | "lint:ts": "tsc --noEmit",
64 | "lint:eslint": "eslint ./",
65 | "demo": "NODE_ENV=development node -r @swc-node/register ./node_modules/.bin/webpack serve --config ./examples/webpack.config.ts --mode development",
66 | "start": "npm run demo",
67 | "build": "npm run build:react && npm run build:types",
68 | "build:react": "NODE_ENV=production node -r @swc-node/register ./node_modules/.bin/webpack --mode production",
69 | "build:types": "rm -rf ./build/types && tsc --project tsconfig.types.json && npm run remove-css",
70 | "remove-css": "replace \"import '[^']+.css';\" '' ./build/**/*.ts",
71 | "github": "npm run git && git push --tags origin HEAD:main",
72 | "git": "git add --all && git commit -m \"New version $npm_package_version. Read more https://github.com/jodit/jodit-react/releases/tag/$npm_package_version \" && git tag $npm_package_version",
73 | "test": "jest"
74 | },
75 | "repository": {
76 | "type": "git",
77 | "url": "git+https://github.com/jodit/jodit-react.git"
78 | },
79 | "license": "MIT",
80 | "bugs": {
81 | "url": "https://github.com/jodit/jodit-react/issues"
82 | },
83 | "homepage": "https://xdsoft.net/jodit/",
84 | "ava": {
85 | "extensions": [
86 | "ts",
87 | "tsx"
88 | ],
89 | "require": [
90 | "@swc-node/register"
91 | ],
92 | "files": [
93 | "packages/**/*.spec.{ts,tsx}"
94 | ]
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/JoditEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, forwardRef } from 'react';
2 | import type { IJodit } from 'jodit/esm/types/jodit';
3 | import type { Jodit as JoditBaseConstructor } from 'jodit/esm/index';
4 | import type { Config } from 'jodit/esm/config';
5 | import { Jodit } from './include.jodit';
6 | import type { DeepPartial } from 'jodit/esm/types';
7 |
8 | function usePrevious(value: string): string {
9 | const ref = useRef('');
10 | useEffect(() => {
11 | ref.current = value;
12 | }, [value]);
13 | return ref.current;
14 | }
15 |
16 | interface Props {
17 | JoditConstructor?: T;
18 | config?: DeepPartial;
19 | className?: string;
20 | id?: string;
21 | name?: string;
22 | onBlur?: (value: string, event: MouseEvent) => void;
23 | onChange?: (value: string) => void;
24 | tabIndex?: number;
25 | value?: string;
26 | editorRef?: (editor: IJodit) => void;
27 | }
28 |
29 | const JoditEditor = forwardRef(
30 | (
31 | {
32 | JoditConstructor = Jodit,
33 | className,
34 | config,
35 | id,
36 | name,
37 | onBlur,
38 | onChange,
39 | tabIndex,
40 | value,
41 | editorRef
42 | },
43 | ref
44 | ) => {
45 | const textAreaRef = useRef(null);
46 | const joditRef = useRef(null);
47 |
48 | useEffect(() => {
49 | const element = textAreaRef.current!;
50 | const jodit = JoditConstructor.make(element, config);
51 | joditRef.current = jodit;
52 |
53 | if (typeof editorRef === 'function') {
54 | editorRef(jodit);
55 | }
56 |
57 | return () => {
58 | if (jodit.isReady) {
59 | jodit.destruct();
60 | } else {
61 | jodit
62 | .waitForReady()
63 | .then(joditInstance => joditInstance.destruct());
64 | }
65 | };
66 | }, [JoditConstructor, config, editorRef]);
67 |
68 | useEffect(() => {
69 | if (ref) {
70 | if (typeof ref === 'function') {
71 | ref(joditRef.current);
72 | } else {
73 | ref.current = joditRef.current;
74 | }
75 | }
76 | }, [textAreaRef, ref, joditRef]);
77 |
78 | const preClassName = usePrevious(className ?? '');
79 |
80 | useEffect(() => {
81 | const classList = joditRef.current?.container?.classList;
82 |
83 | if (
84 | preClassName !== className &&
85 | typeof preClassName === 'string'
86 | ) {
87 | preClassName
88 | .split(/\s+/)
89 | .filter(Boolean)
90 | .forEach(cl => classList?.remove(cl));
91 | }
92 |
93 | if (className && typeof className === 'string') {
94 | className
95 | .split(/\s+/)
96 | .filter(Boolean)
97 | .forEach(cl => classList?.add(cl));
98 | }
99 | }, [className, preClassName]);
100 |
101 | useEffect(() => {
102 | if (joditRef.current?.workplace) {
103 | joditRef.current.workplace.tabIndex = tabIndex || -1;
104 | }
105 | }, [tabIndex]);
106 |
107 | useEffect(() => {
108 | const jodit = joditRef.current;
109 | if (!jodit?.events || !(onBlur || onChange)) {
110 | return;
111 | }
112 |
113 | const onBlurHandler = (event: MouseEvent) =>
114 | onBlur && onBlur(joditRef?.current?.value ?? '', event);
115 |
116 | const onChangeHandler = (value: string) =>
117 | onChange && onChange(value);
118 |
119 | // adding event handlers
120 | jodit.events
121 | .on('blur', onBlurHandler)
122 | .on('change', onChangeHandler);
123 |
124 | return () => {
125 | // Remove event handlers
126 | jodit.events
127 | ?.off('blur', onBlurHandler)
128 | .off('change', onChangeHandler);
129 | };
130 | }, [onBlur, onChange]);
131 |
132 | useEffect(() => {
133 | const jodit = joditRef.current;
134 |
135 | const updateValue = () => {
136 | if (jodit && value !== undefined && jodit.value !== value) {
137 | jodit.value = value;
138 | }
139 | };
140 |
141 | if (jodit) {
142 | if (jodit.isReady) {
143 | updateValue();
144 | } else {
145 | jodit.waitForReady().then(updateValue);
146 | }
147 | }
148 | }, [value]);
149 |
150 | return (
151 |
152 |
158 |
159 | );
160 | }
161 | );
162 |
163 | JoditEditor.displayName = 'JoditEditor';
164 |
165 | export default JoditEditor;
166 |
--------------------------------------------------------------------------------
/src/include.jodit.ts:
--------------------------------------------------------------------------------
1 | import { Jodit as JoditES5 } from 'jodit/esm/index';
2 | import type { Jodit as JoditConstructorType } from 'jodit/esm/jodit';
3 | import 'jodit/es2021/jodit.min.css';
4 |
5 | import 'jodit/esm/plugins/all';
6 |
7 | export const Jodit: typeof JoditConstructorType = JoditES5;
8 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import JoditEditor from './JoditEditor';
2 | import { Jodit } from './include.jodit';
3 |
4 | export default JoditEditor;
5 | export { Jodit };
6 |
--------------------------------------------------------------------------------
/tests/__snapshots__/smoke.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Smoke Test Config should render without crashing 1`] = `
4 |
5 |
8 |
13 |
636 |
642 |
649 |
650 | Hello, world!
651 |
652 |
653 |
656 |
661 |
662 |
663 |
666 |
669 |
670 | Chars: 0
671 |
672 |
673 |
676 |
677 | Words: 0
678 |
679 |
680 |
687 |
702 |
703 |
704 |
709 |
710 |
711 | `;
712 |
713 | exports[`Smoke Test should render without crashing 1`] = `
714 |
715 |
718 |
723 |
1346 |
1352 |
1360 |
1361 | Hello, world!
1362 |
1363 |
1364 |
1367 |
1371 |
1372 |
1373 |
1376 |
1379 |
1380 | Chars: 0
1381 |
1382 |
1383 |
1386 |
1387 | Words: 0
1388 |
1389 |
1390 |
1397 |
1412 |
1413 |
1414 |
1419 |
1420 |
1421 | `;
1422 |
--------------------------------------------------------------------------------
/tests/onchange.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, it } from '@jest/globals';
3 | import { render } from '@testing-library/react';
4 | import JoditEditor from '../src';
5 |
6 | describe('On change Test', () => {
7 | describe('On init', () => {
8 | it('should not call handler', () => {
9 | const onChange = jest.fn();
10 | const value = 'Hello, World!
';
11 | render();
12 | expect(onChange).toHaveBeenCalledTimes(0);
13 | });
14 | });
15 |
16 | describe('On change text', () => {
17 | it('should call handler every time', async () => {
18 | const onChange = jest.fn();
19 | let value = 'Hello, World!
';
20 | const stamp = render(
21 |
22 | );
23 | value = 'Hello!
';
24 | stamp.rerender();
25 | expect(onChange).toHaveBeenCalledTimes(1);
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/ref.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, it } from '@jest/globals';
3 | import { render, act } from '@testing-library/react';
4 | import '@testing-library/jest-dom';
5 | import JoditEditor from '../src';
6 | import type { IJodit } from 'jodit/types/types/jodit';
7 |
8 | describe('Ref Test', () => {
9 | describe('Ref as function', () => {
10 | it('should be instance of imperative Jodit', () => {
11 | const ref = React.createRef();
12 |
13 | const App = () => (
14 | {
16 | ref.current = newRef;
17 | }}
18 | />
19 | );
20 |
21 | const elm = render();
22 |
23 | act(() => {
24 | elm.rerender();
25 | });
26 |
27 | expect(ref.current?.isJodit).toBe(true);
28 | });
29 | });
30 |
31 | describe('Ref as object', () => {
32 | it('should be instance of imperative Jodit', () => {
33 | const ref = React.createRef();
34 |
35 | const App = () => ;
36 |
37 | const elm = render();
38 |
39 | act(() => {
40 | elm.rerender();
41 | });
42 |
43 | expect(ref.current?.isJodit).toBe(true);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/tests/smoke.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, it } from '@jest/globals';
3 | import { render } from '@testing-library/react';
4 | import '@testing-library/jest-dom';
5 | import JoditEditor from '../src';
6 |
7 | describe('Smoke Test', () => {
8 | it('should render without crashing', () => {
9 | const { asFragment, getByText } = render(
10 | Hello, world!
'} />
11 | );
12 | expect(getByText('Hello, world!')).toBeInTheDocument();
13 | expect(asFragment()).toMatchSnapshot();
14 | });
15 |
16 | describe('Config', () => {
17 | it('should render without crashing', () => {
18 | const config = {
19 | readonly: true,
20 | sourceEditor: 'ace',
21 | disabled: true
22 | };
23 |
24 | const { asFragment, getByText } = render(
25 | Hello, world!'} config={config} />
26 | );
27 | expect(getByText('Hello, world!')).toBeInTheDocument();
28 | expect(asFragment()).toMatchSnapshot();
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/tests/theme.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, it } from '@jest/globals';
3 | import { render } from '@testing-library/react';
4 | import '@testing-library/jest-dom';
5 | import JoditEditor from '../src';
6 |
7 | describe('Theme Test', () => {
8 | it('should render with theme classname', () => {
9 | const { container } = render(
10 |
11 | );
12 | expect(
13 | container
14 | .querySelector('.jodit-container')!
15 | .classList.contains('jodit_theme_summer')
16 | ).toBe(true);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "ESNext",
5 | "outDir": "./build",
6 | "declaration": true,
7 | "declarationDir": "./build/types",
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "moduleDetection": "force",
12 | "allowJs": true,
13 | "noUncheckedIndexedAccess": true,
14 | "allowSyntheticDefaultImports": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "jsx": "react-jsx",
17 | "moduleResolution": "node",
18 | "noImplicitAny": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tsconfig.types.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["./tsconfig.json"],
3 | "compilerOptions": {
4 | "target": "ES2022",
5 | "jsx": "react",
6 | "declaration": true,
7 | "outDir": "./build/types",
8 | "declarationDir": "./build/types",
9 | "module": "ES2022",
10 | "moduleResolution": "node",
11 | "esModuleInterop": true
12 | },
13 | "files": ["./src/index"]
14 | }
15 |
--------------------------------------------------------------------------------
/webpack.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import process from 'process';
4 |
5 | export default (env: unknown, argv: { mode?: string }, dir = process.cwd()) => {
6 | const debug = !argv || !argv.mode || !argv.mode.match(/production/);
7 |
8 | return {
9 | context: dir,
10 |
11 | entry: './src/index.ts',
12 | devtool: debug ? 'inline-source-map' : false,
13 |
14 | module: {
15 | rules: [
16 | {
17 | test: /\.(js|jsx|ts|tsx)$/,
18 | use: {
19 | loader: 'swc-loader'
20 | }
21 | },
22 | {
23 | test: /\.css$/,
24 | use: ['style-loader', 'css-loader']
25 | }
26 | ]
27 | },
28 |
29 | resolve: {
30 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
31 | alias: {
32 | 'jodit-react': path.join(__dirname, './src')
33 | }
34 | },
35 |
36 | output: {
37 | path: path.join(dir, './build/'),
38 | filename: 'jodit-react.js',
39 | library: ['JoditEditor', 'Jodit'],
40 | libraryTarget: 'umd'
41 | },
42 |
43 | plugins: [
44 | new webpack.DefinePlugin({
45 | 'process.env': {
46 | NODE_ENV: JSON.stringify(
47 | debug ? 'development' : 'production'
48 | )
49 | }
50 | }),
51 | new webpack.optimize.ModuleConcatenationPlugin()
52 | ],
53 |
54 | externals: {
55 | jodit: 'jodit',
56 | Jodit: 'Jodit',
57 | react: {
58 | root: 'React',
59 | commonjs2: 'react',
60 | commonjs: 'react',
61 | amd: 'react'
62 | },
63 | 'react-dom': {
64 | root: 'ReactDOM',
65 | commonjs2: 'react-dom',
66 | commonjs: 'react-dom',
67 | amd: 'react-dom'
68 | }
69 | }
70 | };
71 | };
72 |
--------------------------------------------------------------------------------