@mdaemon/html-editor-react 1.3.0 → 1.4.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 CHANGED
@@ -1,3 +1,5 @@
1
+ [![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmdaemon-technologies%2FMDHTMLEditor-react%2Fmaster%2Fpackage.json&query=%24.version&prefix=v&label=npm&color=blue)](https://www.npmjs.com/package/@mdaemon/html-editor-react) [![install size](https://packagephobia.com/badge?p=@mdaemon/html-editor-react)](https://packagephobia.com/result?p=@mdaemon/html-editor-react) [![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmdaemon-technologies%2FMDHTMLEditor-react%2Fmaster%2Fpackage.json&query=%24.license&prefix=v&label=license&color=green)](https://github.com/mdaemon-technologies/MDHTMLEditor-react/blob/master/LICENSE) [![Node.js CI](https://github.com/mdaemon-technologies/MDHTMLEditor-react/actions/workflows/ci.yml/badge.svg)](https://github.com/mdaemon-technologies/MDHTMLEditor-react/actions/workflows/ci.yml)
2
+
1
3
  # MDHTMLEditor React
2
4
 
3
5
  A React wrapper for [MDHTMLEditor](https://github.com/mdaemon-technologies/MDHTMLEditor) — a WYSIWYG HTML editor built on TipTap. Provides a drop-in replacement for `@tinymce/tinymce-react` with no license key required.
@@ -236,7 +238,18 @@ The `config` prop (or `useEditor`'s `config` option) accepts an `EditorConfig` o
236
238
  | `toolbar` | `string` | *(preset)* | Custom toolbar layout string. |
237
239
  | `toolbar_mode` | `'sliding' \| 'floating' \| 'wrap'` | `'wrap'` | Toolbar overflow behavior. |
238
240
  | `toolbar_sticky` | `boolean` | `true` | Pin toolbar at top when scrolling. |
241
+ | `toolbar_narrow_breakpoint` | `number` | — | Pixel width below which the toolbar collapses into the overflow toggle. |
242
+ | `toolbar_priority` | `Record<string, number>` | &mdash; | Per-button overflow priority &mdash; higher values stay visible longer as the toolbar narrows. |
243
+ | `menubar` | `boolean` | `false` | Show a TinyMCE-style menu bar above the toolbar. |
244
+ | `contextmenu` | `boolean \| string` | `''` | Enable a right-click context menu. Pass a button-list string to customize its contents. |
245
+ | `quickbars_selection_toolbar` | `string` | `'bold italic \| quicklink blockquote'` | Inline floating toolbar shown over a text selection. |
246
+ | `quickbars_image_toolbar` | `boolean` | `false` | Inline floating toolbar shown when an image is selected. |
247
+ | `quickbars_insert_toolbar` | `boolean` | `false` | Inline floating toolbar shown on empty lines for quick insertion. |
248
+ | `elementpath` | `boolean` | `false` | Show a breadcrumb of the node path at the cursor in the status bar. |
249
+ | `valid_children` | `string` | &mdash; | TinyMCE-style rules controlling which child elements are allowed. |
239
250
  | `auto_focus` | `string` | &mdash; | Auto-focus on init. |
251
+ | `setFocus` | `string` | &mdash; | CSS selector for the element to focus on init (used when `auto_focus` is not set). |
252
+ | `plugins` | `string` | &mdash; | Accepted for TinyMCE compatibility and ignored &mdash; all features are built in. |
240
253
  | `browser_spellcheck` | `boolean` | `true` | Enable browser spell check. |
241
254
  | `entity_encoding` | `'raw' \| 'named' \| 'numeric'` | `'raw'` | HTML entity encoding mode. |
242
255
  | `paste_from_office` | `boolean` | `true` | Clean and preserve formatting when pasting from Microsoft Word/Excel. |
@@ -290,6 +303,25 @@ The `confab` / `confab-dark` skins integrate with the WorldClient theming system
290
303
  />
291
304
  ```
292
305
 
306
+ ### Menu Bar, Context Menu & Quick Toolbars
307
+
308
+ Opt into the additional editing surfaces exposed by `@mdaemon/html-editor` 1.6.0:
309
+
310
+ ```tsx
311
+ <Editor
312
+ config={{
313
+ menubar: true, // menu bar above the toolbar
314
+ contextmenu: true, // right-click context menu
315
+ quickbars_selection_toolbar: 'bold italic | quicklink blockquote',
316
+ quickbars_image_toolbar: true, // floating toolbar when an image is selected
317
+ elementpath: true, // breadcrumb status bar at the cursor
318
+ }}
319
+ />
320
+ ```
321
+
322
+ `contextmenu` also accepts a button-list string to customize its contents, and
323
+ `quickbars_insert_toolbar` shows a floating insert toolbar on empty lines.
324
+
293
325
  ### Read-Only Mode
294
326
 
295
327
  Start the editor read-only with `readonly: true`, or toggle it at runtime through the underlying editor instance:
@@ -636,6 +668,25 @@ npm run demo
636
668
 
637
669
  This starts a Vite dev server at `http://localhost:5173` with examples of both the `<Editor>` component and `useEditor` hook.
638
670
 
671
+ ## Development
672
+
673
+ The package is bundled with [Vite 8](https://vite.dev/) (Rolldown) and ships ES, CommonJS, and TypeScript declaration outputs.
674
+
675
+ | Script | Description |
676
+ |--------|-------------|
677
+ | `npm run build` | Production build to `dist/` (ES + CJS + `.d.ts`). |
678
+ | `npm run build:dev` | Development build (unminified, sourcemaps). |
679
+ | `npm run dev` | Rebuild on change (watch mode). |
680
+ | `npm test` | Run the Jest test suite. |
681
+ | `npm run test:coverage` | Run tests with coverage. |
682
+ | `npm run lint` | Lint `src` with ESLint. |
683
+ | `npm run typecheck` | Type-check with `tsc --noEmit`. |
684
+ | `npm run demo` | Launch the demo app. |
685
+
686
+ > **Node.js:** building the package requires Node `^20.19.0 || >=22.12.0` (a Vite 8 requirement). Consumers of the published package are unaffected.
687
+
688
+ Lint, typecheck, and tests run automatically in [CI](.github/workflows/ci.yml) on every push and pull request to `master` across Node 20, 22, 24, and 26.
689
+
639
690
  ## License
640
691
 
641
692
  LGPL-3.0-or-later &mdash; [MDaemon Technologies, Ltd.](https://www.mdaemon.com)
@@ -0,0 +1,88 @@
1
+ import { HTMLEditor, EditorConfig } from '@mdaemon/html-editor';
2
+ export interface EditorProps {
3
+ /**
4
+ * Editor configuration - TinyMCE compatible
5
+ */
6
+ config: EditorConfig;
7
+ /**
8
+ * Initial HTML content (also known as 'body' for TinyMCE compat)
9
+ */
10
+ body?: string;
11
+ /**
12
+ * Initial HTML content (alias for body)
13
+ */
14
+ initialValue?: string;
15
+ /**
16
+ * Name attribute for the hidden textarea
17
+ */
18
+ name?: string;
19
+ /**
20
+ * Called when content is modified
21
+ */
22
+ onDirty?: (dirty: boolean) => void;
23
+ /**
24
+ * Called on content change (debounced)
25
+ */
26
+ onChange?: (content: string) => void;
27
+ /**
28
+ * Called when editor is fully initialized
29
+ */
30
+ onInit?: (editor: HTMLEditor) => void;
31
+ /**
32
+ * Called when editor receives focus
33
+ */
34
+ onFocus?: () => void;
35
+ /**
36
+ * Called when editor loses focus
37
+ */
38
+ onBlur?: () => void;
39
+ /**
40
+ * Translation function
41
+ */
42
+ translate?: (key: string) => string;
43
+ /**
44
+ * Function to resolve file paths (like TinyMCE's tinymceScriptSrc path)
45
+ */
46
+ getFileSrc?: (path: string) => string;
47
+ /**
48
+ * Disable the editor
49
+ */
50
+ disabled?: boolean;
51
+ }
52
+ export interface EditorRef {
53
+ /**
54
+ * Get the underlying HTMLEditor instance
55
+ */
56
+ getEditor: () => HTMLEditor | null;
57
+ /**
58
+ * Get the editor content as HTML
59
+ */
60
+ getContent: () => string;
61
+ /**
62
+ * Set the editor content
63
+ */
64
+ setContent: (html: string) => void;
65
+ /**
66
+ * Insert content at cursor position
67
+ */
68
+ insertContent: (html: string) => void;
69
+ /**
70
+ * Focus the editor
71
+ */
72
+ focus: () => void;
73
+ }
74
+ /**
75
+ * Get content from the global editor instance
76
+ * Note: Only works with single editor on the page
77
+ */
78
+ export declare function getEditorContent(): string;
79
+ /**
80
+ * Set content on the global editor instance
81
+ * Note: Only works with single editor on the page
82
+ */
83
+ export declare function setEditorContent(html: string): void;
84
+ /**
85
+ * MDHTMLEditor React Component
86
+ */
87
+ export declare const Editor: import('react').ForwardRefExoticComponent<EditorProps & import('react').RefAttributes<EditorRef>>;
88
+ export default Editor;
package/dist/index.d.ts CHANGED
@@ -1,168 +1,9 @@
1
- import { EditorConfig } from '@mdaemon/html-editor';
2
- import { EditorEvents } from '@mdaemon/html-editor';
3
- import { fontNames } from '@mdaemon/html-editor';
4
- import { ForwardRefExoticComponent } from 'react';
5
- import { HTMLEditor } from '@mdaemon/html-editor';
6
- import { RefAttributes } from 'react';
7
- import { setGetFileSrc } from '@mdaemon/html-editor';
8
- import { setTranslate } from '@mdaemon/html-editor';
9
- import { Template } from '@mdaemon/html-editor';
10
- import { ToolbarButtonAPI } from '@mdaemon/html-editor';
11
- import { ToolbarButtonSpec } from '@mdaemon/html-editor';
12
-
13
- /**
14
- * MDHTMLEditor React Component
15
- */
16
- declare const Editor: ForwardRefExoticComponent<EditorProps & RefAttributes<EditorRef>>;
17
- export { Editor }
18
- export { Editor as MDEditor }
19
-
20
- export { EditorConfig }
21
-
22
- export { EditorEvents }
23
-
24
- export declare interface EditorProps {
25
- /**
26
- * Editor configuration - TinyMCE compatible
27
- */
28
- config: EditorConfig;
29
- /**
30
- * Initial HTML content (also known as 'body' for TinyMCE compat)
31
- */
32
- body?: string;
33
- /**
34
- * Initial HTML content (alias for body)
35
- */
36
- initialValue?: string;
37
- /**
38
- * Name attribute for the hidden textarea
39
- */
40
- name?: string;
41
- /**
42
- * Called when content is modified
43
- */
44
- onDirty?: (dirty: boolean) => void;
45
- /**
46
- * Called on content change (debounced)
47
- */
48
- onChange?: (content: string) => void;
49
- /**
50
- * Called when editor is fully initialized
51
- */
52
- onInit?: (editor: HTMLEditor) => void;
53
- /**
54
- * Called when editor receives focus
55
- */
56
- onFocus?: () => void;
57
- /**
58
- * Called when editor loses focus
59
- */
60
- onBlur?: () => void;
61
- /**
62
- * Translation function
63
- */
64
- translate?: (key: string) => string;
65
- /**
66
- * Function to resolve file paths (like TinyMCE's tinymceScriptSrc path)
67
- */
68
- getFileSrc?: (path: string) => string;
69
- /**
70
- * Disable the editor
71
- */
72
- disabled?: boolean;
73
- }
74
-
75
- export declare interface EditorRef {
76
- /**
77
- * Get the underlying HTMLEditor instance
78
- */
79
- getEditor: () => HTMLEditor | null;
80
- /**
81
- * Get the editor content as HTML
82
- */
83
- getContent: () => string;
84
- /**
85
- * Set the editor content
86
- */
87
- setContent: (html: string) => void;
88
- /**
89
- * Insert content at cursor position
90
- */
91
- insertContent: (html: string) => void;
92
- /**
93
- * Focus the editor
94
- */
95
- focus: () => void;
96
- }
97
-
98
- export { fontNames }
99
-
100
- /**
101
- * Get content from the global editor instance
102
- * Note: Only works with single editor on the page
103
- */
104
- export declare function getEditorContent(): string;
105
-
106
- /**
107
- * Set content on the global editor instance
108
- * Note: Only works with single editor on the page
109
- */
110
- export declare function setEditorContent(html: string): void;
111
-
112
- export { setGetFileSrc }
113
-
114
- export { setTranslate }
115
-
116
- export { Template }
117
-
118
- export { ToolbarButtonAPI }
119
-
120
- export { ToolbarButtonSpec }
121
-
122
- /**
123
- * Hook for programmatic editor control
124
- */
125
- export declare function useEditor(options?: UseEditorOptions): UseEditorReturn;
126
-
127
- export declare interface UseEditorOptions {
128
- config?: EditorConfig;
129
- content?: string;
130
- onUpdate?: (html: string) => void;
131
- }
132
-
133
- export declare interface UseEditorReturn {
134
- /**
135
- * The editor instance
136
- */
137
- editor: HTMLEditor | null;
138
- /**
139
- * Ref to attach to the container element
140
- */
141
- containerRef: React.RefObject<HTMLDivElement | null>;
142
- /**
143
- * Whether the editor is ready
144
- */
145
- ready: boolean;
146
- /**
147
- * Get the current HTML content
148
- */
149
- getContent: () => string;
150
- /**
151
- * Set the HTML content
152
- */
153
- setContent: (html: string) => void;
154
- /**
155
- * Insert HTML at cursor
156
- */
157
- insertContent: (html: string) => void;
158
- /**
159
- * Focus the editor
160
- */
161
- focus: () => void;
162
- /**
163
- * Check if editor is dirty
164
- */
165
- isDirty: () => boolean;
166
- }
167
-
168
- export { }
1
+ /**
2
+ * MDHTMLEditor React - Main Entry Point
3
+ */
4
+ export { Editor as MDEditor, Editor, getEditorContent, setEditorContent } from './Editor';
5
+ export type { EditorProps, EditorRef } from './Editor';
6
+ export { useEditor } from './useEditor';
7
+ export type { UseEditorOptions, UseEditorReturn } from './useEditor';
8
+ export { fontNames, setTranslate, setGetFileSrc, } from '@mdaemon/html-editor';
9
+ export type { EditorConfig, EditorEvents, Template, ToolbarButtonSpec, ToolbarButtonAPI, } from '@mdaemon/html-editor';
package/dist/index.js CHANGED
@@ -1,173 +1,178 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const react = require("react");
5
- const htmlEditor = require("@mdaemon/html-editor");
6
- let globalEditor = null;
2
+ let react = require("react");
3
+ let _mdaemon_html_editor = require("@mdaemon/html-editor");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ //#region src/Editor.tsx
6
+ /**
7
+ * MDHTMLEditor React Component
8
+ * A TinyMCE-compatible React wrapper for MDHTMLEditor
9
+ */
10
+ var globalEditor = null;
11
+ /**
12
+ * Get content from the global editor instance
13
+ * Note: Only works with single editor on the page
14
+ */
7
15
  function getEditorContent() {
8
- return globalEditor?.getContent() ?? "";
16
+ return globalEditor?.getContent() ?? "";
9
17
  }
18
+ /**
19
+ * Set content on the global editor instance
20
+ * Note: Only works with single editor on the page
21
+ */
10
22
  function setEditorContent(html) {
11
- globalEditor?.setContent(html);
23
+ globalEditor?.setContent(html);
12
24
  }
13
- const Editor = react.forwardRef(function Editor2(props, ref) {
14
- const {
15
- config,
16
- body,
17
- initialValue,
18
- name,
19
- onDirty,
20
- onChange,
21
- onInit,
22
- onFocus,
23
- onBlur,
24
- translate,
25
- getFileSrc,
26
- disabled
27
- } = props;
28
- const containerRef = react.useRef(null);
29
- const editorRef = react.useRef(null);
30
- const [ready, setReady] = react.useState(false);
31
- react.useEffect(() => {
32
- if (translate) {
33
- htmlEditor.setTranslate(translate);
34
- }
35
- }, [translate]);
36
- react.useEffect(() => {
37
- if (getFileSrc) {
38
- htmlEditor.setGetFileSrc(getFileSrc);
39
- }
40
- }, [getFileSrc]);
41
- react.useEffect(() => {
42
- if (!containerRef.current) return;
43
- const editor = new htmlEditor.HTMLEditor(containerRef.current, {
44
- ...config,
45
- // Pass setup through to allow custom buttons
46
- setup: (ed) => {
47
- config.setup?.(ed);
48
- }
49
- });
50
- editorRef.current = editor;
51
- globalEditor = editor;
52
- const content = body ?? initialValue ?? "";
53
- if (content) {
54
- editor.setContent(content);
55
- }
56
- editor.on("init", () => {
57
- setReady(true);
58
- onInit?.(editor);
59
- });
60
- editor.on("dirty", (dirty) => {
61
- onDirty?.(dirty);
62
- });
63
- editor.on("change", (content2) => {
64
- onChange?.(content2);
65
- editor.setDirty(false);
66
- });
67
- editor.on("focus", () => {
68
- onFocus?.();
69
- });
70
- editor.on("blur", () => {
71
- onBlur?.();
72
- });
73
- return () => {
74
- editor.destroy();
75
- editorRef.current = null;
76
- if (globalEditor === editor) {
77
- globalEditor = null;
78
- }
79
- };
80
- }, []);
81
- react.useEffect(() => {
82
- if (containerRef.current) {
83
- containerRef.current.style.pointerEvents = disabled ? "none" : "";
84
- containerRef.current.style.opacity = disabled ? "0.6" : "";
85
- }
86
- }, [disabled]);
87
- react.useImperativeHandle(ref, () => ({
88
- getEditor: () => editorRef.current,
89
- getContent: () => editorRef.current?.getContent() ?? "",
90
- setContent: (html) => editorRef.current?.setContent(html),
91
- insertContent: (html) => editorRef.current?.insertContent(html),
92
- focus: () => editorRef.current?.focus()
93
- }), []);
94
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "md-editor-react-wrapper", children: [
95
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "md-editor-container" }),
96
- name && ready && /* @__PURE__ */ jsxRuntime.jsx(
97
- "input",
98
- {
99
- type: "hidden",
100
- name,
101
- value: editorRef.current?.getContent() ?? ""
102
- }
103
- )
104
- ] });
25
+ /**
26
+ * MDHTMLEditor React Component
27
+ */
28
+ var Editor = (0, react.forwardRef)(function Editor(props, ref) {
29
+ const { config, body, initialValue, name, onDirty, onChange, onInit, onFocus, onBlur, translate, getFileSrc, disabled } = props;
30
+ const containerRef = (0, react.useRef)(null);
31
+ const editorRef = (0, react.useRef)(null);
32
+ const [ready, setReady] = (0, react.useState)(false);
33
+ (0, react.useEffect)(() => {
34
+ if (translate) (0, _mdaemon_html_editor.setTranslate)(translate);
35
+ }, [translate]);
36
+ (0, react.useEffect)(() => {
37
+ if (getFileSrc) (0, _mdaemon_html_editor.setGetFileSrc)(getFileSrc);
38
+ }, [getFileSrc]);
39
+ (0, react.useEffect)(() => {
40
+ if (!containerRef.current) return;
41
+ const editor = new _mdaemon_html_editor.HTMLEditor(containerRef.current, {
42
+ ...config,
43
+ setup: (ed) => {
44
+ config.setup?.(ed);
45
+ }
46
+ });
47
+ editorRef.current = editor;
48
+ globalEditor = editor;
49
+ const content = body ?? initialValue ?? "";
50
+ if (content) editor.setContent(content);
51
+ editor.on("init", () => {
52
+ setReady(true);
53
+ onInit?.(editor);
54
+ });
55
+ editor.on("dirty", (dirty) => {
56
+ onDirty?.(dirty);
57
+ });
58
+ editor.on("change", (content) => {
59
+ onChange?.(content);
60
+ editor.setDirty(false);
61
+ });
62
+ editor.on("focus", () => {
63
+ onFocus?.();
64
+ });
65
+ editor.on("blur", () => {
66
+ onBlur?.();
67
+ });
68
+ return () => {
69
+ editor.destroy();
70
+ editorRef.current = null;
71
+ if (globalEditor === editor) globalEditor = null;
72
+ };
73
+ }, []);
74
+ (0, react.useEffect)(() => {
75
+ if (containerRef.current) {
76
+ containerRef.current.style.pointerEvents = disabled ? "none" : "";
77
+ containerRef.current.style.opacity = disabled ? "0.6" : "";
78
+ }
79
+ }, [disabled]);
80
+ (0, react.useImperativeHandle)(ref, () => ({
81
+ getEditor: () => editorRef.current,
82
+ getContent: () => editorRef.current?.getContent() ?? "",
83
+ setContent: (html) => editorRef.current?.setContent(html),
84
+ insertContent: (html) => editorRef.current?.insertContent(html),
85
+ focus: () => editorRef.current?.focus()
86
+ }), []);
87
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
88
+ className: "md-editor-react-wrapper",
89
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
90
+ ref: containerRef,
91
+ className: "md-editor-container"
92
+ }), name && ready && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
93
+ type: "hidden",
94
+ name,
95
+ value: editorRef.current?.getContent() ?? ""
96
+ })]
97
+ });
105
98
  });
