@malaya_jeeva/rich-text-editor 1.0.12 → 1.0.13

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
@@ -26,7 +26,13 @@ export default function Page() {
26
26
  const [value, setValue] = useState('')
27
27
 
28
28
  return (
29
- <RichTextEditor value={value} onChange={setValue} />
29
+ <RichTextEditor
30
+ value={value}
31
+ onChange={setValue}
32
+ placeholder="Write something..."
33
+ height={400}
34
+ theme="light"
35
+ />
30
36
  )
31
37
  }
32
38
  ```
@@ -45,11 +51,14 @@ export default function Page() {
45
51
 
46
52
  ## ⚙️ Props
47
53
 
48
- | Prop | Type | Required | Description |
49
- | ---------- | ------------------------- | -------- | ---------------------------------------- |
50
- | `value` | `string` | No | Initial HTML content (read on first mount only) |
51
- | `onChange` | `(value: string) => void` | No | Fires with full `innerHTML` on every change |
52
- | `toolbar` | `ToolbarItem[]` | No | Controls which toolbar buttons are shown. If omitted, all buttons are shown |
54
+ | Prop | Type | Required | Default | Description |
55
+ | ------------- | ----------------------------- | -------- | -------------------- | --------------------------------------------------------------------------- |
56
+ | `value` | `string` | No | — | Initial HTML content (read on first mount only) |
57
+ | `onChange` | `(value: string) => void` | No | — | Fires with full `innerHTML` on every change |
58
+ | `toolbar` | `ToolbarItem[]` | No | all items | Controls which toolbar buttons are shown. If omitted, all buttons are shown |
59
+ | `placeholder` | `string` | No | `"Start typing..."` | Placeholder text shown when the editor is empty |
60
+ | `height` | `number \| string` | No | auto (max `350px`) | Editor content area height — `500`, `"400px"`, `"60vh"` etc. |
61
+ | `theme` | `"light" \| "dark" \| "auto"` | No | `"auto"` | Color theme. `"auto"` follows the OS `prefers-color-scheme` setting |
53
62
 
54
63
  ### Type export
55
64
 
@@ -59,6 +68,44 @@ import type { RichTextEditorProps, ToolbarItem } from '@malaya_jeeva/rich-text-e
59
68
 
60
69
  ---
61
70
 
71
+ ## 📐 Height
72
+
73
+ Control the height of the editor content area via the `height` prop. Accepts a number (pixels) or any valid CSS string.
74
+
75
+ ```tsx
76
+ <RichTextEditor height={500} /> // 500px fixed
77
+ <RichTextEditor height="60vh" /> // viewport-relative
78
+ <RichTextEditor height="400px" /> // explicit px string
79
+ <RichTextEditor /> // default — grows with content, caps at 350px
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 💬 Placeholder
85
+
86
+ Set the placeholder text shown when the editor is empty.
87
+
88
+ ```tsx
89
+ <RichTextEditor placeholder="Write something..." />
90
+ // omit it — defaults to "Start typing..."
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 🌗 Theme
96
+
97
+ Three options: `"light"`, `"dark"`, or `"auto"` (default).
98
+
99
+ ```tsx
100
+ <RichTextEditor theme="light" /> // always light
101
+ <RichTextEditor theme="dark" /> // always dark
102
+ <RichTextEditor /> // auto — follows OS prefers-color-scheme
103
+ ```
104
+
105
+ > When `theme="auto"` (or the prop is omitted) the editor automatically switches between light and dark based on the user's OS setting — no extra code needed.
106
+
107
+ ---
108
+
62
109
  ## 🛠️ Toolbar Customization
63
110
 
64
111
  Pass a `toolbar` array to control exactly which buttons appear. If the prop is omitted, **all buttons are shown** — fully backward compatible.
package/dist/index.d.mts CHANGED
@@ -6,8 +6,10 @@ type RichTextEditorProps = {
6
6
  onChange?: (value: string) => void;
7
7
  toolbar?: ToolbarItem[];
8
8
  theme?: "light" | "dark";
9
+ height?: number | string;
10
+ placeholder?: string;
9
11
  };
10
12
 
11
- declare function RichTextEditor({ value, onChange, toolbar, theme }: RichTextEditorProps): ReactElement;
13
+ declare function RichTextEditor({ value, onChange, toolbar, theme, height, placeholder }: RichTextEditorProps): ReactElement;
12
14
 
13
15
  export { type RichTextEditorProps, RichTextEditor as default };
package/dist/index.d.ts CHANGED
@@ -6,8 +6,10 @@ type RichTextEditorProps = {
6
6
  onChange?: (value: string) => void;
7
7
  toolbar?: ToolbarItem[];
8
8
  theme?: "light" | "dark";
9
+ height?: number | string;
10
+ placeholder?: string;
9
11
  };
10
12
 
11
- declare function RichTextEditor({ value, onChange, toolbar, theme }: RichTextEditorProps): ReactElement;
13
+ declare function RichTextEditor({ value, onChange, toolbar, theme, height, placeholder }: RichTextEditorProps): ReactElement;
12
14
 
13
15
  export { type RichTextEditorProps, RichTextEditor as default };
package/dist/index.js CHANGED
@@ -2,7 +2,21 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
6
20
  var __export = (target, all) => {
7
21
  for (var name in all)
8
22
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -164,7 +178,7 @@ var CSS = `
164
178
  .rte-linkbar{min-width:340px;}
