@thangph2146/lexical-editor 0.0.4 → 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.
Files changed (99) hide show
  1. package/dist/editor-x/editor.cjs +721 -432
  2. package/dist/editor-x/editor.cjs.map +1 -1
  3. package/dist/editor-x/editor.css +1418 -1120
  4. package/dist/editor-x/editor.css.map +1 -1
  5. package/dist/editor-x/editor.js +725 -436
  6. package/dist/editor-x/editor.js.map +1 -1
  7. package/dist/index.cjs +757 -469
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.css +1418 -1120
  10. package/dist/index.css.map +1 -1
  11. package/dist/index.js +760 -472
  12. package/dist/index.js.map +1 -1
  13. package/package.json +3 -3
  14. package/src/components/lexical-editor.tsx +138 -123
  15. package/src/editor-x/editor.tsx +16 -3
  16. package/src/editor-x/plugins.tsx +385 -380
  17. package/src/nodes/list-with-color-node.tsx +160 -160
  18. package/src/plugins/autocomplete-plugin.tsx +2574 -2574
  19. package/src/plugins/context-menu-plugin.tsx +239 -9
  20. package/src/plugins/floating-text-format-plugin.tsx +84 -92
  21. package/src/plugins/images-plugin.tsx +4 -4
  22. package/src/plugins/list-color-plugin.tsx +178 -178
  23. package/src/plugins/tab-focus-plugin.tsx +66 -66
  24. package/src/plugins/table-column-resizer-plugin.tsx +329 -190
  25. package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
  26. package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
  27. package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
  28. package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
  29. package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
  30. package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
  31. package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
  32. package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
  33. package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
  34. package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
  35. package/src/themes/_mixins.scss +158 -10
  36. package/src/themes/_variables.scss +168 -0
  37. package/src/themes/core/_code.scss +59 -0
  38. package/src/themes/core/_images.scss +80 -0
  39. package/src/themes/core/_lists.scss +214 -0
  40. package/src/themes/core/_misc.scss +46 -0
  41. package/src/themes/core/_reset.scss +119 -0
  42. package/src/themes/core/_tables.scss +145 -0
  43. package/src/themes/core/_text.scss +35 -0
  44. package/src/themes/core/_typography.scss +73 -0
  45. package/src/themes/editor-theme copy.scss +763 -0
  46. package/src/themes/editor-theme.scss +9 -623
  47. package/src/themes/editor-theme.ts +118 -118
  48. package/src/themes/plugins/_auto-embed.scss +11 -0
  49. package/src/themes/plugins/_color-picker.scss +103 -0
  50. package/src/themes/plugins/_draggable-block.scss +32 -0
  51. package/src/themes/plugins/_floating-link-editor.scss +47 -0
  52. package/src/themes/plugins/_floating-toolbars.scss +61 -0
  53. package/src/themes/plugins/_image-resizer.scss +38 -0
  54. package/src/themes/plugins/_image.scss +57 -0
  55. package/src/themes/plugins/_layout.scss +39 -0
  56. package/src/themes/plugins/_list-color.scss +23 -0
  57. package/src/themes/plugins/_mentions.scss +21 -0
  58. package/src/themes/plugins/_menus-and-pickers.scss +153 -0
  59. package/src/themes/plugins/_table.scss +20 -0
  60. package/src/themes/plugins/_toolbar.scss +36 -0
  61. package/src/themes/plugins/_tree-view.scss +11 -0
  62. package/src/themes/plugins copy.scss +656 -0
  63. package/src/themes/plugins.scss +20 -1165
  64. package/src/themes/ui-components/_animations.scss +31 -0
  65. package/src/themes/ui-components/_backgrounds.scss +27 -0
  66. package/src/themes/ui-components/_borders.scss +20 -0
  67. package/src/themes/ui-components/_button.scss +176 -0
  68. package/src/themes/ui-components/_checkbox.scss +14 -0
  69. package/src/themes/ui-components/_cursors.scss +31 -0
  70. package/src/themes/ui-components/_dialog.scss +86 -0
  71. package/src/themes/ui-components/_display-sizing.scss +100 -0
  72. package/src/themes/ui-components/_flex.scss +124 -0
  73. package/src/themes/ui-components/_form-layout.scss +15 -0
  74. package/src/themes/ui-components/_icons.scss +23 -0
  75. package/src/themes/ui-components/_input.scss +86 -0
  76. package/src/themes/ui-components/_label.scss +19 -0
  77. package/src/themes/ui-components/_loader.scss +9 -0
  78. package/src/themes/ui-components/_margins-paddings.scss +45 -0
  79. package/src/themes/ui-components/_popover.scss +16 -0
  80. package/src/themes/ui-components/_positioning.scss +73 -0
  81. package/src/themes/ui-components/_rounded.scss +19 -0
  82. package/src/themes/ui-components/_scroll-area.scss +11 -0
  83. package/src/themes/ui-components/_select.scss +110 -0
  84. package/src/themes/ui-components/_separator.scss +19 -0
  85. package/src/themes/ui-components/_shadow.scss +15 -0
  86. package/src/themes/ui-components/_tabs.scss +46 -0
  87. package/src/themes/ui-components/_text-utilities.scss +48 -0
  88. package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
  89. package/src/themes/ui-components/_toggle.scss +80 -0
  90. package/src/themes/ui-components/_typography.scss +22 -0
  91. package/src/themes/ui-components copy.scss +1335 -0
  92. package/src/themes/ui-components.scss +27 -937
  93. package/src/transformers/markdown-list-transformer.ts +51 -51
  94. package/src/ui/button.tsx +11 -2
  95. package/src/ui/collapsible.tsx +1 -1
  96. package/src/ui/dialog.tsx +2 -2
  97. package/src/ui/flex.tsx +4 -4
  98. package/src/ui/popover.tsx +1 -1
  99. package/src/ui/tooltip.tsx +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thangph2146/lexical-editor",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.cjs",
@@ -48,8 +48,8 @@
48
48
  "sass": "^1.83.0",
49
49
  "tsup": "^8.4.0",
50
50
  "typescript": "^5.9.3",
51
- "@workspace/typescript-config": "0.0.0",
52
- "@workspace/eslint-config": "0.0.0"
51
+ "@workspace/eslint-config": "0.0.0",
52
+ "@workspace/typescript-config": "0.0.0"
53
53
  },
54
54
  "exports": {
55
55
  ".": {
@@ -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
- 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
+ }: 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
+ }
@@ -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
- if (!config) {
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 (