@thangph2146/lexical-editor 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +47 -0
  2. package/dist/editor-x/editor.cjs +732 -443
  3. package/dist/editor-x/editor.cjs.map +1 -1
  4. package/dist/editor-x/editor.css +1418 -1120
  5. package/dist/editor-x/editor.css.map +1 -1
  6. package/dist/editor-x/editor.d.cts +2 -1
  7. package/dist/editor-x/editor.d.ts +2 -1
  8. package/dist/editor-x/editor.js +736 -447
  9. package/dist/editor-x/editor.js.map +1 -1
  10. package/dist/index.cjs +772 -482
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.css +1418 -1120
  13. package/dist/index.css.map +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +775 -485
  17. package/dist/index.js.map +1 -1
  18. package/package.json +86 -84
  19. package/src/components/lexical-editor.tsx +140 -123
  20. package/src/editor-x/editor.tsx +20 -5
  21. package/src/editor-x/plugins.tsx +385 -380
  22. package/src/nodes/list-with-color-node.tsx +160 -160
  23. package/src/plugins/autocomplete-plugin.tsx +2574 -2574
  24. package/src/plugins/context-menu-plugin.tsx +239 -9
  25. package/src/plugins/floating-text-format-plugin.tsx +84 -92
  26. package/src/plugins/images-plugin.tsx +4 -4
  27. package/src/plugins/list-color-plugin.tsx +178 -178
  28. package/src/plugins/tab-focus-plugin.tsx +66 -66
  29. package/src/plugins/table-column-resizer-plugin.tsx +329 -190
  30. package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
  31. package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
  32. package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
  33. package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
  34. package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
  35. package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
  36. package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
  37. package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
  38. package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
  39. package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
  40. package/src/themes/_mixins.scss +158 -10
  41. package/src/themes/_variables.scss +168 -0
  42. package/src/themes/core/_code.scss +59 -0
  43. package/src/themes/core/_images.scss +80 -0
  44. package/src/themes/core/_lists.scss +214 -0
  45. package/src/themes/core/_misc.scss +46 -0
  46. package/src/themes/core/_reset.scss +119 -0
  47. package/src/themes/core/_tables.scss +145 -0
  48. package/src/themes/core/_text.scss +35 -0
  49. package/src/themes/core/_typography.scss +73 -0
  50. package/src/themes/editor-theme.scss +9 -623
  51. package/src/themes/editor-theme.ts +118 -118
  52. package/src/themes/plugins/_auto-embed.scss +11 -0
  53. package/src/themes/plugins/_color-picker.scss +103 -0
  54. package/src/themes/plugins/_draggable-block.scss +32 -0
  55. package/src/themes/plugins/_floating-link-editor.scss +47 -0
  56. package/src/themes/plugins/_floating-toolbars.scss +61 -0
  57. package/src/themes/plugins/_image-resizer.scss +38 -0
  58. package/src/themes/plugins/_image.scss +57 -0
  59. package/src/themes/plugins/_layout.scss +39 -0
  60. package/src/themes/plugins/_list-color.scss +23 -0
  61. package/src/themes/plugins/_mentions.scss +21 -0
  62. package/src/themes/plugins/_menus-and-pickers.scss +153 -0
  63. package/src/themes/plugins/_table.scss +20 -0
  64. package/src/themes/plugins/_toolbar.scss +36 -0
  65. package/src/themes/plugins/_tree-view.scss +11 -0
  66. package/src/themes/plugins.scss +20 -1165
  67. package/src/themes/ui-components/_animations.scss +31 -0
  68. package/src/themes/ui-components/_backgrounds.scss +27 -0
  69. package/src/themes/ui-components/_borders.scss +20 -0
  70. package/src/themes/ui-components/_button.scss +176 -0
  71. package/src/themes/ui-components/_checkbox.scss +14 -0
  72. package/src/themes/ui-components/_cursors.scss +31 -0
  73. package/src/themes/ui-components/_dialog.scss +86 -0
  74. package/src/themes/ui-components/_display-sizing.scss +100 -0
  75. package/src/themes/ui-components/_flex.scss +124 -0
  76. package/src/themes/ui-components/_form-layout.scss +15 -0
  77. package/src/themes/ui-components/_icons.scss +23 -0
  78. package/src/themes/ui-components/_input.scss +86 -0
  79. package/src/themes/ui-components/_label.scss +19 -0
  80. package/src/themes/ui-components/_loader.scss +9 -0
  81. package/src/themes/ui-components/_margins-paddings.scss +45 -0
  82. package/src/themes/ui-components/_popover.scss +16 -0
  83. package/src/themes/ui-components/_positioning.scss +73 -0
  84. package/src/themes/ui-components/_rounded.scss +19 -0
  85. package/src/themes/ui-components/_scroll-area.scss +11 -0
  86. package/src/themes/ui-components/_select.scss +110 -0
  87. package/src/themes/ui-components/_separator.scss +19 -0
  88. package/src/themes/ui-components/_shadow.scss +15 -0
  89. package/src/themes/ui-components/_tabs.scss +46 -0
  90. package/src/themes/ui-components/_text-utilities.scss +48 -0
  91. package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
  92. package/src/themes/ui-components/_toggle.scss +80 -0
  93. package/src/themes/ui-components/_typography.scss +22 -0
  94. package/src/themes/ui-components.scss +27 -937
  95. package/src/transformers/markdown-list-transformer.ts +51 -51
  96. package/src/ui/button.tsx +11 -2
  97. package/src/ui/collapsible.tsx +1 -1
  98. package/src/ui/dialog.tsx +2 -2
  99. package/src/ui/flex.tsx +4 -4
  100. package/src/ui/popover.tsx +1 -1
  101. package/src/ui/tooltip.tsx +2 -2