106
99
  Editor.displayName = "MDHTMLEditor";
100
+ //#endregion
101
+ //#region src/useEditor.ts
102
+ /**
103
+ * useEditor Hook
104
+ * Custom hook for advanced editor usage
105
+ */
106
+ /**
107
+ * Hook for programmatic editor control
108
+ */
107
109
  function useEditor(options = {}) {
108
- const { config = {}, content = "", onUpdate } = options;
109
- const containerRef = react.useRef(null);
110
- const editorRef = react.useRef(null);
111
- const [ready, setReady] = react.useState(false);
112
- react.useEffect(() => {
113
- if (!containerRef.current) return;
114
- const editor = new htmlEditor.HTMLEditor(containerRef.current, config);
115
- editorRef.current = editor;
116
- if (content) {
117
- editor.setContent(content);
118
- }
119
- editor.on("init", () => {
120
- setReady(true);
121
- });
122
- if (onUpdate) {
123
- editor.on("change", onUpdate);
124
- }
125
- return () => {
126
- editor.destroy();
127
- editorRef.current = null;
128
- setReady(false);
129
- };
130
- }, []);
131
- const getContent = react.useCallback(() => {
132
- return editorRef.current?.getContent() ?? "";
133
- }, []);
134
- const setContent = react.useCallback((html) => {
135
- editorRef.current?.setContent(html);
136
- }, []);
137
- const insertContent = react.useCallback((html) => {
138
- editorRef.current?.insertContent(html);
139
- }, []);
140
- const focus = react.useCallback(() => {
141
- editorRef.current?.focus();
142
- }, []);
143
- const isDirty = react.useCallback(() => {
144
- return editorRef.current?.isDirty() ?? false;
145
- }, []);
146
- return {
147
- editor: editorRef.current,
148
- containerRef,
149
- ready,
150
- getContent,
151
- setContent,
152
- insertContent,
153
- focus,
154
- isDirty
155
- };
110
+ const { config = {}, content = "", onUpdate } = options;
111
+ const containerRef = (0, react.useRef)(null);
112
+ const editorRef = (0, react.useRef)(null);
113
+ const [ready, setReady] = (0, react.useState)(false);
114
+ (0, react.useEffect)(() => {
115
+ if (!containerRef.current) return;
116
+ const editor = new _mdaemon_html_editor.HTMLEditor(containerRef.current, config);
117
+ editorRef.current = editor;
118
+ if (content) editor.setContent(content);
119
+ editor.on("init", () => {
120
+ setReady(true);
121
+ });
122
+ if (onUpdate) editor.on("change", onUpdate);
123
+ return () => {
124
+ editor.destroy();
125
+ editorRef.current = null;
126
+ setReady(false);
127
+ };
128
+ }, []);
129
+ const getContent = (0, react.useCallback)(() => {
130
+ return editorRef.current?.getContent() ?? "";
131
+ }, []);
132
+ const setContent = (0, react.useCallback)((html) => {
133
+ editorRef.current?.setContent(html);
134
+ }, []);
135
+ const insertContent = (0, react.useCallback)((html) => {
136
+ editorRef.current?.insertContent(html);
137
+ }, []);
138
+ const focus = (0, react.useCallback)(() => {
139
+ editorRef.current?.focus();
140
+ }, []);
141
+ const isDirty = (0, react.useCallback)(() => {
142
+ return editorRef.current?.isDirty() ?? false;
143
+ }, []);
144
+ return {
145
+ editor: editorRef.current,
146
+ containerRef,
147
+ ready,
148
+ getContent,
149
+ setContent,
150
+ insertContent,
151
+ focus,
152
+ isDirty
153
+ };
156
154
  }
