@wakastellar/ui 3.3.3 → 3.5.0

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 (140) hide show
  1. package/dist/badge-BbwO7QeZ.js +1 -0
  2. package/dist/badge-BfiocODp.mjs +23 -0
  3. package/dist/charts.cjs.js +1 -1
  4. package/dist/charts.es.js +1 -1
  5. package/dist/chunk-14q5BKub.js +1 -0
  6. package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
  7. package/dist/cn-DEtaFQsA.js +1 -0
  8. package/dist/cn-DUn6aSIQ.mjs +24 -0
  9. package/dist/doc.cjs.js +2 -2
  10. package/dist/doc.es.js +19 -19
  11. package/dist/editor.cjs.js +48 -0
  12. package/dist/editor.d.ts +1 -0
  13. package/dist/editor.es.js +6551 -0
  14. package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
  15. package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
  16. package/dist/export.cjs.js +1 -1
  17. package/dist/export.es.js +1 -1
  18. package/dist/index.cjs.js +150 -150
  19. package/dist/index.es.js +26782 -27591
  20. package/dist/input-BfaSAGVw.js +1 -0
  21. package/dist/input-DVr_Qkl8.mjs +14 -0
  22. package/dist/rich-text.cjs.js +1 -1
  23. package/dist/rich-text.es.js +1 -1
  24. package/dist/security-CyBpuklN.mjs +122 -0
  25. package/dist/security-bFWwDrlg.js +1 -0
  26. package/dist/separator-NrkltulH.js +1 -0
  27. package/dist/separator-ibN2mycs.mjs +51 -0
  28. package/dist/src/components/editor/blocks/index.d.ts +51 -0
  29. package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
  30. package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
  31. package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
  32. package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
  33. package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
  34. package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
  35. package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
  36. package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
  37. package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
  38. package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
  39. package/dist/src/components/editor/index.d.ts +66 -0
  40. package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
  41. package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
  42. package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
  43. package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
  44. package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
  45. package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
  46. package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
  47. package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
  48. package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
  49. package/dist/src/components/editor/waka-editor.d.ts +59 -0
  50. package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
  51. package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
  52. package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
  53. package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
  54. package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
  55. package/dist/src/components/index.d.ts +1 -15
  56. package/dist/src/editor.d.ts +26 -0
  57. package/dist/textarea-CdQWggYG.js +1 -0
  58. package/dist/textarea-DJDXJ3nd.mjs +23 -0
  59. package/dist/types-C2St0wOW.js +1 -0
  60. package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
  61. package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
  62. package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
  63. package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
  64. package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
  65. package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
  66. package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
  67. package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
  68. package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
  69. package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
  70. package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
  71. package/package.json +87 -2
  72. package/src/blocks/footer/index.tsx +1 -6
  73. package/src/blocks/login/index.tsx +1 -7
  74. package/src/blocks/profile/index.tsx +3 -5
  75. package/src/components/editor/blocks/index.ts +182 -0
  76. package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
  77. package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
  78. package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
  79. package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
  80. package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
  81. package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
  82. package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
  83. package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
  84. package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
  85. package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
  86. package/src/components/editor/index.ts +279 -0
  87. package/src/components/editor/waka-ai-writer.tsx +434 -0
  88. package/src/components/editor/waka-collaborative-editor.tsx +426 -0
  89. package/src/components/editor/waka-diff-viewer.tsx +352 -0
  90. package/src/components/editor/waka-dnd-editor.tsx +284 -0
  91. package/src/components/editor/waka-document-editor.tsx +502 -0
  92. package/src/components/editor/waka-editor-elements.tsx +312 -0
  93. package/src/components/editor/waka-editor-leaves.tsx +101 -0
  94. package/src/components/editor/waka-editor-plugins.ts +207 -0
  95. package/src/components/editor/waka-editor-toolbar.tsx +358 -0
  96. package/src/components/editor/waka-editor.tsx +431 -0
  97. package/src/components/editor/waka-floating-toolbar.tsx +268 -0
  98. package/src/components/editor/waka-markdown-editor.tsx +395 -0
  99. package/src/components/editor/waka-mention-editor.tsx +459 -0
  100. package/src/components/editor/waka-slash-menu.tsx +392 -0
  101. package/src/components/editor/waka-spec-editor.tsx +657 -0
  102. package/src/components/index.ts +1 -18
  103. package/dist/chunk-BDDJmn7V.js +0 -1
  104. package/dist/cn-DnPbmOCy.js +0 -1
  105. package/dist/cn-DpLcAzrf.mjs +0 -22
  106. package/dist/separator-BDReXBvI.mjs +0 -59
  107. package/dist/separator-BKjNl9sI.js +0 -1
  108. package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
  109. package/dist/src/components/waka-actors-list/index.d.ts +0 -18
  110. package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
  111. package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
  112. package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
  113. package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
  114. package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
  115. package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
  116. package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
  117. package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
  118. package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
  119. package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
  120. package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
  121. package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
  122. package/dist/types-BH9cQRqZ.js +0 -1
  123. package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
  124. package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
  125. package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
  126. package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
  127. package/src/components/waka-actor-badge/index.tsx +0 -34
  128. package/src/components/waka-actors-list/index.tsx +0 -125
  129. package/src/components/waka-ai-assistant-button/index.tsx +0 -31
  130. package/src/components/waka-document-flyover/index.tsx +0 -36
  131. package/src/components/waka-document-preview-popup/index.tsx +0 -103
  132. package/src/components/waka-hour-balance-badge/index.tsx +0 -43
  133. package/src/components/waka-hour-consumption-table/index.tsx +0 -72
  134. package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
  135. package/src/components/waka-project-stats-header/index.tsx +0 -69
  136. package/src/components/waka-step-comment-bubble/index.tsx +0 -71
  137. package/src/components/waka-step-comment-panel/index.tsx +0 -106
  138. package/src/components/waka-step-permission-matrix/index.tsx +0 -65
  139. package/src/components/waka-time-entry-dialog/index.tsx +0 -131
  140. package/src/components/waka-time-tracking-flyover/index.tsx +0 -41
