@thangph2146/lexical-editor 0.0.3 → 0.0.5
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.
- package/dist/editor-x/editor.cjs +724 -435
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +1391 -1091
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.js +728 -439
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +760 -472
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1391 -1091
- package/dist/index.css.map +1 -1
- package/dist/index.js +763 -475
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/lexical-editor.tsx +138 -123
- package/src/editor-ui/content-editable.tsx +3 -3
- package/src/editor-x/editor.tsx +16 -3
- package/src/editor-x/plugins.tsx +385 -380
- package/src/nodes/list-with-color-node.tsx +160 -160
- package/src/plugins/autocomplete-plugin.tsx +2574 -2574
- package/src/plugins/context-menu-plugin.tsx +239 -9
- package/src/plugins/floating-text-format-plugin.tsx +84 -92
- package/src/plugins/images-plugin.tsx +4 -4
- package/src/plugins/list-color-plugin.tsx +178 -178
- package/src/plugins/tab-focus-plugin.tsx +66 -66
- package/src/plugins/table-column-resizer-plugin.tsx +329 -190
- package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
- package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
- package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
- package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
- package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
- package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
- package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
- package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
- package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
- package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
- package/src/themes/_mixins.scss +158 -10
- package/src/themes/_variables.scss +168 -0
- package/src/themes/core/_code.scss +59 -0
- package/src/themes/core/_images.scss +80 -0
- package/src/themes/core/_lists.scss +214 -0
- package/src/themes/core/_misc.scss +46 -0
- package/src/themes/core/_reset.scss +119 -0
- package/src/themes/core/_tables.scss +145 -0
- package/src/themes/core/_text.scss +35 -0
- package/src/themes/core/_typography.scss +73 -0
- package/src/themes/editor-theme copy.scss +763 -0
- package/src/themes/editor-theme.scss +9 -621
- package/src/themes/editor-theme.ts +118 -118
- package/src/themes/plugins/_auto-embed.scss +11 -0
- package/src/themes/plugins/_color-picker.scss +103 -0
- package/src/themes/plugins/_draggable-block.scss +32 -0
- package/src/themes/plugins/_floating-link-editor.scss +47 -0
- package/src/themes/plugins/_floating-toolbars.scss +61 -0
- package/src/themes/plugins/_image-resizer.scss +38 -0
- package/src/themes/plugins/_image.scss +57 -0
- package/src/themes/plugins/_layout.scss +39 -0
- package/src/themes/plugins/_list-color.scss +23 -0
- package/src/themes/plugins/_mentions.scss +21 -0
- package/src/themes/plugins/_menus-and-pickers.scss +153 -0
- package/src/themes/plugins/_table.scss +20 -0
- package/src/themes/plugins/_toolbar.scss +36 -0
- package/src/themes/plugins/_tree-view.scss +11 -0
- package/src/themes/plugins copy.scss +656 -0
- package/src/themes/plugins.scss +20 -1165
- package/src/themes/ui-components/_animations.scss +31 -0
- package/src/themes/ui-components/_backgrounds.scss +27 -0
- package/src/themes/ui-components/_borders.scss +20 -0
- package/src/themes/ui-components/_button.scss +176 -0
- package/src/themes/ui-components/_checkbox.scss +14 -0
- package/src/themes/ui-components/_cursors.scss +31 -0
- package/src/themes/ui-components/_dialog.scss +86 -0
- package/src/themes/ui-components/_display-sizing.scss +100 -0
- package/src/themes/ui-components/_flex.scss +124 -0
- package/src/themes/ui-components/_form-layout.scss +15 -0
- package/src/themes/ui-components/_icons.scss +23 -0
- package/src/themes/ui-components/_input.scss +86 -0
- package/src/themes/ui-components/_label.scss +19 -0
- package/src/themes/ui-components/_loader.scss +9 -0
- package/src/themes/ui-components/_margins-paddings.scss +45 -0
- package/src/themes/ui-components/_popover.scss +16 -0
- package/src/themes/ui-components/_positioning.scss +73 -0
- package/src/themes/ui-components/_rounded.scss +19 -0
- package/src/themes/ui-components/_scroll-area.scss +11 -0
- package/src/themes/ui-components/_select.scss +110 -0
- package/src/themes/ui-components/_separator.scss +19 -0
- package/src/themes/ui-components/_shadow.scss +15 -0
- package/src/themes/ui-components/_tabs.scss +46 -0
- package/src/themes/ui-components/_text-utilities.scss +48 -0
- package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
- package/src/themes/ui-components/_toggle.scss +80 -0
- package/src/themes/ui-components/_typography.scss +22 -0
- package/src/themes/ui-components copy.scss +1335 -0
- package/src/themes/ui-components.scss +27 -937
- package/src/transformers/markdown-list-transformer.ts +51 -51
- package/src/ui/button.tsx +11 -2
- package/src/ui/collapsible.tsx +1 -1
- package/src/ui/dialog.tsx +2 -2
- package/src/ui/flex.tsx +4 -4
- package/src/ui/popover.tsx +1 -1
- package/src/ui/tooltip.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,123 +1,138 @@
|
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
}: LexicalEditorProps) {
|
|
34
|
+
// Parse initial value as SerializedEditorState
|
|
35
|
+
const [editorState, setEditorState] = useState<SerializedEditorState | undefined>(() => {
|
|
36
|
+
if (value && typeof value === "object" && value !== null) {
|
|
37
|
+
if (isValidSerializedEditorState(value)) {
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
// If value is a JSON string, try to parse it
|
|
43
|
+
if (typeof value === "string" && value.trim().startsWith("{")) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(value)
|
|
46
|
+
if (isValidSerializedEditorState(parsed)) {
|
|
47
|
+
return parsed
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// Invalid JSON, return undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Ref to track if we are syncing from external value
|
|
57
|
+
const isSyncingRef = useRef(false)
|
|
58
|
+
const lastValueRef = useRef(value)
|
|
59
|
+
|
|
60
|
+
// Ref to track current state for comparison
|
|
61
|
+
const editorStateRef = useRef(editorState)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
editorStateRef.current = editorState
|
|
64
|
+
}, [editorState])
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
// Skip if value hasn't changed by reference
|
|
68
|
+
if (value === lastValueRef.current && !isSyncingRef.current) return
|
|
69
|
+
|
|
70
|
+
lastValueRef.current = value
|
|
71
|
+
|
|
72
|
+
const syncState = (newState: SerializedEditorState | undefined) => {
|
|
73
|
+
const currentStateStr = JSON.stringify(editorStateRef.current)
|
|
74
|
+
const newStateStr = JSON.stringify(newState)
|
|
75
|
+
|
|
76
|
+
if (currentStateStr !== newStateStr) {
|
|
77
|
+
isSyncingRef.current = true
|
|
78
|
+
setEditorState(newState)
|
|
79
|
+
// Reset syncing flag in next tick
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
isSyncingRef.current = false
|
|
82
|
+
}, 0)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (value && typeof value === "object" && value !== null) {
|
|
87
|
+
if (isValidSerializedEditorState(value)) {
|
|
88
|
+
syncState(value)
|
|
89
|
+
} else {
|
|
90
|
+
logger.error("[LexicalEditor] Invalid value object structure:", value)
|
|
91
|
+
}
|
|
92
|
+
} else if (typeof value === "string" && value.trim().startsWith("{")) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(value)
|
|
95
|
+
if (isValidSerializedEditorState(parsed)) {
|
|
96
|
+
syncState(parsed)
|
|
97
|
+
} else {
|
|
98
|
+
logger.error("[LexicalEditor] Invalid parsed JSON structure:", parsed)
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logger.error("[LexicalEditor] Error parsing value string:", error)
|
|
102
|
+
}
|
|
103
|
+
} else if (value === null || value === undefined) {
|
|
104
|
+
if (editorStateRef.current !== undefined) {
|
|
105
|
+
syncState(undefined)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, [value]) // Only depend on value
|
|
109
|
+
|
|
110
|
+
const handleSerializedChange = (newState: SerializedEditorState) => {
|
|
111
|
+
if (readOnly) return
|
|
112
|
+
|
|
113
|
+
// Avoid triggering update if state hasn't effectively changed (optional optimization)
|
|
114
|
+
// But Lexical's onChange usually implies a change.
|
|
115
|
+
|
|
116
|
+
// Update local state to avoid re-syncing from props immediately if parent re-renders
|
|
117
|
+
// isSyncingRef.current = true
|
|
118
|
+
setEditorState(newState)
|
|
119
|
+
|
|
120
|
+
if (onChange) {
|
|
121
|
+
onChange(newState)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// setTimeout(() => {
|
|
125
|
+
// isSyncingRef.current = false
|
|
126
|
+
// }, 0)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div className={className}>
|
|
131
|
+
<Editor
|
|
132
|
+
editorSerializedState={editorState}
|
|
133
|
+
onSerializedChange={handleSerializedChange}
|
|
134
|
+
readOnly={readOnly}
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
@@ -15,13 +15,13 @@ export function ContentEditable({
|
|
|
15
15
|
placeholderClassName,
|
|
16
16
|
placeholderDefaults = true,
|
|
17
17
|
}: Props): JSX.Element {
|
|
18
|
-
const
|
|
18
|
+
const isReadOnlyOrReview = className?.includes("--readonly") || className?.includes("--review")
|
|
19
19
|
|
|
20
20
|
return (
|
|
21
21
|
<LexicalContentEditable
|
|
22
22
|
className={cn(
|
|
23
23
|
"ContentEditable__root relative block focus:outline-none",
|
|
24
|
-
!
|
|
24
|
+
!isReadOnlyOrReview && "min-h-72 px-8 py-4",
|
|
25
25
|
className
|
|
26
26
|
)}
|
|
27
27
|
aria-placeholder={placeholder}
|
|
@@ -31,7 +31,7 @@ export function ContentEditable({
|
|
|
31
31
|
className={cn(
|
|
32
32
|
placeholderClassName,
|
|
33
33
|
"text-muted-foreground pointer-events-none select-none",
|
|
34
|
-
placeholderDefaults && !
|
|
34
|
+
placeholderDefaults && !isReadOnlyOrReview && "absolute top-0 left-0 overflow-hidden px-8 py-[18px] text-ellipsis"
|
|
35
35
|
)}
|
|
36
36
|
>
|
|
37
37
|
{placeholder}
|
package/src/editor-x/editor.tsx
CHANGED
|
@@ -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,
|
|
@@ -61,7 +61,21 @@ export function Editor({
|
|
|
61
61
|
]).then(([nodes, { Plugins }]) => setConfig({ nodes, Plugins }))
|
|
62
62
|
}, [])
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
const editorConfig = useMemo(() => {
|
|
65
|
+
if (!config || !config.nodes) return null
|
|
66
|
+
|
|
67
|
+
const validNodes = config.nodes.filter((node) => {
|
|
68
|
+
if (node === undefined) {
|
|
69
|
+
console.error("[Editor] Found undefined node in config.nodes")
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
return true
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return createEditorConfig(validNodes)
|
|
76
|
+
}, [config])
|
|
77
|
+
|
|
78
|
+
if (!config || !editorConfig) {
|
|
65
79
|
return (
|
|
66
80
|
<div
|
|
67
81
|
className={cn(
|
|
@@ -74,7 +88,6 @@ export function Editor({
|
|
|
74
88
|
)
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
const editorConfig = createEditorConfig(config.nodes)
|
|
78
91
|
const { Plugins } = config
|
|
79
92
|
|
|
80
93
|
return (
|