155
+ //#endregion
156
+ exports.Editor = Editor;
157
+ exports.MDEditor = Editor;
157
158
  Object.defineProperty(exports, "fontNames", {
158
- enumerable: true,
159
- get: () => htmlEditor.fontNames
159
+ enumerable: true,
160
+ get: function() {
161
+ return _mdaemon_html_editor.fontNames;
162
+ }
160
163
  });
164
+ exports.getEditorContent = getEditorContent;
165
+ exports.setEditorContent = setEditorContent;
161
166
  Object.defineProperty(exports, "setGetFileSrc", {
162
- enumerable: true,
163
- get: () => htmlEditor.setGetFileSrc
167
+ enumerable: true,
168
+ get: function() {
169
+ return _mdaemon_html_editor.setGetFileSrc;
170
+ }
164
171
  });
165
172
  Object.defineProperty(exports, "setTranslate", {
166
- enumerable: true,
167
- get: () => htmlEditor.setTranslate
173
+ enumerable: true,
174
+ get: function() {
175
+ return _mdaemon_html_editor.setTranslate;
176
+ }
168
177
  });
169
- exports.Editor = Editor;
170
- exports.MDEditor = Editor;
171
- exports.getEditorContent = getEditorContent;
172
- exports.setEditorContent = setEditorContent;
173
178
  exports.useEditor = useEditor;