@@ -0,0 +1,395 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Label } from "../label"
6
+ import { Textarea } from "../textarea"
7
+ import { Button } from "../button"
8
+
9
+ // ─── Types ───────────────────────────────────────────────────────────────────
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ type SlateNode = any
13
+
14
+ export type MarkdownEditorMode = "rich" | "markdown" | "split"
15
+
16
+ export interface WakaMarkdownEditorProps {
17
+ /** Markdown string content */
18
+ markdownValue?: string
19
+ /** Callback when markdown content changes */
20
+ onMarkdownChange?: (markdown: string) => void
21
+ /** Slate value (for rich text mode) */
22
+ slateValue?: SlateNode[]
23
+ /** Callback when slate value changes */
24
+ onSlateChange?: (value: SlateNode[]) => void
25
+ /** Initial view mode */
26
+ defaultMode?: MarkdownEditorMode
27
+ /** Read-only mode */
28
+ readOnly?: boolean
29
+ /** Placeholder */
30
+ placeholder?: string
31
+ /** CSS class */
32
+ className?: string
33
+ /** Editor area CSS class */
34
+ editorClassName?: string
35
+ /** Minimum height in px */
36
+ minHeight?: number
37
+ /** Label */
38
+ label?: string
39
+ /** Description */
40
+ description?: string
41
+ /** Error */
42
+ error?: string
43
+ /** Extra Plate plugins */
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ extraPlugins?: any[]
46
+ /** Ref to editor instance */
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ editorRef?: React.MutableRefObject<any>
49
+ }
50
+
51
+ // ─── Plate bundle ────────────────────────────────────────────────────────────
52
+
53
+ interface PlateBundle {
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ Plate: React.ComponentType<any>
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ PlateContent: React.ComponentType<any>
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ usePlateEditor: (config: any) => any
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ plugins: any[]
62
+ /** Serializes Slate nodes to Markdown */
63
+ serializeMd: (editor: unknown, opts?: { value?: SlateNode[] }) => string
64
+ /** Deserializes Markdown to Slate nodes */
65
+ deserializeMd: (editor: unknown, data: string) => SlateNode[]
66
+ }
67
+
68
+ // ─── Component ───────────────────────────────────────────────────────────────
69
+
70
+ /**
71
+ * WakaMarkdownEditor -- Dual-mode rich text / Markdown editor.
72
+ *
73
+ * Transparently syncs content between Plate (rich text) and raw Markdown.
74
+ * Supports three view modes:
75
+ * - `rich`: WYSIWYG Plate editor (default)
76
+ * - `markdown`: Raw Markdown textarea with monospace font
77
+ * - `split`: Side-by-side Plate + Markdown preview
78
+ *
79
+ * Uses `@platejs/markdown` for serialization/deserialization.
80
+ *
81
+ * Perfect for developer documentation, README editors,
82
+ * or any context where users need to switch between rich text and Markdown.
83
+ *
84
+ * @example
85
+ * ```tsx
86
+ * <WakaMarkdownEditor
87
+ * markdownValue={md}
88
+ * onMarkdownChange={setMd}
89
+ * defaultMode="split"
90
+ * />
91
+ * ```
92
+ */
93
+ export const WakaMarkdownEditor = React.forwardRef<HTMLDivElement, WakaMarkdownEditorProps>(
94
+ (
95
+ {
96
+ markdownValue = "",
97
+ onMarkdownChange,
98
+ slateValue,
99
+ onSlateChange,
100
+ defaultMode = "rich",
101
+ readOnly = false,
102
+ placeholder = "Commencez a ecrire...",
103
+ className,
104
+ editorClassName,
105
+ minHeight = 300,
106
+ label,
107
+ description,
108
+ error,
109
+ extraPlugins,
110
+ editorRef,
111
+ },
112
+ ref
113
+ ) => {
114
+ const [mode, setMode] = React.useState<MarkdownEditorMode>(defaultMode)
115
+ const [bundle, setBundle] = React.useState<PlateBundle | null>(null)
116
+ const [loadError, setLoadError] = React.useState(false)
117
+ const [localMd, setLocalMd] = React.useState(markdownValue)
118
+
119
+ // Sync external markdown changes
120
+ React.useEffect(() => { setLocalMd(markdownValue) }, [markdownValue])
121
+
122
+ // ── Load Plate ────────────────────────────────────────────────────────
123
+ React.useEffect(() => {
124
+ let cancelled = false
125
+
126
+ const load = async () => {
127
+ try {
128
+ const [plateReact, basicNodes, tableModule, layoutModule, calloutModule, linkModule, markdownModule] =
129
+ await Promise.all([
130
+ import("platejs/react"),
131
+ import("@platejs/basic-nodes/react"),
132
+ import("@platejs/table/react"),
133
+ import("@platejs/layout/react"),
134
+ import("@platejs/callout/react"),
135
+ import("@platejs/link/react"),
136
+ import("@platejs/markdown"),
137
+ ])
138
+
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ const plugins: any[] = [
141
+ basicNodes.BoldPlugin, basicNodes.ItalicPlugin, basicNodes.UnderlinePlugin,
142
+ basicNodes.StrikethroughPlugin, basicNodes.CodePlugin, basicNodes.HighlightPlugin,
143
+ basicNodes.H1Plugin, basicNodes.H2Plugin, basicNodes.H3Plugin,
144
+ basicNodes.H4Plugin, basicNodes.BlockquotePlugin,
145
+ tableModule.TablePlugin, tableModule.TableRowPlugin,
146
+ tableModule.TableCellPlugin, tableModule.TableCellHeaderPlugin,
147
+ layoutModule.ColumnPlugin, layoutModule.ColumnItemPlugin,
148
+ calloutModule.CalloutPlugin,
149
+ linkModule.LinkPlugin.configure({
150
+ options: { allowedSchemes: ["http", "https", "mailto"] },
151
+ }),
152
+ markdownModule.MarkdownPlugin,
153
+ ]
154
+
155
+ if (!cancelled) {
156
+ setBundle({
157
+ Plate: plateReact.Plate,
158
+ PlateContent: plateReact.PlateContent,
159
+ usePlateEditor: plateReact.usePlateEditor,
160
+ plugins,
161
+ serializeMd: markdownModule.serializeMd,
162
+ deserializeMd: markdownModule.deserializeMd,
163
+ })
164
+ }
165
+ } catch (err) {
166
+ console.error("[WakaMarkdownEditor] Failed to load:", err)
167
+ if (!cancelled) setLoadError(true)
168
+ }
169
+ }
170
+
171
+ load()
172
+ return () => { cancelled = true }
173
+ }, [])
174
+
175
+ // ── Mode switcher ─────────────────────────────────────────────────────
176
+ const ModeSwitcher = (
177
+ <div className="flex items-center gap-1 p-1 border-b bg-muted/30">
178
+ {(["rich", "markdown", "split"] as MarkdownEditorMode[]).map((m) => (
179
+ <Button
180
+ key={m}
181
+ type="button"
182
+ variant={mode === m ? "secondary" : "ghost"}
183
+ size="sm"
184
+ className="h-7 text-xs"
185
+ onClick={() => setMode(m)}
186
+ >
187
+ {m === "rich" ? "Editeur" : m === "markdown" ? "Markdown" : "Split"}
188
+ </Button>
189
+ ))}
190
+ </div>
191
+ )
192
+
193
+ // ── Handle markdown textarea changes ──────────────────────────────────
194
+ const handleMdChange = React.useCallback(
195
+ (md: string) => {
196
+ setLocalMd(md)
197
+ onMarkdownChange?.(md)
198
+ },
199
+ [onMarkdownChange]
200
+ )
201
+
202
+ // ── Render ────────────────────────────────────────────────────────────
203
+ return (
204
+ <div ref={ref} className={cn("space-y-1.5", className)}>
205
+ {label && <Label>{label}</Label>}
206
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
207
+
208
+ <div className={cn("border rounded-lg overflow-hidden", error && "border-destructive")}>
209
+ {!readOnly && ModeSwitcher}
210
+
211
+ {mode === "markdown" && (
212
+ <MarkdownPane
213
+ value={localMd}
214
+ onChange={handleMdChange}
215
+ readOnly={readOnly}
216
+ placeholder={placeholder}
217
+ minHeight={minHeight}
218
+ className={editorClassName}
219
+ />
220
+ )}
221
+
222
+ {mode === "rich" && bundle && (
223
+ <RichPane
224
+ bundle={bundle}
225
+ markdownValue={localMd}
226
+ slateValue={slateValue}
227
+ onSlateChange={onSlateChange}
228
+ onMarkdownChange={onMarkdownChange}
229
+ readOnly={readOnly}
230
+ placeholder={placeholder}
231
+ minHeight={minHeight}
232
+ className={editorClassName}
233
+ extraPlugins={extraPlugins}
234
+ editorRef={editorRef}
235
+ />
236
+ )}
237
+
238
+ {mode === "split" && bundle && (
239
+ <div className="flex divide-x" style={{ minHeight }}>
240
+ <div className="flex-1 min-w-0">
241
+ <RichPane
242
+ bundle={bundle}
243
+ markdownValue={localMd}
244
+ slateValue={slateValue}
245
+ onSlateChange={onSlateChange}
246
+ onMarkdownChange={onMarkdownChange}
247
+ readOnly={readOnly}
248
+ placeholder={placeholder}
249
+ minHeight={minHeight}
250
+ className={editorClassName}
251
+ extraPlugins={extraPlugins}
252
+ editorRef={editorRef}
253
+ />
254
+ </div>
255
+ <div className="flex-1 min-w-0">
256
+ <MarkdownPane
257
+ value={localMd}
258
+ onChange={handleMdChange}
259
+ readOnly={readOnly}
260
+ placeholder="Markdown..."
261
+ minHeight={minHeight}
262
+ />
263
+ </div>
264
+ </div>
265
+ )}
266
+
267
+ {/* Fallback while loading */}
268
+ {!bundle && mode !== "markdown" && (
269
+ <div className="relative" style={{ minHeight }}>
270
+ <div className="absolute inset-0 flex items-center justify-center bg-background/50">
271
+ <span className="text-sm text-muted-foreground animate-pulse">
272
+ {loadError ? "Erreur de chargement" : "Chargement..."}
273
+ </span>
274
+ </div>
275
+ </div>
276
+ )}
277
+ </div>
278
+
279
+ {error && <p className="text-sm text-destructive">{error}</p>}
280
+ </div>
281
+ )
282
+ }
283
+ )
284
+
285
+ WakaMarkdownEditor.displayName = "WakaMarkdownEditor"
286
+
287
+ // ─── Markdown pane ──────────────────────────────────────────────────────────
288
+
289
+ function MarkdownPane({
290
+ value,
291
+ onChange,
292
+ readOnly,
293
+ placeholder,
294
+ minHeight,
295
+ className,
296
+ }: {
297
+ value: string
298
+ onChange: (v: string) => void
299
+ readOnly?: boolean
300
+ placeholder?: string
301
+ minHeight?: number
302
+ className?: string
303
+ }) {
304
+ return (
305
+ <Textarea
306
+ value={value}
307
+ onChange={(e) => onChange(e.target.value)}
308
+ readOnly={readOnly}
309
+ placeholder={placeholder}
310
+ style={{ minHeight }}
311
+ className={cn(
312
+ "rounded-none border-0 resize-none font-mono text-sm",
313
+ "focus-visible:ring-0 focus-visible:ring-offset-0",
314
+ className,
315
+ )}
316
+ />
317
+ )
318
+ }
319
+
320
+ // ─── Rich pane ──────────────────────────────────────────────────────────────
321
+
322
+ function RichPane({
323
+ bundle: { Plate, PlateContent, usePlateEditor, plugins, serializeMd },
324
+ markdownValue,
325
+ slateValue,
326
+ onSlateChange,
327
+ onMarkdownChange,
328
+ readOnly,
329
+ placeholder,
330
+ minHeight,
331
+ className,
332
+ extraPlugins,
333
+ editorRef,
334
+ }: {
335
+ bundle: PlateBundle
336
+ markdownValue: string
337
+ slateValue?: SlateNode[]
338
+ onSlateChange?: (v: SlateNode[]) => void
339
+ onMarkdownChange?: (md: string) => void
340
+ readOnly?: boolean
341
+ placeholder?: string
342
+ minHeight?: number
343
+ className?: string
344
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
345
+ extraPlugins?: any[]
346
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
+ editorRef?: React.MutableRefObject<any>
348
+ }) {
349
+ const allPlugins = React.useMemo(
350
+ () => [...plugins, ...(extraPlugins ?? [])],
351
+ [plugins, extraPlugins]
352
+ )
353
+
354
+ const editor = usePlateEditor({
355
+ plugins: allPlugins,
356
+ value: slateValue ?? [{ type: "p", children: [{ text: markdownValue || "" }] }],
357
+ })
358
+
359
+ React.useEffect(() => {
360
+ if (editorRef) editorRef.current = editor
361
+ }, [editor, editorRef])
362
+
363
+ const handleChange = React.useCallback(
364
+ ({ value: v }: { value: SlateNode[] }) => {
365
+ onSlateChange?.(v)
366
+ // Sync to Markdown
367
+ if (onMarkdownChange && serializeMd) {
368
+ try {
369
+ const md = serializeMd(editor, { value: v })
370
+ onMarkdownChange(md)
371
+ } catch {
372
+ // Serialization may fail during intermediate states
373
+ }
374
+ }
375
+ },
376
+ [editor, onSlateChange, onMarkdownChange, serializeMd]
377
+ )
378
+
379
+ return (
380
+ <Plate editor={editor} onChange={handleChange} readOnly={readOnly}>
381
+ <div style={{ minHeight }}>
382
+ <PlateContent
383
+ placeholder={placeholder}
384
+ readOnly={readOnly}
385
+ className={cn(
386
+ "prose prose-sm max-w-none p-4",
387
+ "focus-within:outline-none",
388
+ "[&_[data-slate-editor]]:outline-none [&_[data-slate-editor]]:min-h-[inherit]",
389
+ className,
390
+ )}
391
+ />
392
+ </div>
393
+ </Plate>
394
+ )
395
+ }