165
179
  .rte-linkbar input{flex:1;height:26px;border:1px solid var(--rte-linkinput-border);border-radius:3px;padding:0 8px;font-size:13px;outline:none;font-family:var(--font-sans);background:var(--rte-linkinput-bg);color:var(--rte-linkinput);min-width:0;}
166
180
  .rte-area{position:relative;background:var(--rte-area-bg);}
167
- .rte-body{min-height:280px;outline:none;font-size:15px;line-height:1.75;color:var(--rte-body);caret-color:var(--rte-body);padding:18px 20px;}
181
+ .rte-body{height:200px;overflow-y: auto;outline:none;font-size:15px;line-height:1.75;color:var(--rte-body);caret-color:var(--rte-body);padding:18px 20px;}
168
182
  .rte-body h1{font-size:26px;font-weight:600;margin:0.6em 0 0.2em;}
169
183
  .rte-body h2{font-size:20px;font-weight:600;margin:0.6em 0 0.2em;}
170
184
  .rte-body h3{font-size:16px;font-weight:600;margin:0.6em 0 0.2em;}
@@ -208,7 +222,7 @@ var CSS = `
208
222
  .rte-ie-remove{flex:1;height:26px;background:#fdecea;color:#c0392b;border:1px solid #f5c6c2;border-radius:4px;font-size:11px;cursor:pointer;}
209
223
  .rte-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #1a5fb4;border-radius:2px;pointer-events:all;z-index:53;}
210
224
  .rte-body blockquote{background-color: rgb(245, 243, 255);
211
- border-left: 4px solid rgb(100, 80, 220);
225
+ border-left: 4px solid rgb(100, 80, 220);
212
226
  margin: 8px 0;
213
227
  padding: 10px 16px;
214
228
  font-style: italic;
@@ -1188,13 +1202,14 @@ function ImageEditor({ img, containerRef, onClose, onDelete, onChange }) {
1188
1202
 
1189
1203
  // src/RichTextEditor.tsx
1190
1204
  var import_jsx_runtime7 = require("react/jsx-runtime");
1191
- function RichTextEditor({ value, onChange, toolbar, theme }) {
1205
+ function RichTextEditor({ value, onChange, toolbar, theme, height, placeholder = "Start typing..." }) {
1192
1206
  var _a;
1193
1207
  const editorRef = (0, import_react5.useRef)(null);
1194
1208
  const editorAreaRef = (0, import_react5.useRef)(null);
1195
1209
  const savedRangeRef = (0, import_react5.useRef)(null);
1196
1210
  const dragStartCell = (0, import_react5.useRef)(null);
1197
1211
  const isDragging = (0, import_react5.useRef)(false);
1212
+ const isMounting = (0, import_react5.useRef)(true);
1198
1213
  const [isCode, setIsCode] = (0, import_react5.useState)(false);
1199
1214
  const [codeVal, setCodeVal] = (0, import_react5.useState)("");
1200
1215
  const [fmt, setFmt] = (0, import_react5.useState)({ block: "p" });
@@ -1281,7 +1296,9 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1281
1296
  else setLinkInfoFP(null);
1282
1297
  const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
1283
1298
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
1284
- onChange == null ? void 0 : onChange(sanitizeScriptTags((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : ""));
1299
+ if (!isMounting.current) {
1300
+ onChange == null ? void 0 : onChange(sanitizeScriptTags((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : ""));
1301
+ }
1285
1302
  }, [onChange, calcFloat, linkBar, sanitizeScriptTags]);
1286
1303
  const fixListInParagraph = (0, import_react5.useCallback)(() => {
1287
1304
  var _a2;
@@ -1719,8 +1736,9 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1719
1736
  (0, import_react5.useEffect)(() => {
1720
1737
  if (editorRef.current) {
1721
1738
  document.execCommand("defaultParagraphSeparator", false, "p");
1722
- editorRef.current.innerHTML = sanitizeScriptTags(value != null ? value : "<p>Start writing here...</p>");
1739
+ editorRef.current.innerHTML = sanitizeScriptTags(value != null ? value : "");
1723
1740
  refresh();
1741
+ isMounting.current = false;
1724
1742
  }
1725
1743
  }, []);
1726
1744
  const canMerge = selCells.length >= 2;
@@ -1729,7 +1747,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1729
1747
  const show = (key) => !toolbar || toolbar.includes(key);
1730
1748
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { padding: "1rem 0" }, children: [
1731
1749
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: CSS }),
1732
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `customeditor${theme ? ` rte-${theme}` : ""}`, style: { border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
1750
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `customeditor${theme ? ` rte-${theme}` : ""}`, style: __spreadValues({ border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, height != null && { maxHeight: "none" }), children: [
1733
1751
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rte-toolbar", children: [
1734
1752
  !isCode && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1735
1753
  show("bold") && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
@@ -1851,7 +1869,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1851
1869
  value: codeVal,
1852
1870
  onChange: (e) => setCodeVal(e.target.value),
1853
1871
  spellCheck: false,
1854
- style: { display: isCode ? "block" : "none" }
1872
+ style: __spreadValues({ display: isCode ? "block" : "none" }, height != null && { height, minHeight: 0 })
1855
1873
  }
1856
1874
  ),
1857
1875
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -1861,7 +1879,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1861
1879
  className: "rte-body",
1862
1880
  contentEditable: true,
1863
1881
  suppressContentEditableWarning: true,
1864
- "data-ph": "Start typing...",
1882
+ "data-ph": placeholder,
1865
1883
  onMouseDown: handleEditorMouseDown,
1866
1884
  onMouseMove: handleEditorMouseMove,
1867
1885
  onMouseUp: () => {
@@ -1882,7 +1900,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1882
1900
  onDragOver: (e) => {
1883
1901
  if (e.dataTransfer.types.includes("Files")) e.preventDefault();
1884
1902
  },
1885
- style: { display: isCode ? "none" : "block" }
1903
+ style: __spreadValues({ display: isCode ? "none" : "block" }, height != null && { height, minHeight: 0 })
1886
1904
  }
1887
1905
  ),
1888
1906
  !isCode && selectedImg && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
package/dist/index.mjs CHANGED
@@ -1,3 +1,20 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
1
18
  // src/RichTextEditor.tsx
2
19
  import {
3
20
  useState as useState5,
@@ -143,7 +160,7 @@ var CSS = `
143
160
  .rte-linkbar{min-width:340px;}