package/dist/index.mjs CHANGED
@@ -1,165 +1,155 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { forwardRef, useRef, useState, useEffect, useImperativeHandle, useCallback } from "react";
3
- import { setTranslate, setGetFileSrc, HTMLEditor } from "@mdaemon/html-editor";
4
- import { fontNames, setGetFileSrc as setGetFileSrc2, setTranslate as setTranslate2 } from "@mdaemon/html-editor";
5
- let globalEditor = null;
1
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
2
+ import { HTMLEditor, fontNames, setGetFileSrc, setGetFileSrc as setGetFileSrc$1, setTranslate, setTranslate as setTranslate$1 } from "@mdaemon/html-editor";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/Editor.tsx
5
+ /**
6
+ * MDHTMLEditor React Component
7
+ * A TinyMCE-compatible React wrapper for MDHTMLEditor
8
+ */
9
+ var globalEditor = null;
10
+ /**
11
+ * Get content from the global editor instance
12
+ * Note: Only works with single editor on the page
13
+ */
6
14
  function getEditorContent() {
7
- return globalEditor?.getContent() ?? "";
15
+ return globalEditor?.getContent() ?? "";
8
16
  }
17
+ /**
18
+ * Set content on the global editor instance
19
+ * Note: Only works with single editor on the page
20
+ */
9
21
  function setEditorContent(html) {
10
- globalEditor?.setContent(html);
22
+ globalEditor?.setContent(html);
11
23
  }