package/package.json CHANGED
@@ -1,84 +1,86 @@
1
- {
2
- "name": "@thangph2146/lexical-editor",
3
- "version": "0.0.4",
4
- "type": "module",
5
- "private": false,
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "files": [
10
- "dist",
11
- "src"
12
- ],
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
- "dependencies": {
17
- "@lexical/code": "^0.38.2",
18
- "@lexical/file": "^0.38.2",
19
- "@lexical/hashtag": "^0.38.2",
20
- "@lexical/link": "^0.38.2",
21
- "@lexical/list": "^0.38.2",
22
- "@lexical/markdown": "^0.38.2",
23
- "@lexical/overflow": "^0.38.2",
24
- "@lexical/rich-text": "^0.38.2",
25
- "@lexical/selection": "^0.38.2",
26
- "@lexical/table": "^0.38.2",
27
- "@lexical/text": "^0.38.2",
28
- "@lexical/utils": "^0.38.2",
29
- "@radix-ui/react-slider": "^1.2.3",
30
- "@radix-ui/react-slot": "^1.2.4",
31
- "framer-motion": "^12.4.7",
32
- "lucide-react": "^0.552.0",
33
- "sonner": "^2.0.7"
34
- },
35
- "peerDependencies": {
36
- "@lexical/react": "^0.38.2",
37
- "lexical": "^0.38.2",
38
- "react": ">=18",
39
- "react-dom": ">=18"
40
- },
41
- "devDependencies": {
42
- "@types/node": "^25.1.0",
43
- "@types/react": "^19.2.10",
44
- "@types/react-dom": "^19.2.3",
45
- "esbuild-sass-plugin": "^3.3.1",
46
- "eslint": "^9.39.2",
47
- "next": "16.1.6",
48
- "sass": "^1.83.0",
49
- "tsup": "^8.4.0",
50
- "typescript": "^5.9.3",
51
- "@workspace/typescript-config": "0.0.0",
52
- "@workspace/eslint-config": "0.0.0"
53
- },
54
- "exports": {
55
- ".": {
56
- "types": "./dist/index.d.ts",
57
- "import": "./dist/index.js",
58
- "require": "./dist/index.cjs"
59
- },
60
- "./style.css": "./dist/index.css",
61
- "./editor": {
62
- "types": "./dist/editor-x/editor.d.ts",
63
- "import": "./dist/editor-x/editor.js",
64
- "require": "./dist/editor-x/editor.cjs"
65
- },
66
- "./styles": {
67
- "sass": "./src/themes/editor-theme.scss",
68
- "default": "./src/themes/editor-theme.scss"
69
- },
70
- "./variables": {
71
- "sass": "./src/themes/_variables.scss",
72
- "default": "./src/themes/_variables.scss"
73
- },
74
- "./themes/*": "./src/themes/*.ts",
75
- "./utils/*": "./src/utils/*.ts"
76
- },
77
- "scripts": {
78
- "build": "tsup",
79
- "dev": "tsup --watch",
80
- "lint": "eslint",
81
- "format": "prettier --write \"**/*.{ts,tsx}\"",
82
- "typecheck": "tsc --noEmit"
83
- }
84
- }
1
+ {
2
+ "name": "@thangph2146/lexical-editor",
3
+ "version": "0.0.6",
4
+ "description": "Rich Text Editor library based on Lexical for React/Next.js",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "private": false,
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "eslint",
22
+ "format": "prettier --write \"**/*.{ts,tsx}\"",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "@lexical/code": "^0.38.2",
27
+ "@lexical/file": "^0.38.2",
28
+ "@lexical/hashtag": "^0.38.2",
29
+ "@lexical/link": "^0.38.2",
30
+ "@lexical/list": "^0.38.2",
31
+ "@lexical/markdown": "^0.38.2",
32
+ "@lexical/overflow": "^0.38.2",
33
+ "@lexical/rich-text": "^0.38.2",
34
+ "@lexical/selection": "^0.38.2",
35
+ "@lexical/table": "^0.38.2",
36
+ "@lexical/text": "^0.38.2",
37
+ "@lexical/utils": "^0.38.2",
38
+ "@radix-ui/react-slider": "^1.2.3",
39
+ "@radix-ui/react-slot": "^1.2.4",
40
+ "framer-motion": "^12.4.7",
41
+ "lucide-react": "^0.552.0",
42
+ "sonner": "^2.0.7"
43
+ },
44
+ "peerDependencies": {
45
+ "@lexical/react": "^0.38.2",
46
+ "lexical": "^0.38.2",
47
+ "react": ">=18",
48
+ "react-dom": ">=18"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.1.0",
52
+ "@types/react": "^19.2.10",
53
+ "@types/react-dom": "^19.2.3",
54
+ "@workspace/eslint-config": "workspace:*",
55
+ "@workspace/typescript-config": "workspace:*",
56
+ "esbuild-sass-plugin": "^3.3.1",
57
+ "eslint": "^9.39.2",
58
+ "next": "16.1.6",
59
+ "sass": "^1.83.0",
60
+ "tsup": "^8.4.0",
61
+ "typescript": "^5.9.3"
62
+ },
63
+ "exports": {
64
+ ".": {
65
+ "types": "./dist/index.d.ts",
66
+ "import": "./dist/index.js",
67
+ "require": "./dist/index.cjs"
68
+ },
69
+ "./style.css": "./dist/index.css",
70
+ "./editor": {
71
+ "types": "./dist/editor-x/editor.d.ts",
72
+ "import": "./dist/editor-x/editor.js",
73
+ "require": "./dist/editor-x/editor.cjs"
74
+ },
75
+ "./styles": {
76
+ "sass": "./src/themes/editor-theme.scss",
77
+ "default": "./src/themes/editor-theme.scss"
78
+ },
79
+ "./variables": {
80
+ "sass": "./src/themes/_variables.scss",
81
+ "default": "./src/themes/_variables.scss"
82
+ },
83
+ "./themes/*": "./src/themes/*.ts",
84
+ "./utils/*": "./src/utils/*.ts"
85
+ }
86
+ }
@@ -1,123 +1,140 @@
1
- "use client"
2
-
3
- import { useState, useEffect, useRef } from "react"
4
- import { Editor } from "../editor-x/editor"
5
- import type { SerializedEditorState } from "lexical"
6
- import { logger } from "../lib/logger"
7
-
8
- export interface LexicalEditorProps {
9
- value?: unknown
10
- onChange?: (value: SerializedEditorState) => void
11
- readOnly?: boolean
12
- className?: string
13
- placeholder?: string
14
- }
15
-
16
- export function LexicalEditor({
17
- value,
18
- onChange,
19
- readOnly = false,
20
- className,
21
- }: LexicalEditorProps) {
22
- // Parse initial value as SerializedEditorState
23
- const [editorState, setEditorState] = useState<SerializedEditorState | undefined>(() => {
24
- if (value && typeof value === "object" && value !== null) {
25
- try {
26
- return value as unknown as SerializedEditorState
27
- } catch {
28
- return undefined
29
- }
30
- }
31
- // If value is a JSON string, try to parse it
32
- if (typeof value === "string" && value.trim().startsWith("{")) {
33
- try {
34
- const parsed = JSON.parse(value)
35
- if (parsed && typeof parsed === "object" && parsed !== null) {
36
- return parsed as SerializedEditorState
37
- }
38
- } catch {
39
- // Invalid JSON, return undefined
40
- }
41
- }
42
- return undefined
43
- })
44
-
45
- // Ref to track if we are syncing from external value
46
- const isSyncingRef = useRef(false)
47
-
48
- useEffect(() => {
49
- if (isSyncingRef.current) return
50
-
51
- if (value && typeof value === "object" && value !== null) {
52
- try {
53
- const newState = value as unknown as SerializedEditorState
54
- const currentStateStr = editorState ? JSON.stringify(editorState) : null
55
- const newStateStr = JSON.stringify(newState)
56
- if (currentStateStr !== newStateStr) {
57
- isSyncingRef.current = true
58
- setEditorState(newState)
59
- setTimeout(() => {
60
- isSyncingRef.current = false
61
- }, 0)
62
- }
63
- } catch (error) {
64
- logger.error("[LexicalEditor] Error parsing value object:", error)
65
- }
66
- } else if (typeof value === "string" && value.trim().startsWith("{")) {
67
- try {
68
- const parsed = JSON.parse(value)
69
- if (parsed && typeof parsed === "object" && parsed !== null) {
70
- const newState = parsed as SerializedEditorState
71
- const currentStateStr = editorState ? JSON.stringify(editorState) : null
72
- const newStateStr = JSON.stringify(newState)
73
- if (currentStateStr !== newStateStr) {
74
- isSyncingRef.current = true
75
- setEditorState(newState)
76
- setTimeout(() => {
77
- isSyncingRef.current = false
78
- }, 0)
79
- }
80
- }
81
- } catch (error) {
82
- logger.error("[LexicalEditor] Error parsing value string:", error)
83
- }
84
- } else if (value === null || value === undefined) {
85
- if (editorState !== undefined) {
86
- isSyncingRef.current = true
87
- setEditorState(undefined)
88
- setTimeout(() => {
89
- isSyncingRef.current = false
90
- }, 0)
91
- }
92
- }
93
- }, [value, editorState])
94
-
95
- const handleSerializedChange = (newState: SerializedEditorState) => {
96
- if (readOnly) return
97
-
98
- // Avoid triggering update if state hasn't effectively changed (optional optimization)
99
- // But Lexical's onChange usually implies a change.
100
-
101
- // Update local state to avoid re-syncing from props immediately if parent re-renders
102
- // isSyncingRef.current = true
103
- setEditorState(newState)
104
-
105
- if (onChange) {
106
- onChange(newState)
107
- }
108
-
109
- // setTimeout(() => {
110
- // isSyncingRef.current = false
111
- // }, 0)
112
- }
113
-
114
- return (
115
- <div className={className}>
116
- <Editor
117
- editorSerializedState={editorState}
118
- onSerializedChange={handleSerializedChange}
119
- readOnly={readOnly}
120
- />
121
- </div>
122
- )
123
- }
1
+ "use client"
2
+
3
+ import { useState, useEffect, useRef } from "react"
4
+ import { Editor } from "../editor-x/editor"
5
+ import type { SerializedEditorState } from "lexical"
6
+ import { logger } from "../lib/logger"
7
+
8
+ export interface LexicalEditorProps {
9
+ value?: unknown
10
+ onChange?: (value: SerializedEditorState) => void
11
+ readOnly?: boolean
12
+ className?: string
13
+ placeholder?: string
14
+ }
15
+
16
+ function isValidSerializedEditorState(value: unknown): value is SerializedEditorState {
17
+ return (
18
+ value !== null &&
19
+ typeof value === "object" &&
20
+ "root" in value &&
21
+ value.root !== null &&
22
+ typeof value.root === "object" &&
23
+ "type" in (value.root as Record<string, unknown>) &&
24
+ (value.root as Record<string, unknown>).type === "root"
25
+ )
26
+ }
27
+
28
+ export function LexicalEditor({
29
+ value,
30
+ onChange,
31
+ readOnly = false,
32
+ className,
33
+ placeholder = "",
34
+ }: LexicalEditorProps) {
35
+ // Parse initial value as SerializedEditorState
36
+ const [editorState, setEditorState] = useState<SerializedEditorState | undefined>(() => {
37
+ if (value && typeof value === "object" && value !== null) {
38
+ if (isValidSerializedEditorState(value)) {
39
+ return value
40
+ }
41
+ return undefined
42
+ }
43
+ // If value is a JSON string, try to parse it
44
+ if (typeof value === "string" && value.trim().startsWith("{")) {
45
+ try {
46
+ const parsed = JSON.parse(value)
47
+ if (isValidSerializedEditorState(parsed)) {
48
+ return parsed
49
+ }
50
+ } catch {
51
+ // Invalid JSON, return undefined
52
+ }
53
+ }
54
+ return undefined
55
+ })
56
+
57
+ // Ref to track if we are syncing from external value
58
+ const isSyncingRef = useRef(false)
59
+ const lastValueRef = useRef(value)
60
+
61
+ // Ref to track current state for comparison
62
+ const editorStateRef = useRef(editorState)
63
+ useEffect(() => {
64
+ editorStateRef.current = editorState
65
+ }, [editorState])
66
+
67
+ useEffect(() => {
68
+ // Skip if value hasn't changed by reference
69
+ if (value === lastValueRef.current && !isSyncingRef.current) return
70
+
71
+ lastValueRef.current = value
72
+
73
+ const syncState = (newState: SerializedEditorState | undefined) => {
74
+ const currentStateStr = JSON.stringify(editorStateRef.current)
75
+ const newStateStr = JSON.stringify(newState)
76
+
77
+ if (currentStateStr !== newStateStr) {
78
+ isSyncingRef.current = true
79
+ setEditorState(newState)
80
+ // Reset syncing flag in next tick
81
+ setTimeout(() => {
82
+ isSyncingRef.current = false
83
+ }, 0)
84
+ }
85
+ }
86
+
87
+ if (value && typeof value === "object" && value !== null) {
88
+ if (isValidSerializedEditorState(value)) {
89
+ syncState(value)
90
+ } else {
91
+ logger.error("[LexicalEditor] Invalid value object structure:", value)
92
+ }
93
+ } else if (typeof value === "string" && value.trim().startsWith("{")) {
94
+ try {
95
+ const parsed = JSON.parse(value)
96
+ if (isValidSerializedEditorState(parsed)) {
97
+ syncState(parsed)
98
+ } else {
99
+ logger.error("[LexicalEditor] Invalid parsed JSON structure:", parsed)
100
+ }
101
+ } catch (error) {
102
+ logger.error("[LexicalEditor] Error parsing value string:", error)
103
+ }
104
+ } else if (value === null || value === undefined) {
105
+ if (editorStateRef.current !== undefined) {
106
+ syncState(undefined)
107
+ }
108
+ }
109
+ }, [value]) // Only depend on value
110
+
111
+ const handleSerializedChange = (newState: SerializedEditorState) => {
112
+ if (readOnly) return
113
+
114
+ // Avoid triggering update if state hasn't effectively changed (optional optimization)
115
+ // But Lexical's onChange usually implies a change.
116
+
117
+ // Update local state to avoid re-syncing from props immediately if parent re-renders
118
+ // isSyncingRef.current = true
119
+ setEditorState(newState)
120
+
121
+ if (onChange) {
122
+ onChange(newState)
123
+ }
124
+
125
+ // setTimeout(() => {
126
+ // isSyncingRef.current = false
127
+ // }, 0)
128
+ }
129
+
130
+ return (
131
+ <div className={className}>
132
+ <Editor
133
+ editorSerializedState={editorState}
134
+ onSerializedChange={handleSerializedChange}
135
+ readOnly={readOnly}
136
+ placeholder={placeholder}
137
+ />
138
+ </div>
139
+ )
140
+ }
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import { useEffect, useState } from "react"
3
+ import { useEffect, useMemo, useState } from "react"
4
4
  import {
5
5
  InitialConfigType,
6
6
  LexicalComposer,
@@ -39,19 +39,21 @@ export function Editor({
39
39
  onChange,
40
40
  onSerializedChange,
41
41
  readOnly = false,
42
+ placeholder = "",
42
43
  }: {
43
44
  editorState?: EditorState
44
45
  editorSerializedState?: SerializedEditorState
45
46
  onChange?: (editorState: EditorState) => void
46
47
  onSerializedChange?: (editorSerializedState: SerializedEditorState) => void
47
48
  readOnly?: boolean
49
+ placeholder?: string
48
50
  }) {
49
51
  const { ref: editorRef, width: editorWidth } = useElementSize<HTMLDivElement>()
50
52
  const editorMaxWidth = editorWidth || undefined
51
53
 
52
54
  const [config, setConfig] = useState<{
53
55
  nodes: InitialConfigType["nodes"]
54
- Plugins: React.ComponentType<{ readOnly?: boolean }>
56
+ Plugins: React.ComponentType<{ readOnly?: boolean; placeholder?: string }>
55
57
  } | null>(null)
56
58
 
57
59
  useEffect(() => {
@@ -61,7 +63,21 @@ export function Editor({
61
63
  ]).then(([nodes, { Plugins }]) => setConfig({ nodes, Plugins }))
62
64
  }, [])
63
65
 
64
- if (!config) {
66
+ const editorConfig = useMemo(() => {
67
+ if (!config || !config.nodes) return null
68
+
69
+ const validNodes = config.nodes.filter((node) => {
70
+ if (node === undefined) {
71
+ console.error("[Editor] Found undefined node in config.nodes")
72
+ return false
73
+ }
74
+ return true
75
+ })
76
+
77
+ return createEditorConfig(validNodes)
78
+ }, [config])
79
+
80
+ if (!config || !editorConfig) {
65
81
  return (
66
82
  <div
67
83
  className={cn(
@@ -74,7 +90,6 @@ export function Editor({
74
90
  )
75
91
  }
76
92
 
77
- const editorConfig = createEditorConfig(config.nodes)
78
93
  const { Plugins } = config
79
94
 
80
95
  return (
@@ -98,7 +113,7 @@ export function Editor({
98
113
  }}
99
114
  >
100
115
  <TooltipProvider>
101
- <Plugins readOnly={readOnly} />
116
+ <Plugins readOnly={readOnly} placeholder={placeholder} />
102
117
 
103
118
  {!readOnly && (
104
119
  <OnChangePlugin