@editora/react 1.0.1

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/README.md ADDED
@@ -0,0 +1,496 @@
1
+ # @editora/react
2
+
3
+ React components for Editora Rich Text Editor - A modern, extensible WYSIWYG editor.
4
+
5
+ ## 📦 Installation
6
+
7
+ ```bash
8
+ npm install @editora/react @editora/core @editora/plugins @editora/themes
9
+ ```
10
+
11
+ ## 🎯 Overview
12
+
13
+ The React package provides ready-to-use React components for building rich text editing experiences. It includes hooks, components, and utilities specifically designed for React applications.
14
+
15
+ ## ✨ Features
16
+
17
+ - **React Hooks**: Modern hooks-based API (`useEditor`, `useEditorState`)
18
+ - **TypeScript**: Full type safety and IntelliSense support
19
+ - **SSR Compatible**: Works with Next.js and server-side rendering
20
+ - **Tree Shakeable**: Optimized bundle sizes
21
+ - **Accessible**: WCAG 2.1 compliant
22
+ - **Multi-Instance**: Support for multiple editors on one page
23
+
24
+ ## 🚀 Quick Start
25
+
26
+ ### Basic Editor
27
+
28
+ ```tsx
29
+ import { EditoraEditor } from '@editora/react';
30
+ import { BoldPlugin, ItalicPlugin } from '@editora/plugins';
31
+ import '@editora/themes/styles';
32
+
33
+ function App() {
34
+ const [content, setContent] = useState('<p>Start writing...</p>');
35
+
36
+ return (
37
+ <EditoraEditor
38
+ value={content}
39
+ onChange={setContent}
40
+ plugins={[
41
+ BoldPlugin(),
42
+ ItalicPlugin()
43
+ ]}
44
+ placeholder="Type something..."
45
+ />
46
+ );
47
+ }
48
+ ```
49
+
50
+ ### Full-Featured Editor
51
+
52
+ ```tsx
53
+ import { EditoraEditor } from '@editora/react';
54
+ import {
55
+ BoldPlugin,
56
+ ItalicPlugin,
57
+ UnderlinePlugin,
58
+ HeadingPlugin,
59
+ ParagraphPlugin,
60
+ ListPlugin,
61
+ LinkPlugin,
62
+ ImagePlugin,
63
+ TablePlugin,
64
+ CodeSamplePlugin,
65
+ HistoryPlugin
66
+ } from '@editora/plugins';
67
+ import '@editora/themes/styles';
68
+
69
+ function FullEditor() {
70
+ const [content, setContent] = useState('');
71
+
72
+ const plugins = [
73
+ BoldPlugin(),
74
+ ItalicPlugin(),
75
+ UnderlinePlugin(),
76
+ HeadingPlugin(),
77
+ ListPlugin(),
78
+ LinkPlugin({
79
+ onLinkClick: (url) => window.open(url, '_blank')
80
+ }),
81
+ createImagePlugin({
82
+ upload: async (file) => {
83
+ const formData = new FormData();
84
+ formData.append('image', file);
85
+ const response = await fetch('/api/upload', {
86
+ method: 'POST',
87
+ body: formData
88
+ });
89
+ const data = await response.json();
90
+ return data.url;
91
+ }
92
+ }),
93
+ createTablePlugin(),
94
+ createCodeSamplePlugin(),
95
+ createHistoryPlugin()
96
+ ];
97
+
98
+ return (
99
+ <div className="editor-container">
100
+ <EditoraEditor
101
+ value={content}
102
+ onChange={setContent}
103
+ plugins={plugins}
104
+ placeholder="Start writing your document..."
105
+ autofocus
106
+ />
107
+ </div>
108
+ );
109
+ }
110
+ ```
111
+
112
+ ### With Custom Toolbar
113
+
114
+ ```tsx
115
+ import { EditoraEditor, Toolbar, ToolbarButton } from '@editora/react';
116
+ import { useEditor } from '@editora/react/hooks';
117
+
118
+ function EditorWithCustomToolbar() {
119
+ const { editor, html, setHtml } = useEditor({
120
+ plugins: [/* your plugins */],
121
+ content: '<p>Hello</p>'
122
+ });
123
+
124
+ return (
125
+ <div>
126
+ <Toolbar editor={editor}>
127
+ <ToolbarButton command="bold" icon="bold" />
128
+ <ToolbarButton command="italic" icon="italic" />
129
+ <ToolbarButton command="underline" icon="underline" />
130
+ <div className="separator" />
131
+ <ToolbarButton command="heading" level={1} icon="h1" />
132
+ <ToolbarButton command="heading" level={2} icon="h2" />
133
+ </Toolbar>
134
+
135
+ <div
136
+ ref={(el) => el && editor.mount(el)}
137
+ className="editor-content"
138
+ />
139
+ </div>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ## 📖 API Reference
145
+
146
+ ### Components
147
+
148
+ #### `<EditoraEditor />`
149
+
150
+ Main editor component with built-in toolbar.
151
+
152
+ **Props:**
153
+
154
+ ```typescript
155
+ interface EditoraEditorProps {
156
+ // Content
157
+ value?: string;
158
+ defaultValue?: string;
159
+ onChange?: (html: string) => void;
160
+
161
+ // Plugins
162
+ plugins?: Plugin[];
163
+
164
+ // Configuration
165
+ placeholder?: string;
166
+ readonly?: boolean;
167
+ autofocus?: boolean;
168
+ maxLength?: number;
169
+
170
+ // Styling
171
+ className?: string;
172
+ style?: React.CSSProperties;
173
+ theme?: 'light' | 'dark' | 'auto';
174
+
175
+ // Toolbar
176
+ showToolbar?: boolean;
177
+ toolbarItems?: ToolbarItem[];
178
+ toolbarPosition?: 'top' | 'bottom' | 'floating';
179
+
180
+ // Events
181
+ onFocus?: () => void;
182
+ onBlur?: () => void;
183
+ onReady?: (editor: Editor) => void;
184
+ }
185
+ ```
186
+
187
+ #### `<Toolbar />`
188
+
189
+ Customizable toolbar component.
190
+
191
+ **Props:**
192
+
193
+ ```typescript
194
+ interface ToolbarProps {
195
+ editor: Editor;
196
+ items?: ToolbarItem[];
197
+ position?: 'top' | 'bottom' | 'floating';
198
+ className?: string;
199
+ }
200
+ ```
201
+
202
+ #### `<ToolbarButton />`
203
+
204
+ Individual toolbar button.
205
+
206
+ **Props:**
207
+
208
+ ```typescript
209
+ interface ToolbarButtonProps {
210
+ command: string;
211
+ icon?: React.ReactNode;
212
+ label?: string;
213
+ active?: boolean;
214
+ disabled?: boolean;
215
+ onClick?: () => void;
216
+ }
217
+ ```
218
+
219
+ ### Hooks
220
+
221
+ #### `useEditor(config)`
222
+
223
+ Main hook for editor management.
224
+
225
+ ```typescript
226
+ const { editor, html, json, setHtml, setJson } = useEditor({
227
+ plugins: [...],
228
+ content: '<p>Initial content</p>',
229
+ onChange: (html) => console.log(html)
230
+ });
231
+ ```
232
+
233
+ **Returns:**
234
+ - `editor`: Editor instance
235
+ - `html`: Current HTML content
236
+ - `json`: Current JSON content
237
+ - `setHtml`: Function to set HTML content
238
+ - `setJson`: Function to set JSON content
239
+
240
+ #### `useEditorState(editor)`
241
+
242
+ Hook for accessing editor state.
243
+
244
+ ```typescript
245
+ const {
246
+ isFocused,
247
+ isEmpty,
248
+ canUndo,
249
+ canRedo
250
+ } = useEditorState(editor);
251
+ ```
252
+
253
+ #### `useEditorCommands(editor)`
254
+
255
+ Hook for editor commands.
256
+
257
+ ```typescript
258
+ const {
259
+ bold,
260
+ italic,
261
+ undo,
262
+ redo,
263
+ insertText,
264
+ insertImage
265
+ } = useEditorCommands(editor);
266
+ ```
267
+
268
+ ## 🎨 Theming
269
+
270
+ ### Using Built-in Themes
271
+
272
+ ```tsx
273
+ import '@editora/themes/styles';
274
+
275
+ <EditoraEditor theme="dark" />
276
+ ```
277
+
278
+ ### Custom Theme
279
+
280
+ ```css
281
+ :root {
282
+ --editora-bg: #ffffff;
283
+ --editora-text: #000000;
284
+ --editora-border: #cccccc;
285
+ --editora-primary: #0066cc;
286
+ --editora-toolbar-bg: #f5f5f5;
287
+ }
288
+
289
+ [data-theme="dark"] {
290
+ --editora-bg: #1e1e1e;
291
+ --editora-text: #ffffff;
292
+ --editora-border: #444444;
293
+ --editora-primary: #3399ff;
294
+ --editora-toolbar-bg: #2d2d2d;
295
+ }
296
+ ```
297
+
298
+ ## 🔌 Plugin Configuration
299
+
300
+ ### Bold Plugin
301
+
302
+ ```tsx
303
+ import { BoldPlugin } from '@editora/plugins';
304
+
305
+ const boldPlugin = BoldPlugin({
306
+ keyboard: 'Mod-b',
307
+ icon: <BoldIcon />
308
+ });
309
+ ```
310
+
311
+ ### Image Plugin with Upload
312
+
313
+ ```tsx
314
+ import { createImagePlugin } from '@editora/plugins';
315
+
316
+ const imagePlugin = createImagePlugin({
317
+ upload: async (file) => {
318
+ const url = await uploadToServer(file);
319
+ return url;
320
+ },
321
+ validate: (file) => {
322
+ return file.size < 5 * 1024 * 1024; // 5MB limit
323
+ },
324
+ resize: true,
325
+ maxWidth: 1200
326
+ });
327
+ ```
328
+
329
+ ### Link Plugin
330
+
331
+ ```tsx
332
+ import { createLinkPlugin } from '@editora/plugins';
333
+
334
+ const linkPlugin = createLinkPlugin({
335
+ openOnClick: false,
336
+ validate: (url) => {
337
+ return url.startsWith('http') || url.startsWith('https');
338
+ },
339
+ onLinkClick: (url) => {
340
+ window.open(url, '_blank', 'noopener,noreferrer');
341
+ }
342
+ });
343
+ ```
344
+
345
+ ## 💡 Examples
346
+
347
+ ### Form Integration
348
+
349
+ ```tsx
350
+ function BlogPostForm() {
351
+ const [formData, setFormData] = useState({
352
+ title: '',
353
+ content: ''
354
+ });
355
+
356
+ const handleSubmit = async (e) => {
357
+ e.preventDefault();
358
+ await fetch('/api/posts', {
359
+ method: 'POST',
360
+ body: JSON.stringify(formData)
361
+ });
362
+ };
363
+
364
+ return (
365
+ <form onSubmit={handleSubmit}>
366
+ <input
367
+ type="text"
368
+ value={formData.title}
369
+ onChange={(e) => setFormData({ ...formData, title: e.target.value })}
370
+ placeholder="Post title"
371
+ />
372
+
373
+ <EditoraEditor
374
+ value={formData.content}
375
+ onChange={(content) => setFormData({ ...formData, content })}
376
+ plugins={[/* ... */]}
377
+ />
378
+
379
+ <button type="submit">Publish</button>
380
+ </form>
381
+ );
382
+ }
383
+ ```
384
+
385
+ ### Controlled Editor with Save
386
+
387
+ ```tsx
388
+ function DocumentEditor() {
389
+ const [content, setContent] = useState('');
390
+ const [isSaving, setIsSaving] = useState(false);
391
+
392
+ const handleSave = async () => {
393
+ setIsSaving(true);
394
+ try {
395
+ await fetch('/api/save', {
396
+ method: 'POST',
397
+ body: JSON.stringify({ content })
398
+ });
399
+ alert('Saved successfully!');
400
+ } catch (error) {
401
+ alert('Failed to save');
402
+ } finally {
403
+ setIsSaving(false);
404
+ }
405
+ };
406
+
407
+ return (
408
+ <div>
409
+ <div className="toolbar-actions">
410
+ <button onClick={handleSave} disabled={isSaving}>
411
+ {isSaving ? 'Saving...' : 'Save'}
412
+ </button>
413
+ </div>
414
+
415
+ <EditoraEditor
416
+ value={content}
417
+ onChange={setContent}
418
+ plugins={[/* ... */]}
419
+ />
420
+ </div>
421
+ );
422
+ }
423
+ ```
424
+
425
+ ### Read-Only Mode
426
+
427
+ ```tsx
428
+ function ArticlePreview({ html }) {
429
+ return (
430
+ <EditoraEditor
431
+ value={html}
432
+ readonly
433
+ showToolbar={false}
434
+ className="article-preview"
435
+ />
436
+ );
437
+ }
438
+ ```
439
+
440
+ ## 🔧 TypeScript Support
441
+
442
+ The package is written in TypeScript and includes comprehensive type definitions.
443
+
444
+ ```typescript
445
+ import type {
446
+ Editor,
447
+ Plugin,
448
+ ToolbarItem,
449
+ EditorConfig
450
+ } from '@editora/react';
451
+
452
+ const config: EditorConfig = {
453
+ plugins: [],
454
+ onChange: (html: string) => {
455
+ // TypeScript knows html is a string
456
+ }
457
+ };
458
+ ```
459
+
460
+ ## 📱 Responsive Design
461
+
462
+ The editor automatically adapts to different screen sizes:
463
+
464
+ ```tsx
465
+ <EditoraEditor
466
+ // Toolbar collapses to hamburger menu on mobile
467
+ toolbarBreakpoint={768}
468
+
469
+ // Custom mobile configuration
470
+ mobileConfig={{
471
+ toolbarPosition: 'bottom',
472
+ compactMode: true
473
+ }}
474
+ />
475
+ ```
476
+
477
+ ## ♿ Accessibility
478
+
479
+ The editor is fully accessible and follows WCAG 2.1 guidelines:
480
+
481
+ - Keyboard navigation support
482
+ - Screen reader announcements
483
+ - ARIA attributes
484
+ - Focus management
485
+ - High contrast mode support
486
+
487
+ ## 📄 License
488
+
489
+ MIT © [Ajay Kumar](https://github.com/ajaykr089)
490
+
491
+ ## 🔗 Links
492
+
493
+ - [Documentation](https://github.com/ajaykr089/Editora#readme)
494
+ - [GitHub Repository](https://github.com/ajaykr089/Editora)
495
+ - [Issue Tracker](https://github.com/ajaykr089/Editora/issues)
496
+ - [npm Package](https://www.npmjs.com/package/@editora/react)
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),c=require("react"),I=require("@editora/core"),K=({isOpen:e,options:t,onSelect:u,onClose:s,anchorRef:i,className:a=""})=>{const g=c.useRef(null),[m,b]=c.useState({top:0,left:0});return c.useEffect(()=>{if(e&&i.current){const r=i.current.getBoundingClientRect(),d=120,w=t.length*36;let h=r.bottom+4,f=r.left;const p=window.innerWidth,x=window.innerHeight;f+d>p&&(f=p-d-8),h+w>x&&(h=r.top-w-4),b({top:h,left:f})}},[e,i,t.length]),c.useEffect(()=>{if(e&&i.current&&g.current){const r=i.current.getBoundingClientRect(),d=g.current.getBoundingClientRect();let w=r.bottom+4,h=r.left;const f=window.innerWidth,p=window.innerHeight;h+d.width>f&&(h=f-d.width-8),w+d.height>p&&(w=r.top-d.height-4),b({top:w,left:h})}},[e]),c.useEffect(()=>{const r=w=>{g.current&&!g.current.contains(w.target)&&i.current&&!i.current.contains(w.target)&&s()},d=w=>{w.key==="Escape"&&s()};return e&&(document.addEventListener("mousedown",r),document.addEventListener("keydown",d)),()=>{document.removeEventListener("mousedown",r),document.removeEventListener("keydown",d)}},[e,s,i]),e?o.jsx("div",{ref:g,className:`rte-inline-menu ${a}`,style:{position:"fixed",top:m.top,left:m.left,zIndex:1e3,background:"white",border:"1px solid #ccc",borderRadius:"4px",boxShadow:"0 2px 8px rgba(0, 0, 0, 0.15)",minWidth:"120px",maxWidth:"200px",pointerEvents:"auto"},children:t.map(r=>o.jsx("div",{className:"rte-inline-menu-item",onClick:()=>{u(r.value),s()},style:{padding:"8px 12px",cursor:"pointer",borderBottom:"1px solid #f0f0f0",fontSize:"14px",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},onMouseEnter:d=>{d.currentTarget.style.backgroundColor="#f5f5f5"},onMouseLeave:d=>{d.currentTarget.style.backgroundColor="transparent"},children:r.label},r.value))}):null},H=({editor:e,position:t="top",sticky:u=!1,floating:s=!1})=>{const[i,a]=c.useState(null),[g,m]=c.useState(null),[b,r]=c.useState(null),[d,w]=c.useState(null),[h,f]=c.useState(!1),p=c.useRef({}),x=c.useRef(null),y=c.useRef(null),C=c.useRef(null),v=e.pluginManager.getToolbarItems();c.useEffect(()=>{const n=()=>{if(!x.current||!y.current)return;const N=x.current.clientWidth,A=16,Q=40,F=4,Y=N-A-Q-F;let B=0,O=0;const U=y.current.children;for(let D=0;D<U.length;D++){const q=U[D].offsetWidth+F;if(B+q<=Y)B+=q,O++;else break}w(Math.max(1,O))},l=requestAnimationFrame(()=>{n()}),E=new ResizeObserver(()=>{n()});x.current&&E.observe(x.current);const k=new MutationObserver(()=>{n()});return y.current&&k.observe(y.current,{childList:!0,subtree:!0}),()=>{cancelAnimationFrame(l),E.disconnect(),k.disconnect()}},[v.length]);const T=n=>(p.current[n]||(p.current[n]=c.createRef()),p.current[n]),S=(n,l)=>{const E=x.current?.closest("[data-editora-editor]"),k=E?.querySelector(".rte-content");if(k&&k.focus(),b&&(n==="setTextAlignment"||n==="setFontFamily"||n==="setBlockType")){const A=window.getSelection();A&&(A.removeAllRanges(),A.addRange(b)),r(null)}typeof window<"u"&&window.executeEditorCommand&&window.executeEditorCommand(n,l),a(null);const N=E?.querySelector(".rte-content");N&&N.focus()},R=n=>{const l=window.getSelection();l&&l.rangeCount>0&&r(l.getRangeAt(0).cloneRange()),a(i===n?null:n)},j=n=>{const l=window.getSelection();l&&l.rangeCount>0&&r(l.getRangeAt(0).cloneRange()),m(g===n?null:n),a(null)},L=(n,l)=>{if(b){const E=window.getSelection();E&&(E.removeAllRanges(),E.addRange(b)),r(null)}S(n,l),m(null)},M=(n,l)=>n&&n.startsWith("<svg")&&n.endsWith("</svg>")?o.jsx("span",{dangerouslySetInnerHTML:{__html:n}}):n&&n.length===1&&/^[BIUSH]$/.test(n)?o.jsx("span",{style:{fontWeight:"bold",fontSize:"14px",lineHeight:"1"},children:n}):n||"⚪";if(s)return null;const J={...u&&{position:"sticky",top:0,zIndex:100,backgroundColor:"#fff",boxShadow:"0 2px 4px rgba(0,0,0,0.1)"},...t==="bottom"&&{order:2}},z=n=>n.map((l,E)=>o.jsx("div",{className:"rte-toolbar-item",style:{display:d!==null&&E>=d?"none":"flex"},children:l.type==="dropdown"?o.jsxs("div",{className:"rte-toolbar-dropdown",children:[o.jsxs("button",{className:"rte-toolbar-button","data-command":l.command,"data-active":"false",onClick:()=>R(l.command),children:[l.label," ▼"]}),i===l.command&&o.jsx("div",{className:"rte-toolbar-dropdown-menu",children:l.options?.map(k=>o.jsx("div",{className:"rte-toolbar-dropdown-item",onClick:()=>S(l.command,k.value),children:k.label},k.value))})]}):l.type==="inline-menu"?o.jsx("button",{ref:T(l.command),className:"rte-toolbar-button","data-command":l.command,"data-active":"false",onClick:()=>j(l.command),title:l.label,children:M(l.icon,l.command)}):l.type==="input"?o.jsx("input",{type:"text",className:`rte-toolbar-input ${l.label.toLowerCase().replace(/\s+/g,"-")}`,placeholder:l.placeholder,onChange:k=>S(l.command,k.target.value),onKeyDown:k=>{k.key==="Enter"&&S(l.command,k.target.value)},title:l.label}):l.type==="group"?o.jsx("div",{className:`rte-toolbar-group-button ${l.label.toLowerCase().replace(/\s+/g,"-")}`,title:`${l.label}`,children:o.jsx("div",{className:`rte-toolbar-group-items ${l.label.toLowerCase().replace(/\s+/g,"-")}`,children:z(l.items||[])})}):o.jsx("button",{className:"rte-toolbar-button","data-command":l.command,"data-active":"false",onClick:()=>S(l.command),title:l.label,children:M(l.icon,l.command)})},E));return o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"rte-toolbar-wrapper",style:J,children:[o.jsxs("div",{className:"rte-toolbar",ref:x,children:[o.jsx("div",{className:"rte-toolbar-items-container",ref:y,children:z(v)}),d!==null&&d<v.length&&o.jsx("button",{ref:C,className:`rte-toolbar-more-button ${h?"active":""}`,onClick:()=>f(!h),title:"Show more options","aria-label":"More toolbar options",children:"☰"})]}),d!==null&&d<v.length&&o.jsx("div",{className:`rte-toolbar-expanded-row ${h?"show":""}`,children:v.map((n,l)=>l>=(d||0)&&o.jsx("div",{className:"rte-toolbar-item",children:n.type==="dropdown"?o.jsxs("div",{className:"rte-toolbar-dropdown",children:[o.jsxs("button",{className:"rte-toolbar-button","data-command":n.command,"data-active":"false",onClick:()=>R(n.command),children:[n.label," ▼"]}),i===n.command&&o.jsx("div",{className:"rte-toolbar-dropdown-menu",children:n.options?.map(E=>o.jsx("div",{className:"rte-toolbar-dropdown-item",onClick:()=>S(n.command,E.value),children:E.label},E.value))})]}):n.type==="inline-menu"?o.jsx("button",{ref:T(n.command),className:"rte-toolbar-button","data-command":n.command,"data-active":"false",onClick:()=>j(n.command),title:n.label,children:M(n.icon,n.command)}):n.type==="input"?o.jsx("input",{type:"text",className:"rte-toolbar-input",placeholder:n.placeholder,onChange:E=>S(n.command,E.target.value),onKeyDown:E=>{E.key==="Enter"&&S(n.command,E.target.value)},title:n.label}):o.jsx("button",{className:"rte-toolbar-button","data-command":n.command,"data-active":"false",onClick:()=>S(n.command),title:n.label,children:M(n.icon,n.command)})},l))})]}),v.map(n=>n.type==="inline-menu"?o.jsx(K,{isOpen:g===n.command,options:n.options||[],onSelect:l=>L(n.command,l),onClose:()=>m(null),anchorRef:T(n.command)},`menu-${n.command}`):null)]})};function Z(e,t){const u=c.useRef(),s=c.useRef("");return c.useEffect(()=>{if(!t?.enabled)return;const a=t.intervalMs||3e4,g=t.storageKey||"rte-autosave",m=t.provider||"localStorage",b=async()=>{const r=e();if(r!==s.current){if(s.current=r,m==="localStorage")try{localStorage.setItem(g,r),localStorage.setItem(`${g}-timestamp`,Date.now().toString()),console.log("[Autosave] Content saved to localStorage")}catch(d){console.error("[Autosave] Failed to save to localStorage:",d)}else if(m==="api"&&t.apiUrl)try{await fetch(t.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:r,timestamp:Date.now()})}),console.log("[Autosave] Content saved to API")}catch(d){console.error("[Autosave] Failed to save to API:",d)}}};return u.current=setInterval(b,a),()=>{u.current&&clearInterval(u.current)}},[t?.enabled,t?.intervalMs,t?.storageKey,t?.provider,t?.apiUrl,e]),{restore:()=>{if(!t?.enabled)return null;const a=t.storageKey||"rte-autosave";if((t.provider||"localStorage")==="localStorage")try{const m=localStorage.getItem(a),b=localStorage.getItem(`${a}-timestamp`);if(m&&b)return console.log("[Autosave] Restored from localStorage, saved at:",new Date(parseInt(b))),m}catch(m){console.error("[Autosave] Failed to restore from localStorage:",m)}return null}}}const ee=["p","br","strong","em","u","s","b","i","h1","h2","h3","h4","h5","h6","ul","ol","li","a","img","video","audio","table","thead","tbody","tr","th","td","blockquote","pre","code","span","div","sup","sub","hr"],te={"*":["class","style","id","data-*"],a:["href","target","rel","title"],img:["src","alt","width","height","loading"],video:["src","controls","width","height","autoplay","loop","muted"],audio:["src","controls","autoplay","loop","muted"],table:["border","cellpadding","cellspacing"],td:["colspan","rowspan","align","valign"],th:["colspan","rowspan","align","valign"]};function P(e,t,u){if(t?.sanitize===!1)return e;const s=t?.allowedTags&&t.allowedTags.length>0?t.allowedTags:ee,i=t?.allowedAttributes||te,a=document.createElement("div");return a.innerHTML=e,$(a,s,i),a.innerHTML}function $(e,t,u){const s=Array.from(e.childNodes);for(const i of s)if(i.nodeType===Node.ELEMENT_NODE){const a=i,g=a.tagName.toLowerCase();if(!t.includes(g)){for(;a.firstChild;)e.insertBefore(a.firstChild,a);e.removeChild(a);continue}ne(a,u),$(a,t,u)}else{if(i.nodeType===Node.TEXT_NODE)continue;e.removeChild(i)}}function ne(e,t){const u=e.tagName.toLowerCase(),s=Array.from(e.attributes),i=t[u]||[],a=t["*"]||[],g=[...i,...a];for(const m of s){const b=m.name.toLowerCase();let r=!1;g.includes(b)&&(r=!0);for(const d of g)if(d.endsWith("*")){const w=d.slice(0,-1);if(b.startsWith(w)){r=!0;break}}(b.startsWith("on")||b==="javascript:"||b==="href"&&m.value.trim().toLowerCase().startsWith("javascript:")||b==="src"&&m.value.trim().toLowerCase().startsWith("javascript:"))&&(r=!1),r||e.removeAttribute(m.name)}e.hasAttribute("href")&&(e.getAttribute("href")||"").trim().toLowerCase().startsWith("javascript:")&&e.removeAttribute("href"),e.hasAttribute("src")&&(e.getAttribute("src")||"").trim().toLowerCase().startsWith("javascript:")&&e.removeAttribute("src")}function oe(e,t,u){return u?.sanitizeOnPaste===!1?e:P(e,t)}function re(e,t,u){return u?.sanitizeOnInput===!1?e:P(e,t)}const V=({editor:e,defaultValue:t,value:u,onChange:s,pasteConfig:i,contentConfig:a,securityConfig:g,performanceConfig:m,autosaveConfig:b})=>{const r=c.useRef(null),d=u!==void 0,w=c.useRef(),{restore:h}=Z(()=>r.current?.innerHTML||"",b);return c.useEffect(()=>{if(!r.current)return;const f=h(),p=f||u||t;p&&r.current.innerHTML!==p&&(r.current.innerHTML=p,f&&s&&s(f))},[]),c.useEffect(()=>{!r.current||!d||u!==r.current.innerHTML&&(r.current.innerHTML=u)},[u,d]),c.useEffect(()=>{if(!r.current)return;const f=()=>{if(!r.current||!s)return;let C=r.current.innerHTML;if(g?.sanitizeOnInput!==!1&&a?.sanitize!==!1&&(C=re(C,a,g),C!==r.current.innerHTML)){const v=window.getSelection(),T=v&&v.rangeCount>0?v.getRangeAt(0):null;if(r.current.innerHTML=C,T&&v)try{v.removeAllRanges(),v.addRange(T)}catch{}}m?.debounceInputMs?(w.current&&clearTimeout(w.current),w.current=setTimeout(()=>{s(C)},m.debounceInputMs)):s(C)},p=C=>{C.preventDefault();let v=C.clipboardData?.getData("text/html");const T=C.clipboardData?.getData("text/plain");if(i?.clean||!i?.keepFormatting){T&&document.execCommand("insertText",!1,T);return}if(v){g?.sanitizeOnPaste!==!1&&a?.sanitize!==!1&&(v=oe(v,a,g));const S=window.getSelection();if(S&&S.rangeCount>0){const R=S.getRangeAt(0);R.deleteContents();const j=document.createElement("div");j.innerHTML=v;const L=document.createDocumentFragment();for(;j.firstChild;)L.appendChild(j.firstChild);R.insertNode(L),R.collapse(!1),S.removeAllRanges(),S.addRange(R)}}else T&&document.execCommand("insertText",!1,T)},x=C=>{const v=C.target;(v.tagName==="IMG"||v.tagName==="VIDEO")&&(v.style.resize="both",v.style.overflow="auto",v.style.display="inline-block")},y=r.current;return y.addEventListener("input",f),y.addEventListener("paste",p),y.addEventListener("click",x),y.focus(),()=>{w.current&&clearTimeout(w.current),y.removeEventListener("input",f),y.removeEventListener("paste",p),y.removeEventListener("click",x)}},[e,s,i,a,g,m]),c.useEffect(()=>{if(!r.current||typeof window>"u")return;const f=new I.KeyboardShortcutManager,p=r.current,x=y=>{f.handleKeyDown(y,(C,v)=>{typeof window<"u"&&window.executeEditorCommand&&window.executeEditorCommand(C,v)})};return p.addEventListener("keydown",x),()=>{p.removeEventListener("keydown",x)}},[]),o.jsx("div",{ref:r,contentEditable:!0,suppressContentEditableWarning:!0,className:"rte-content",style:{minHeight:"200px",maxHeight:"100%",padding:"16px",outline:"none",border:"1px solid #ddd",borderRadius:"4px",fontSize:"14px",lineHeight:"1.5",overflow:"auto",flex:1,boxSizing:"border-box",wordWrap:"break-word",overflowWrap:"break-word"},children:o.jsx("p",{children:o.jsx("br",{})})})},ae=({editor:e,isEnabled:t})=>{const[u,s]=c.useState(!1),[i,a]=c.useState({top:0,left:0}),g=c.useRef(null),m=c.useRef(null),b=c.useRef(null),r=c.useRef(null);c.useEffect(()=>{if(!t){s(!1);return}r.current=g.current?.closest("[data-editora-editor]");const w=()=>{b.current&&clearTimeout(b.current);const f=window.getSelection();if(!f||f.rangeCount===0){s(!1),m.current=null;return}const p=f.getRangeAt(0),x=f.toString().trim(),y=r.current?.querySelector(".rte-content");if(!y||!y.contains(p.commonAncestorContainer)){s(!1),m.current=null;return}if(x.length>0){const C=p.getBoundingClientRect(),v=y.getBoundingClientRect(),T=300;if(C&&v){const S=C.top-50;let R=C.left+C.width/2;const j=T/2,L=v.left,M=v.right;R-j<L&&(R=L+j+10),R+j>M&&(R=M-j-10),a({top:S,left:R}),b.current=setTimeout(()=>{s(!0),m.current=p.cloneRange()},300)}}else s(!1),m.current=null},h=f=>{g.current&&!g.current.contains(f.target)&&(window.getSelection(),r.current?.querySelector(".rte-content")?.contains(f.target)||(s(!1),m.current=null))};return document.addEventListener("selectionchange",w),document.addEventListener("mousedown",h),document.addEventListener("keydown",f=>{f.key==="Escape"&&(s(!1),m.current=null)}),()=>{document.removeEventListener("selectionchange",w),document.removeEventListener("mousedown",h),b.current&&clearTimeout(b.current)}},[t]);const d=(w,h)=>{if(!m.current)return;const f=r.current?.querySelector(".rte-content");f&&f.focus(),{toggleBold:()=>document.execCommand("bold",!1),toggleItalic:()=>document.execCommand("italic",!1),toggleUnderline:()=>document.execCommand("underline",!1),toggleStrikethrough:()=>document.execCommand("strikeThrough",!1),createLink:()=>{typeof window<"u"&&window.executeEditorCommand&&window.executeEditorCommand("openLinkDialog")},clearFormatting:()=>{document.execCommand("removeFormat",!1),document.execCommand("unlink",!1)},toggleCode:()=>{const x=window.getSelection();if(x&&x.rangeCount>0){const y=x.getRangeAt(0),C=document.createElement("code");y.surroundContents(C)}},setBlockType:()=>{if(h==="blockquote"){const x=window.getSelection();if(x&&x.rangeCount>0){const y=x.getRangeAt(0);(y.commonAncestorContainer.nodeType===Node.TEXT_NODE?y.commonAncestorContainer.parentElement:y.commonAncestorContainer)?.closest?.("blockquote")?document.execCommand("formatBlock",!1,"p"):document.execCommand("formatBlock",!1,"blockquote")}}else h&&document.execCommand("formatBlock",!1,h)}}[w]?.(),s(!1),m.current=null,f&&f.focus()};return!t||!u?null:o.jsxs("div",{ref:g,className:"floating-toolbar",style:{position:"fixed",top:`${i.top}px`,left:`${i.left}px`,transform:"translateX(-50%)",zIndex:1e3,background:"white",border:"1px solid #e1e5e9",borderRadius:"6px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",padding:"6px",display:"flex",gap:"4px",alignItems:"center"},children:[o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("toggleBold"),title:"Bold (Ctrl+B)",children:o.jsx("strong",{children:"B"})}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("toggleItalic"),title:"Italic (Ctrl+I)",children:o.jsx("em",{children:"I"})}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("toggleUnderline"),title:"Underline (Ctrl+U)",children:o.jsx("u",{children:"U"})}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("toggleStrikethrough"),title:"Strikethrough",children:o.jsx("s",{children:"S"})}),o.jsx("div",{className:"floating-toolbar-separator"}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("clearFormatting"),title:"Clear Formatting",children:"⌫"}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("createLink"),title:"Insert Link",children:"🔗"}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("toggleCode"),title:"Code",children:"Code"}),o.jsx("div",{className:"floating-toolbar-separator"}),o.jsx("button",{className:"floating-toolbar-btn",onClick:()=>d("setBlockType","blockquote"),title:"Quote",children:"❝"})]})},le=({plugins:e,children:t})=>{const u=e.filter(i=>i.context?.provider);return u.length===0?o.jsx(o.Fragment,{children:t}):u.reduce((i,a)=>{const g=a.context.provider;return o.jsx(g,{children:i},a.name)},o.jsx(o.Fragment,{children:t}))},ie={toolbar:{items:[],floating:!1,sticky:!1},menubar:{enabled:!1,items:[]},contextMenu:{enabled:!0},media:{uploadUrl:"",libraryUrl:"",maxFileSize:10*1024*1024,allowedTypes:["image/jpeg","image/png","image/gif","image/webp"],headers:{},withCredentials:!1},paste:{clean:!0,keepFormatting:!1,convertWord:!0},history:{maxSteps:100,debounceMs:300},language:{locale:"en",direction:"ltr"},spellcheck:{enabled:!1,provider:"browser",apiUrl:"",apiHeaders:{}},autosave:{enabled:!1,intervalMs:3e4,storageKey:"rte-autosave",provider:"localStorage",apiUrl:""},accessibility:{enableARIA:!0,keyboardNavigation:!0,checker:!1},performance:{debounceInputMs:100,viewportOnlyScan:!0},content:{allowedTags:[],allowedAttributes:{},sanitize:!0},security:{sanitizeOnPaste:!0,sanitizeOnInput:!0}};function _(e,t){const u={...e};for(const s in t){const i=t[s],a=u[s];i!==void 0&&(typeof i=="object"&&i!==null&&!Array.isArray(i)&&typeof a=="object"&&a!==null&&!Array.isArray(a)?u[s]=_(a,i):u[s]=i)}return u}function G(e){const t=_(ie,{toolbar:e.toolbar,menubar:e.menubar,contextMenu:e.contextMenu,media:e.media,paste:e.paste,history:e.history,language:e.language,spellcheck:e.spellcheck,autosave:e.autosave,accessibility:e.accessibility,performance:e.performance,content:e.content,security:e.security});return e.floatingToolbar!==void 0&&(t.toolbar={...t.toolbar,floating:e.floatingToolbar.enabled??t.toolbar.floating}),e.mediaConfig&&(t.media={...t.media,uploadUrl:e.mediaConfig.uploadUrl||t.media.uploadUrl,libraryUrl:e.mediaConfig.libraryUrl||t.media.libraryUrl,maxFileSize:e.mediaConfig.maxFileSize||t.media.maxFileSize,allowedTypes:e.mediaConfig.allowedTypes||t.media.allowedTypes}),{...t,id:e.id,className:e.className,value:e.value,defaultValue:e.defaultValue,onChange:e.onChange,onInit:e.onInit,onDestroy:e.onDestroy,plugins:Array.isArray(e.plugins)?e.plugins.filter(s=>typeof s!="string"):[],pluginConfig:e.pluginConfig||{}}}const W=new Map;typeof window<"u"&&(window.registerEditorCommand=(e,t)=>{W.set(e,t)},window.executeEditorCommand=(e,t)=>{const u=W.get(e);return u?u(t):(console.warn(`No handler registered for command: ${e}`),!1)});const se=e=>{const t=c.useMemo(()=>G(e),[e.id,e.className,e.value,e.defaultValue,e.plugins,e.toolbar,e.menubar,e.contextMenu,e.media,e.paste,e.history,e.language,e.spellcheck,e.autosave,e.accessibility,e.performance,e.content,e.security,e.floatingToolbar,e.mediaConfig]),u=c.useRef(null),s=c.useRef(null),i=c.useRef(e.onInit),a=c.useRef(e.onDestroy),g=c.useRef(null);c.useEffect(()=>{i.current=e.onInit,a.current=e.onDestroy});const m=c.useMemo(()=>{const w=new I.PluginManager;t.plugins.forEach(f=>{w.register(f),f.commands&&typeof window<"u"&&Object.entries(f.commands).forEach(([p,x])=>{W.set(p,x)})});const h=new I.Editor(w);return u.current=h,h},[t.plugins]);c.useEffect(()=>{const w={getHTML:()=>g.current?.querySelector(".rte-content")?.innerHTML||"",setHTML:h=>{const f=g.current?.querySelector(".rte-content");f&&(f.innerHTML=h)},execCommand:(h,f)=>{typeof window<"u"&&window.executeEditorCommand&&window.executeEditorCommand(h,f)},registerCommand:(h,f)=>{typeof window<"u"&&window.registerEditorCommand&&window.registerEditorCommand(h,f)},focus:()=>{g.current?.querySelector(".rte-content")?.focus()},blur:()=>{g.current?.querySelector(".rte-content")?.blur()},destroy:()=>{a.current&&a.current()},onChange:h=>()=>{},getState:()=>({plugins:t.plugins,config:t}),toolbar:{items:m.toolbar?.items||[]}};return s.current=w,i.current&&i.current(w),()=>{a.current&&a.current()}},[]);const b=t.toolbar.floating??!1,r=t.toolbar.position||"top",d=t.toolbar.sticky??!1;return o.jsx(le,{plugins:t.plugins,children:o.jsxs("div",{ref:g,id:t.id,"data-editora-editor":!0,className:`rte-editor ${t.className||""}`,dir:t.language.direction,style:{display:"flex",flexDirection:"column",height:"100%"},children:[r!=="bottom"&&o.jsx(H,{editor:m,position:r,sticky:d,floating:b}),o.jsx(V,{editor:m,defaultValue:t.defaultValue,value:t.value,onChange:t.onChange,pasteConfig:t.paste,contentConfig:t.content,securityConfig:t.security,performanceConfig:t.performance,autosaveConfig:t.autosave}),r==="bottom"&&o.jsx(H,{editor:m,position:r,sticky:d,floating:b}),o.jsx(ae,{editor:m,isEnabled:b})]})})},X=e=>o.jsx(se,{...e});function ce(e={}){const t=c.useRef(null),u=c.useRef(e.onCommand);return c.useEffect(()=>{u.current=e.onCommand}),c.useEffect(()=>{if(typeof window>"u"||e.enabled===!1)return;const s=new I.KeyboardShortcutManager(e);t.current=s;const i=g=>s.handleKeyDown(g,(b,r)=>{u.current&&u.current(b,r),typeof window<"u"&&window.executeEditorCommand&&window.executeEditorCommand(b,r)}),a=e.editorElement||document;return a&&a.addEventListener("keydown",i),()=>{a&&a.removeEventListener("keydown",i)}},[e.editorElement,e.enabled,e.shortcuts,e.customShortcuts]),{getShortcuts:()=>t.current?.getAllShortcuts()||[],getShortcutForCommand:s=>t.current?.getShortcutForCommand(s),getShortcutsHelp:()=>t.current?.getShortcutsHelp()||"",enable:()=>t.current?.enable(),disable:()=>t.current?.disable(),isEnabled:()=>t.current?.isEnabled()||!1}}exports.EditorContent=V;exports.EditoraEditor=X;exports.InlineMenu=K;exports.RichTextEditor=X;exports.Toolbar=H;exports.mergeConfig=G;exports.useKeyboardShortcuts=ce;