12
- const Editor = forwardRef(function Editor2(props, ref) {
13
- const {
14
- config,
15
- body,
16
- initialValue,
17
- name,
18
- onDirty,
19
- onChange,
20
- onInit,
21
- onFocus,
22
- onBlur,
23
- translate,
24
- getFileSrc,
25
- disabled
26
- } = props;
27
- const containerRef = useRef(null);
28
- const editorRef = useRef(null);
29
- const [ready, setReady] = useState(false);
30
- useEffect(() => {
31
- if (translate) {
32
- setTranslate(translate);
33
- }
34
- }, [translate]);
35
- useEffect(() => {
36
- if (getFileSrc) {
37
- setGetFileSrc(getFileSrc);
38
- }
39
- }, [getFileSrc]);
40
- useEffect(() => {
41
- if (!containerRef.current) return;
42
- const editor = new HTMLEditor(containerRef.current, {
43
- ...config,
44
- // Pass setup through to allow custom buttons
45
- setup: (ed) => {
46
- config.setup?.(ed);
47
- }
48
- });
49
- editorRef.current = editor;
50
- globalEditor = editor;
51
- const content = body ?? initialValue ?? "";
52
- if (content) {
53
- editor.setContent(content);
54
- }
55
- editor.on("init", () => {
56
- setReady(true);
57
- onInit?.(editor);
58
- });
59
- editor.on("dirty", (dirty) => {
60
- onDirty?.(dirty);
61
- });
62
- editor.on("change", (content2) => {
63
- onChange?.(content2);
64
- editor.setDirty(false);
65
- });
66
- editor.on("focus", () => {
67
- onFocus?.();
68
- });
69
- editor.on("blur", () => {
70
- onBlur?.();
71
- });
72
- return () => {
73
- editor.destroy();
74
- editorRef.current = null;
75
- if (globalEditor === editor) {
76
- globalEditor = null;
77
- }
78
- };
79
- }, []);
80
- useEffect(() => {
81
- if (containerRef.current) {
82
- containerRef.current.style.pointerEvents = disabled ? "none" : "";
83
- containerRef.current.style.opacity = disabled ? "0.6" : "";
84
- }
85
- }, [disabled]);
86
- useImperativeHandle(ref, () => ({
87
- getEditor: () => editorRef.current,
88
- getContent: () => editorRef.current?.getContent() ?? "",
89
- setContent: (html) => editorRef.current?.setContent(html),
90
- insertContent: (html) => editorRef.current?.insertContent(html),
91
- focus: () => editorRef.current?.focus()
92
- }), []);
93
- return /* @__PURE__ */ jsxs("div", { className: "md-editor-react-wrapper", children: [
94
- /* @__PURE__ */ jsx("div", { ref: containerRef, className: "md-editor-container" }),
95
- name && ready && /* @__PURE__ */ jsx(
96
- "input",
97
- {
98
- type: "hidden",
99
- name,
100
- value: editorRef.current?.getContent() ?? ""
101
- }
102
- )
103
- ] });
24
+ /**
25
+ * MDHTMLEditor React Component
26
+ */
27
+ var Editor = forwardRef(function Editor(props, ref) {
28
+ const { config, body, initialValue, name, onDirty, onChange, onInit, onFocus, onBlur, translate, getFileSrc, disabled } = props;
29
+ const containerRef = useRef(null);
30
+ const editorRef = useRef(null);
31
+ const [ready, setReady] = useState(false);
32
+ useEffect(() => {
33
+ if (translate) setTranslate$1(translate);
34
+ }, [translate]);
35
+ useEffect(() => {
36
+ if (getFileSrc) setGetFileSrc$1(getFileSrc);
37
+ }, [getFileSrc]);
38
+ useEffect(() => {
39
+ if (!containerRef.current) return;
40
+ const editor = new HTMLEditor(containerRef.current, {
41
+ ...config,
42
+ setup: (ed) => {
43
+ config.setup?.(ed);
44
+ }
45
+ });
46
+ editorRef.current = editor;
47
+ globalEditor = editor;
48
+ const content = body ?? initialValue ?? "";
49
+ if (content) editor.setContent(content);
50
+ editor.on("init", () => {
51
+ setReady(true);
52
+ onInit?.(editor);
53
+ });
54
+ editor.on("dirty", (dirty) => {
55
+ onDirty?.(dirty);
56
+ });
57
+ editor.on("change", (content) => {
58
+ onChange?.(content);
59
+ editor.setDirty(false);
60
+ });
61
+ editor.on("focus", () => {
62
+ onFocus?.();
63
+ });
64
+ editor.on("blur", () => {
65
+ onBlur?.();
66
+ });
67
+ return () => {
68
+ editor.destroy();
69
+ editorRef.current = null;
70
+ if (globalEditor === editor) globalEditor = null;
71
+ };
72
+ }, []);
73
+ useEffect(() => {
74
+ if (containerRef.current) {
75
+ containerRef.current.style.pointerEvents = disabled ? "none" : "";
76
+ containerRef.current.style.opacity = disabled ? "0.6" : "";
77
+ }
78
+ }, [disabled]);
79
+ useImperativeHandle(ref, () => ({
80
+ getEditor: () => editorRef.current,
81
+ getContent: () => editorRef.current?.getContent() ?? "",
82
+ setContent: (html) => editorRef.current?.setContent(html),
83
+ insertContent: (html) => editorRef.current?.insertContent(html),
84
+ focus: () => editorRef.current?.focus()
85
+ }), []);
86
+ return /* @__PURE__ */ jsxs("div", {
87
+ className: "md-editor-react-wrapper",
88
+ children: [/* @__PURE__ */ jsx("div", {
89
+ ref: containerRef,
90
+ className: "md-editor-container"
91
+ }), name && ready && /* @__PURE__ */ jsx("input", {
92
+ type: "hidden",
93
+ name,
94
+ value: editorRef.current?.getContent() ?? ""
95
+ })]
96
+ });
104
97
  });
105
98
  Editor.displayName = "MDHTMLEditor";
99
+ //#endregion
100
+ //#region src/useEditor.ts
101
+ /**
102
+ * useEditor Hook
103
+ * Custom hook for advanced editor usage
104
+ */
105
+ /**
106
+ * Hook for programmatic editor control
107
+ */
106
108
  function useEditor(options = {}) {
107
- const { config = {}, content = "", onUpdate } = options;
108
- const containerRef = useRef(null);
109
- const editorRef = useRef(null);
110
- const [ready, setReady] = useState(false);
111
- useEffect(() => {
112
- if (!containerRef.current) return;
113
- const editor = new HTMLEditor(containerRef.current, config);
114
- editorRef.current = editor;
115
- if (content) {
116
- editor.setContent(content);
117
- }
118
- editor.on("init", () => {
119
- setReady(true);
120
- });
121
- if (onUpdate) {
122
- editor.on("change", onUpdate);
123
- }
124
- return () => {
125
- editor.destroy();
126
- editorRef.current = null;
127
- setReady(false);
128
- };
129
- }, []);
130
- const getContent = useCallback(() => {
131
- return editorRef.current?.getContent() ?? "";
132
- }, []);
133
- const setContent = useCallback((html) => {
134
- editorRef.current?.setContent(html);
135
- }, []);
136
- const insertContent = useCallback((html) => {
137
- editorRef.current?.insertContent(html);
138
- }, []);
139
- const focus = useCallback(() => {
140
- editorRef.current?.focus();
141
- }, []);
142
- const isDirty = useCallback(() => {
143
- return editorRef.current?.isDirty() ?? false;
144
- }, []);
145
- return {
146
- editor: editorRef.current,
147
- containerRef,
148
- ready,
149
- getContent,
150
- setContent,
151
- insertContent,
152
- focus,
153
- isDirty
154
- };
109
+ const { config = {}, content = "", onUpdate } = options;
110
+ const containerRef = useRef(null);
111
+ const editorRef = useRef(null);
112
+ const [ready, setReady] = useState(false);
113
+ useEffect(() => {
114
+ if (!containerRef.current) return;
115
+ const editor = new HTMLEditor(containerRef.current, config);
116
+ editorRef.current = editor;
117
+ if (content) editor.setContent(content);
118
+ editor.on("init", () => {
119
+ setReady(true);
120
+ });
121
+ if (onUpdate) editor.on("change", onUpdate);
122
+ return () => {
123
+ editor.destroy();
124
+ editorRef.current = null;
125
+ setReady(false);
126
+ };
127
+ }, []);
128
+ const getContent = useCallback(() => {
129
+ return editorRef.current?.getContent() ?? "";
130
+ }, []);
131
+ const setContent = useCallback((html) => {
132
+ editorRef.current?.setContent(html);
133
+ }, []);
134
+ const insertContent = useCallback((html) => {
135
+ editorRef.current?.insertContent(html);
136
+ }, []);
137
+ const focus = useCallback(() => {
138
+ editorRef.current?.focus();
139
+ }, []);
140
+ const isDirty = useCallback(() => {
141
+ return editorRef.current?.isDirty() ?? false;
142
+ }, []);
143
+ return {
144
+ editor: editorRef.current,
145
+ containerRef,
146
+ ready,
147
+ getContent,
148
+ setContent,
149
+ insertContent,
150
+ focus,
151
+ isDirty
152
+ };
155
153
  }
156
- export {
157
- Editor,
158
- Editor as MDEditor,
159
- fontNames,
160
- getEditorContent,
161
- setEditorContent,
162
- setGetFileSrc2 as setGetFileSrc,
163
- setTranslate2 as setTranslate,
164
- useEditor
165
- };
154
+ //#endregion
155
+ export { Editor, Editor as MDEditor, fontNames, getEditorContent, setEditorContent, setGetFileSrc, setTranslate, useEditor };
@@ -0,0 +1,44 @@
1
+ import { HTMLEditor, EditorConfig } from '@mdaemon/html-editor';
2
+ export interface UseEditorOptions {
3
+ config?: EditorConfig;
4
+ content?: string;
5
+ onUpdate?: (html: string) => void;
6
+ }
7
+ export interface UseEditorReturn {
8
+ /**
9
+ * The editor instance
10
+ */
11
+ editor: HTMLEditor | null;
12
+ /**
13
+ * Ref to attach to the container element
14
+ */
15
+ containerRef: React.RefObject<HTMLDivElement | null>;
16
+ /**
17
+ * Whether the editor is ready
18
+ */
19
+ ready: boolean;
20
+ /**
21
+ * Get the current HTML content
22
+ */
23
+ getContent: () => string;
24
+ /**
25
+ * Set the HTML content
26
+ */
27
+ setContent: (html: string) => void;
28
+ /**
29
+ * Insert HTML at cursor
30
+ */
31
+ insertContent: (html: string) => void;
32
+ /**
33
+ * Focus the editor
34
+ */
35
+ focus: () => void;
36
+ /**
37
+ * Check if editor is dirty
38
+ */
39
+ isDirty: () => boolean;
40
+ }
41
+ /**
42
+ * Hook for programmatic editor control
43
+ */
44
+ export declare function useEditor(options?: UseEditorOptions): UseEditorReturn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdaemon/html-editor-react",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "React wrapper for MDHTMLEditor",
5
5
  "homepage": "https://github.com/mdaemon-technologies/MDHTMLEditor-react",
6
6
  "repository": {
@@ -53,10 +53,11 @@
53
53
  "react-dom": "^19.0.0 || ^18.0.0"
54
54
  },
55
55
  "dependencies": {
56
- "@mdaemon/html-editor": "^1.5.0",
56
+ "@mdaemon/html-editor": "^1.6.0",
57
57
  "@tiptap/react": "^3.23.4"
58
58
  },
59
59
  "devDependencies": {
60
+ "@eslint/js": "^9.39.4",
60
61
  "@swc/core": "^1.11.0",
61
62
  "@swc/jest": "^0.2.39",
62
63
  "@testing-library/jest-dom": "^6.9.1",
@@ -64,14 +65,19 @@
64
65
  "@types/jest": "^30.0.0",
65
66
  "@types/react": "^19.2.0",
66
67
  "@types/react-dom": "^19.2.0",
67
- "@vitejs/plugin-react": "^5.1.4",
68
+ "@vitejs/plugin-react": "^6.0.2",
69
+ "eslint": "^9.39.4",
70
+ "eslint-plugin-react": "^7.37.5",
71
+ "eslint-plugin-react-hooks": "^5.2.0",
72
+ "globals": "^15.15.0",
68
73
  "jest": "^30.3.0",
69
74
  "jest-environment-jsdom": "^30.3.0",
70
75
  "react": "^19.2.0",
71
76
  "react-dom": "^19.2.0",
72
77
  "typescript": "^6.0.2",
73
- "vite": "^7.3.1",
74
- "vite-plugin-dts": "^4.5.4"
78
+ "typescript-eslint": "^8.61.0",
79
+ "vite": "^8.0.16",
80
+ "vite-plugin-dts": "^5.0.2"
75
81
  },
76
82
  "allowScripts": {
77
83
  "@swc/core@1.15.24": true,