144
161
  .rte-linkbar input{flex:1;height:26px;border:1px solid var(--rte-linkinput-border);border-radius:3px;padding:0 8px;font-size:13px;outline:none;font-family:var(--font-sans);background:var(--rte-linkinput-bg);color:var(--rte-linkinput);min-width:0;}
145
162
  .rte-area{position:relative;background:var(--rte-area-bg);}
146
- .rte-body{min-height:280px;outline:none;font-size:15px;line-height:1.75;color:var(--rte-body);caret-color:var(--rte-body);padding:18px 20px;}
163
+ .rte-body{height:200px;overflow-y: auto;outline:none;font-size:15px;line-height:1.75;color:var(--rte-body);caret-color:var(--rte-body);padding:18px 20px;}
147
164
  .rte-body h1{font-size:26px;font-weight:600;margin:0.6em 0 0.2em;}
148
165
  .rte-body h2{font-size:20px;font-weight:600;margin:0.6em 0 0.2em;}
149
166
  .rte-body h3{font-size:16px;font-weight:600;margin:0.6em 0 0.2em;}
@@ -187,7 +204,7 @@ var CSS = `
187
204
  .rte-ie-remove{flex:1;height:26px;background:#fdecea;color:#c0392b;border:1px solid #f5c6c2;border-radius:4px;font-size:11px;cursor:pointer;}
188
205
  .rte-handle{position:absolute;width:10px;height:10px;background:#fff;border:2px solid #1a5fb4;border-radius:2px;pointer-events:all;z-index:53;}
189
206
  .rte-body blockquote{background-color: rgb(245, 243, 255);
190
- border-left: 4px solid rgb(100, 80, 220);
207
+ border-left: 4px solid rgb(100, 80, 220);
191
208
  margin: 8px 0;
192
209
  padding: 10px 16px;
193
210
  font-style: italic;
@@ -1167,13 +1184,14 @@ function ImageEditor({ img, containerRef, onClose, onDelete, onChange }) {
1167
1184
 
1168
1185
  // src/RichTextEditor.tsx
1169
1186
  import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1170
- function RichTextEditor({ value, onChange, toolbar, theme }) {
1187
+ function RichTextEditor({ value, onChange, toolbar, theme, height, placeholder = "Start typing..." }) {
1171
1188
  var _a;
1172
1189
  const editorRef = useRef4(null);
1173
1190
  const editorAreaRef = useRef4(null);
1174
1191
  const savedRangeRef = useRef4(null);
1175
1192
  const dragStartCell = useRef4(null);
1176
1193
  const isDragging = useRef4(false);
1194
+ const isMounting = useRef4(true);
1177
1195
  const [isCode, setIsCode] = useState5(false);
1178
1196
  const [codeVal, setCodeVal] = useState5("");
1179
1197
  const [fmt, setFmt] = useState5({ block: "p" });
@@ -1260,7 +1278,9 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1260
1278
  else setLinkInfoFP(null);
1261
1279
  const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
1262
1280
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
1263
- onChange == null ? void 0 : onChange(sanitizeScriptTags((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : ""));
1281
+ if (!isMounting.current) {
1282
+ onChange == null ? void 0 : onChange(sanitizeScriptTags((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : ""));
1283
+ }
1264
1284
  }, [onChange, calcFloat, linkBar, sanitizeScriptTags]);
1265
1285
  const fixListInParagraph = useCallback3(() => {
1266
1286
  var _a2;
@@ -1698,8 +1718,9 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1698
1718
  useEffect3(() => {
1699
1719
  if (editorRef.current) {
1700
1720
  document.execCommand("defaultParagraphSeparator", false, "p");
1701
- editorRef.current.innerHTML = sanitizeScriptTags(value != null ? value : "<p>Start writing here...</p>");
1721
+ editorRef.current.innerHTML = sanitizeScriptTags(value != null ? value : "");
1702
1722
  refresh();
1723
+ isMounting.current = false;
1703
1724
  }
1704
1725
  }, []);
1705
1726
  const canMerge = selCells.length >= 2;
@@ -1708,7 +1729,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1708
1729
  const show = (key) => !toolbar || toolbar.includes(key);
1709
1730
  return /* @__PURE__ */ jsxs6("div", { style: { padding: "1rem 0" }, children: [
1710
1731
  /* @__PURE__ */ jsx7("style", { children: CSS }),
1711
- /* @__PURE__ */ jsxs6("div", { className: `customeditor${theme ? ` rte-${theme}` : ""}`, style: { border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
1732
+ /* @__PURE__ */ jsxs6("div", { className: `customeditor${theme ? ` rte-${theme}` : ""}`, style: __spreadValues({ border: "1px solid #d8d8d8", borderRadius: 4, boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, height != null && { maxHeight: "none" }), children: [
1712
1733
  /* @__PURE__ */ jsxs6("div", { className: "rte-toolbar", children: [
1713
1734
  !isCode && /* @__PURE__ */ jsxs6(Fragment4, { children: [
1714
1735
  show("bold") && /* @__PURE__ */ jsx7(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
@@ -1830,7 +1851,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1830
1851
  value: codeVal,
1831
1852
  onChange: (e) => setCodeVal(e.target.value),
1832
1853
  spellCheck: false,
1833
- style: { display: isCode ? "block" : "none" }
1854
+ style: __spreadValues({ display: isCode ? "block" : "none" }, height != null && { height, minHeight: 0 })
1834
1855
  }
1835
1856
  ),
1836
1857
  /* @__PURE__ */ jsx7(
@@ -1840,7 +1861,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1840
1861
  className: "rte-body",
1841
1862
  contentEditable: true,
1842
1863
  suppressContentEditableWarning: true,
1843
- "data-ph": "Start typing...",
1864
+ "data-ph": placeholder,
1844
1865
  onMouseDown: handleEditorMouseDown,
1845
1866
  onMouseMove: handleEditorMouseMove,
1846
1867
  onMouseUp: () => {
@@ -1861,7 +1882,7 @@ function RichTextEditor({ value, onChange, toolbar, theme }) {
1861
1882
  onDragOver: (e) => {
1862
1883
  if (e.dataTransfer.types.includes("Files")) e.preventDefault();
1863
1884
  },
1864
- style: { display: isCode ? "none" : "block" }
1885
+ style: __spreadValues({ display: isCode ? "none" : "block" }, height != null && { height, minHeight: 0 })
1865
1886
  }
1866
1887
  ),
1867
1888
  !isCode && selectedImg && /* @__PURE__ */ jsx7(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malaya_jeeva/rich-text-editor",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Custom React Rich Text Editor",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",