@malaya_jeeva/rich-text-editor 1.0.0 → 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/dist/index.mjs CHANGED
@@ -1,27 +1,27 @@
1
- // src/RichTextEditor.jsx
2
- import { useState, useRef, useCallback, useEffect } from "react";
1
+ // src/RichTextEditor.tsx
2
+ import {
3
+ useState,
4
+ useRef,
5
+ useCallback,
6
+ useEffect
7
+ } from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
9
  var CSS = `
4
10
  * { box-sizing: border-box; }
5
-
6
- /* \u2500\u2500 Toolbar \u2500\u2500 */
7
11
  .rte-toolbar {
8
12
  display: flex; flex-wrap: wrap; align-items: center; gap: 1px;
9
- padding: 4px 8px;
10
- background: #f8f8f8;
11
- border-bottom: 1px solid #e0e0e0;
13
+ padding: 4px 8px; background: #f8f8f8; border-bottom: 1px solid #e0e0e0;
12
14
  }
13
15
  @media (prefers-color-scheme: dark) {
14
16
  .rte-toolbar { background: #1e1e1e; border-color: #333; }
15
17
  }
16
-
17
18
  .rte-btn {
18
19
  background: transparent; border: none; border-radius: 3px;
19
20
  cursor: pointer; height: 30px; min-width: 30px; padding: 0 6px;
20
21
  color: #444; font-size: 13px; font-weight: 500;
21
22
  font-family: var(--font-sans); display: inline-flex; align-items: center;
22
23
  justify-content: center; user-select: none; white-space: nowrap;
23
- transition: background 0.1s;
24
- flex-shrink: 0;
24
+ transition: background 0.1s; flex-shrink: 0;
25
25
  }
26
26
  .rte-btn:hover { background: #e8e8e8; }
27
27
  .rte-btn.active { background: #d0e4ff; color: #1a5fb4; }
@@ -30,47 +30,25 @@ var CSS = `
30
30
  .rte-btn:hover { background: #2e2e2e; }
31
31
  .rte-btn.active { background: #1a3a5c; color: #90c4ff; }
32
32
  }
33
-
34
- /* thin vertical separator */
35
- .rte-sep {
36
- width: 1px; height: 20px; background: #d8d8d8;
37
- margin: 0 4px; flex-shrink: 0;
38
- }
39
- @media (prefers-color-scheme: dark) {
40
- .rte-sep { background: #3a3a3a; }
41
- }
42
-
43
- /* Block format select */
33
+ .rte-sep { width: 1px; height: 20px; background: #d8d8d8; margin: 0 4px; flex-shrink: 0; }
34
+ @media (prefers-color-scheme: dark) { .rte-sep { background: #3a3a3a; } }
44
35
  .rte-select {
45
36
  height: 28px; border: 1px solid #d8d8d8; border-radius: 3px;
46
- background: #fff; color: #333;
47
- font-size: 12px; padding: 0 22px 0 7px; cursor: pointer;
48
- outline: none; font-family: var(--font-sans);
37
+ background: #fff; color: #333; font-size: 12px; padding: 0 22px 0 7px;
38
+ cursor: pointer; outline: none; font-family: var(--font-sans);
49
39
  appearance: none; -webkit-appearance: none;
50
40
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");
51
41
  background-repeat: no-repeat; background-position: right 6px center;
52
42
  }
53
- @media (prefers-color-scheme: dark) {
54
- .rte-select { background-color: #2a2a2a; border-color: #444; color: #ccc; }
55
- }
56
-
57
- /* color swatch under A */
43
+ @media (prefers-color-scheme: dark) { .rte-select { background-color: #2a2a2a; border-color: #444; color: #ccc; } }
58
44
  .rte-swatch { width: 14px; height: 3px; border-radius: 1px; margin-top: 2px; }
59
-
60
- /* \u2500\u2500 Editor body \u2500\u2500 */
61
- .rte-wrap {
62
- background: #fff;
63
- }
64
- @media (prefers-color-scheme: dark) {
65
- .rte-wrap { background: #141414; }
66
- }
45
+ .rte-wrap { background: #fff; }
46
+ @media (prefers-color-scheme: dark) { .rte-wrap { background: #141414; } }
67
47
  .rte-body {
68
48
  min-height: 280px; outline: none; font-size: 15px; line-height: 1.75;
69
49
  color: #222; caret-color: #222; padding: 18px 20px;
70
50
  }
71
- @media (prefers-color-scheme: dark) {
72
- .rte-body { color: #ddd; caret-color: #ddd; }
73
- }
51
+ @media (prefers-color-scheme: dark) { .rte-body { color: #ddd; caret-color: #ddd; } }
74
52
  .rte-body h1 { font-size: 26px; font-weight: 600; margin: 0.6em 0 0.2em; }
75
53
  .rte-body h2 { font-size: 20px; font-weight: 600; margin: 0.6em 0 0.2em; }
76
54
  .rte-body h3 { font-size: 16px; font-weight: 600; margin: 0.6em 0 0.2em; }
@@ -87,51 +65,110 @@ var CSS = `
87
65
  .rte-body pre { background: #f4f4f4; border: 1px solid #e0e0e0; border-radius: 4px; padding: 12px 14px; margin: 0.6em 0; overflow-x: auto; }
88
66
  .rte-body code { font-family: var(--font-mono); font-size: 13px; }
89
67
  .rte-body:empty:before { content: attr(data-ph); color: #aaa; pointer-events: none; }
90
-
91
68
  .rte-code {
92
- min-height: 280px; width: 100%; background: #1e1e2e;
93
- color: #cdd6f4; font-family: var(--font-mono); font-size: 13px;
94
- line-height: 1.6; padding: 18px 20px; border: none; outline: none;
95
- resize: vertical;
69
+ min-height: 280px; width: 100%; background: #1e1e2e; color: #cdd6f4;
70
+ font-family: var(--font-mono); font-size: 13px; line-height: 1.6;
71
+ padding: 18px 20px; border: none; outline: none; resize: vertical;
96
72
  }
97
73
  .rte-footer {
98
74
  font-size: 11px; color: #999; padding: 4px 20px 5px;
99
75
  border-top: 1px solid #e8e8e8; background: #fafafa;
100
76
  }
101
- @media (prefers-color-scheme: dark) {
102
- .rte-footer { border-color: #2a2a2a; background: #1a1a1a; color: #555; }
103
- }
77
+ @media (prefers-color-scheme: dark) { .rte-footer { border-color: #2a2a2a; background: #1a1a1a; color: #555; } }
104
78
  `;
105
- var Btn = ({ onClick, title, active, children, style }) => /* @__PURE__ */ React.createElement(
106
- "button",
107
- {
108
- className: `rte-btn${active ? " active" : ""}`,
109
- style,
110
- onMouseDown: (e) => {
111
- e.preventDefault();
112
- onClick(e);
79
+ function Btn({ onClick, title, active, children, style }) {
80
+ return /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ className: `rte-btn${active ? " active" : ""}`,
84
+ style,
85
+ onMouseDown: (e) => {
86
+ e.preventDefault();
87
+ onClick(e);
88
+ },
89
+ title,
90
+ children
91
+ }
92
+ );
93
+ }
94
+ function Sep() {
95
+ return /* @__PURE__ */ jsx("div", { className: "rte-sep" });
96
+ }
97
+ function IcoUL() {
98
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
99
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "4", r: "1.3", fill: "currentColor" }),
100
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "8", r: "1.3", fill: "currentColor" }),
101
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "12", r: "1.3", fill: "currentColor" }),
102
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
103
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
104
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
105
+ ] });
106
+ }
107
+ function IcoOL() {
108
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
109
+ /* @__PURE__ */ jsx("text", { x: "0", y: "5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "1." }),
110
+ /* @__PURE__ */ jsx("text", { x: "0", y: "9.5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "2." }),
111
+ /* @__PURE__ */ jsx("text", { x: "0", y: "14", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace", children: "3." }),
112
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
113
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }),
114
+ /* @__PURE__ */ jsx("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" })
115
+ ] });
116
+ }
117
+ function IcoIndent() {
118
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
119
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
120
+ /* @__PURE__ */ jsx("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
121
+ /* @__PURE__ */ jsx("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
122
+ /* @__PURE__ */ jsx("path", { d: "M1 6.5L3.5 8.25L1 10V6.5Z", fill: "currentColor" })
123
+ ] });
124
+ }
125
+ function IcoOutdent() {
126
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
127
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }),
128
+ /* @__PURE__ */ jsx("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
129
+ /* @__PURE__ */ jsx("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }),
130
+ /* @__PURE__ */ jsx("path", { d: "M3.5 6.5L1 8.25L3.5 10V6.5Z", fill: "currentColor" })
131
+ ] });
132
+ }
133
+ function IcoLink() {
134
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
135
+ /* @__PURE__ */ jsx("path", { d: "M6 9C6.4 9.6 7.1 10 7.8 10H9.8C10.6 10 11.4 9.6 11.9 9C12.4 8.4 12.6 7.6 12.6 6.8C12.6 6 12.4 5.3 11.9 4.7C11.4 4.1 10.6 3.8 9.8 3.8H8.6", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }),
136
+ /* @__PURE__ */ jsx("path", { d: "M9 6C8.6 5.4 7.9 5 7.2 5H5.2C4.4 5 3.6 5.4 3.1 6C2.6 6.6 2.4 7.4 2.4 8.2C2.4 9 2.6 9.7 3.1 10.3C3.6 10.9 4.4 11.2 5.2 11.2H6.4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })
137
+ ] });
138
+ }
139
+ function IcoCode() {
140
+ return /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [
141
+ /* @__PURE__ */ jsx("path", { d: "M5 4.5L1.5 7.5L5 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
142
+ /* @__PURE__ */ jsx("path", { d: "M10 4.5L13.5 7.5L10 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }),
143
+ /* @__PURE__ */ jsx("path", { d: "M8.5 2L6.5 13", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
144
+ ] });
145
+ }
146
+ function IcoCopy() {
147
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: [
148
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "3", width: "8.5", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
149
+ /* @__PURE__ */ jsx("path", { d: "M4 1H12.5C13.1 1 13.5 1.4 13.5 2V10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
150
+ ] });
151
+ }
152
+ function AlignIco({ t }) {
153
+ const w = { left: [13, 9, 11], center: [9, 7, 11], right: [13, 9, 11], justify: [13, 13, 13] };
154
+ return /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
155
+ "rect",
156
+ {
157
+ x: t === "right" ? 15 - w[t][i] - 1 : 1,
158
+ y: [2, 6.5, 11][i],
159
+ width: w[t][i],
160
+ height: "1.6",
161
+ rx: ".8",
162
+ fill: "currentColor"
113
163
  },
114
- title
115
- },
116
- children
117
- );
118
- var Sep = () => /* @__PURE__ */ React.createElement("div", { className: "rte-sep" });
119
- var IcoUL = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: "2", cy: "4", r: "1.3", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "2", cy: "8", r: "1.3", fill: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "2", cy: "12", r: "1.3", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }));
120
- var IcoOL = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("text", { x: "0", y: "5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace" }, "1."), /* @__PURE__ */ React.createElement("text", { x: "0", y: "9.5", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace" }, "2."), /* @__PURE__ */ React.createElement("text", { x: "0", y: "14", fontSize: "5.5", fill: "currentColor", fontFamily: "monospace" }, "3."), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "3.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "7.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "5.5", y: "11.2", width: "8.5", height: "1.6", rx: ".8", fill: "currentColor" }));
121
- var IcoIndent = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("path", { d: "M1 6.5L3.5 8.25L1 10V6.5Z", fill: "currentColor" }));
122
- var IcoOutdent = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("rect", { x: "1", y: "2", width: "13", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "4.5", y: "6", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("rect", { x: "4.5", y: "10", width: "9.5", height: "1.6", rx: ".8", fill: "currentColor" }), /* @__PURE__ */ React.createElement("path", { d: "M3.5 6.5L1 8.25L3.5 10V6.5Z", fill: "currentColor" }));
123
- var IcoLink = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("path", { d: "M6 9C6.4 9.6 7.1 10 7.8 10H9.8C10.6 10 11.4 9.6 11.9 9C12.4 8.4 12.6 7.6 12.6 6.8C12.6 6 12.4 5.3 11.9 4.7C11.4 4.1 10.6 3.8 9.8 3.8H8.6", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }), /* @__PURE__ */ React.createElement("path", { d: "M9 6C8.6 5.4 7.9 5 7.2 5H5.2C4.4 5 3.6 5.4 3.1 6C2.6 6.6 2.4 7.4 2.4 8.2C2.4 9 2.6 9.7 3.1 10.3C3.6 10.9 4.4 11.2 5.2 11.2H6.4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" }));
124
- var IcoCode = () => /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, /* @__PURE__ */ React.createElement("path", { d: "M5 4.5L1.5 7.5L5 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }), /* @__PURE__ */ React.createElement("path", { d: "M10 4.5L13.5 7.5L10 10.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }), /* @__PURE__ */ React.createElement("path", { d: "M8.5 2L6.5 13", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }));
125
- var IcoCopy = () => /* @__PURE__ */ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none" }, /* @__PURE__ */ React.createElement("rect", { x: "1", y: "3", width: "8.5", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }), /* @__PURE__ */ React.createElement("path", { d: "M4 1H12.5C13.1 1 13.5 1.4 13.5 2V10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }));
126
- var AlignIco = ({ t }) => {
127
- const w = { left: [13, 9, 11], center: [9, 7, 11], right: [13, 9, 11], justify: [13, 13, 13] }[t];
128
- const x = { left: [1, 1, 1], center: [3, 4, 2], right: [1, 1, 1], justify: [1, 1, 1] }[t];
129
- const xr = { left: [0, 0, 0], center: [0, 0, 0], right: [1, 1, 1], justify: [0, 0, 0] }[t];
130
- return /* @__PURE__ */ React.createElement("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none" }, [0, 1, 2].map((i) => /* @__PURE__ */ React.createElement("rect", { key: i, x: t === "right" ? 15 - w[i] - 1 : x[i], y: [2, 6.5, 11][i], width: w[i], height: "1.6", rx: ".8", fill: "currentColor" })));
131
- };
132
- function RichTextEditor() {
164
+ i
165
+ )) });
166
+ }
167
+ function RichTextEditor({ value, onChange }) {
168
+ var _a;
133
169
  const editorRef = useRef(null);
134
170
  const colorRef = useRef(null);
171
+ const savedRangeRef = useRef(null);
135
172
  const [isCode, setIsCode] = useState(false);
136
173
  const [codeVal, setCodeVal] = useState("");
137
174
  const [fmt, setFmt] = useState({ block: "p" });
@@ -139,21 +176,20 @@ function RichTextEditor() {
139
176
  const [words, setWords] = useState(0);
140
177
  const [linkBar, setLinkBar] = useState(false);
141
178
  const [linkUrl, setLinkUrl] = useState("https://");
142
- const savedRangeRef = useRef(null);
143
179
  const exec = useCallback((cmd, val = null) => {
144
- var _a;
145
- (_a = editorRef.current) == null ? void 0 : _a.focus();
146
- document.execCommand(cmd, false, val);
180
+ var _a2;
181
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
182
+ document.execCommand(cmd, false, val != null ? val : void 0);
147
183
  refresh();
148
184
  }, []);
149
185
  const refresh = useCallback(() => {
150
- var _a;
186
+ var _a2, _b, _c, _d;
151
187
  const raw = document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g, "");
152
188
  const block = ["h1", "h2", "h3"].includes(raw) ? raw : "p";
153
189
  const c = document.queryCommandValue("foreColor");
154
190
  if (c && c !== "false") {
155
191
  const m = c.match(/\d+/g);
156
- if (m) setColor("#" + m.slice(0, 3).map((n) => (+n).toString(16).padStart(2, "0")).join(""));
192
+ if (m) setColor("#" + m.slice(0, 3).map((n) => parseInt(n).toString(16).padStart(2, "0")).join(""));
157
193
  }
158
194
  setFmt({
159
195
  bold: document.queryCommandState("bold"),
@@ -167,14 +203,15 @@ function RichTextEditor() {
167
203
  aJ: document.queryCommandState("justifyFull"),
168
204
  block
169
205
  });
170
- const txt = ((_a = editorRef.current) == null ? void 0 : _a.innerText) || "";
206
+ const txt = (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerText) != null ? _b : "";
171
207
  setWords(txt.trim() ? txt.trim().split(/\s+/).length : 0);
172
- }, []);
208
+ onChange == null ? void 0 : onChange((_d = (_c = editorRef.current) == null ? void 0 : _c.innerHTML) != null ? _d : "");
209
+ }, [onChange]);
173
210
  const handleKeyDown = useCallback((e) => {
174
- var _a, _b;
211
+ var _a2, _b;
175
212
  if (e.key === "Tab") {
176
213
  e.preventDefault();
177
- const node = (_a = window.getSelection()) == null ? void 0 : _a.anchorNode;
214
+ const node = (_a2 = window.getSelection()) == null ? void 0 : _a2.anchorNode;
178
215
  const li = (node == null ? void 0 : node.nodeType) === 1 ? node.closest("li") : (_b = node == null ? void 0 : node.parentElement) == null ? void 0 : _b.closest("li");
179
216
  if (li) exec(e.shiftKey ? "outdent" : "indent");
180
217
  else document.execCommand("insertText", false, "\xA0\xA0\xA0\xA0");
@@ -195,8 +232,8 @@ function RichTextEditor() {
195
232
  }
196
233
  }, [exec]);
197
234
  const toCode = () => {
198
- var _a;
199
- setCodeVal(((_a = editorRef.current) == null ? void 0 : _a.innerHTML) || "");
235
+ var _a2, _b;
236
+ setCodeVal((_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "");
200
237
  setIsCode(true);
201
238
  };
202
239
  const toVisual = () => {
@@ -207,25 +244,25 @@ function RichTextEditor() {
207
244
  const openLinkBar = () => {
208
245
  const sel = window.getSelection();
209
246
  savedRangeRef.current = (sel == null ? void 0 : sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
210
- setLinkUrl((sel == null ? void 0 : sel.toString()) ? "https://" : "https://");
247
+ setLinkUrl("https://");
211
248
  setLinkBar(true);
212
249
  };
213
250
  const applyLink = () => {
214
- var _a, _b, _c;
251
+ var _a2, _b;
215
252
  if (!linkUrl) return;
216
- (_a = editorRef.current) == null ? void 0 : _a.focus();
253
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
217
254
  const sel = window.getSelection();
218
- if (savedRangeRef.current) {
255
+ if (savedRangeRef.current && sel) {
219
256
  sel.removeAllRanges();
220
257
  sel.addRange(savedRangeRef.current);
221
258
  }
222
259
  if ((_b = savedRangeRef.current) == null ? void 0 : _b.toString()) {
223
260
  document.execCommand("createLink", false, linkUrl);
224
- } else {
261
+ } else if (savedRangeRef.current) {
225
262
  const a = document.createElement("a");
226
263
  a.href = linkUrl;
227
264
  a.textContent = linkUrl;
228
- (_c = savedRangeRef.current) == null ? void 0 : _c.insertNode(a);
265
+ savedRangeRef.current.insertNode(a);
229
266
  }
230
267
  setLinkBar(false);
231
268
  setLinkUrl("https://");
@@ -250,112 +287,171 @@ function RichTextEditor() {
250
287
  refresh();
251
288
  };
252
289
  const copyHtml = () => {
253
- var _a;
254
- return navigator.clipboard.writeText(isCode ? codeVal : ((_a = editorRef.current) == null ? void 0 : _a.innerHTML) || "").catch(() => {
290
+ var _a2, _b;
291
+ navigator.clipboard.writeText(isCode ? codeVal : (_b = (_a2 = editorRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b : "").catch(() => {
255
292
  });
256
293
  };
257
294
  useEffect(() => {
258
295
  if (editorRef.current) {
259
- editorRef.current.innerHTML = "<p>Start writing here...</p>";
296
+ editorRef.current.innerHTML = value != null ? value : "<p>Start writing here...</p>";
260
297
  refresh();
261
298
  }
262
299
  }, []);
263
- return /* @__PURE__ */ React.createElement("div", { style: { padding: "1rem 0" } }, /* @__PURE__ */ React.createElement("style", null, CSS), /* @__PURE__ */ React.createElement("div", { style: { border: "1px solid #d8d8d8", borderRadius: 4, overflow: "hidden", boxShadow: "0 1px 3px rgba(0,0,0,0.06)" } }, /* @__PURE__ */ React.createElement("div", { className: "rte-toolbar" }, !isCode && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" } }, "B"), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("italic"), title: "Italic (Ctrl+I)", active: fmt.italic, style: { fontStyle: "italic", fontFamily: "Georgia,serif" } }, "I"), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" } }, "U"), /* @__PURE__ */ React.createElement(Btn, { onClick: () => {
264
- var _a;
265
- return (_a = colorRef.current) == null ? void 0 : _a.click();
266
- }, title: "Text color", style: { flexDirection: "column", gap: 1, padding: "3px 6px" } }, /* @__PURE__ */ React.createElement("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" } }, "A"), /* @__PURE__ */ React.createElement("span", { className: "rte-swatch", style: { background: color } })), /* @__PURE__ */ React.createElement(
267
- "input",
268
- {
269
- ref: colorRef,
270
- type: "color",
271
- value: color,
272
- onChange: (e) => {
273
- setColor(e.target.value);
274
- exec("foreColor", e.target.value);
275
- },
276
- style: { position: "absolute", opacity: 0, width: 0, height: 0, pointerEvents: "none" }
277
- }
278
- ), /* @__PURE__ */ React.createElement(Sep, null), /* @__PURE__ */ React.createElement(
279
- "select",
280
- {
281
- className: "rte-select",
282
- value: fmt.block || "p",
283
- onMouseDown: () => {
284
- var _a;
285
- return (_a = editorRef.current) == null ? void 0 : _a.focus();
286
- },
287
- onChange: (e) => {
288
- var _a;
289
- (_a = editorRef.current) == null ? void 0 : _a.focus();
290
- document.execCommand("formatBlock", false, e.target.value);
291
- refresh();
292
- }
293
- },
294
- /* @__PURE__ */ React.createElement("option", { value: "p" }, "Paragraph"),
295
- /* @__PURE__ */ React.createElement("option", { value: "h1" }, "Heading 1"),
296
- /* @__PURE__ */ React.createElement("option", { value: "h2" }, "Heading 2"),
297
- /* @__PURE__ */ React.createElement("option", { value: "h3" }, "Heading 3")
298
- ), /* @__PURE__ */ React.createElement(Sep, null), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("justifyLeft"), title: "Align left", active: fmt.aL }, /* @__PURE__ */ React.createElement(AlignIco, { t: "left" })), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("justifyCenter"), title: "Align center", active: fmt.aC }, /* @__PURE__ */ React.createElement(AlignIco, { t: "center" })), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("justifyRight"), title: "Align right", active: fmt.aR }, /* @__PURE__ */ React.createElement(AlignIco, { t: "right" })), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("justifyFull"), title: "Justify", active: fmt.aJ }, /* @__PURE__ */ React.createElement(AlignIco, { t: "justify" })), /* @__PURE__ */ React.createElement(Sep, null), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("insertUnorderedList"), title: "Bullet list", active: fmt.ul }, /* @__PURE__ */ React.createElement(IcoUL, null)), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("insertOrderedList"), title: "Numbered list", active: fmt.ol }, /* @__PURE__ */ React.createElement(IcoOL, null)), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("indent"), title: "Indent (Tab)" }, /* @__PURE__ */ React.createElement(IcoIndent, null)), /* @__PURE__ */ React.createElement(Btn, { onClick: () => exec("outdent"), title: "Outdent (Shift+Tab)" }, /* @__PURE__ */ React.createElement(IcoOutdent, null)), /* @__PURE__ */ React.createElement(Sep, null), /* @__PURE__ */ React.createElement(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar }, /* @__PURE__ */ React.createElement(IcoLink, null)), /* @__PURE__ */ React.createElement(Btn, { onClick: insertCodeBlock, title: "Code block" }, /* @__PURE__ */ React.createElement(IcoCode, null)), /* @__PURE__ */ React.createElement(Sep, null)), /* @__PURE__ */ React.createElement(
299
- Btn,
300
- {
301
- onClick: isCode ? toVisual : toCode,
302
- title: "Toggle HTML source",
303
- active: isCode,
304
- style: { fontSize: 12, fontWeight: 600, letterSpacing: "0.03em", padding: "0 8px" }
305
- },
306
- isCode ? "Visual" : "HTML"
307
- ), /* @__PURE__ */ React.createElement(Btn, { onClick: copyHtml, title: "Copy HTML" }, /* @__PURE__ */ React.createElement(IcoCopy, null)), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "#aaa" } }, words, " ", words === 1 ? "word" : "words")), linkBar && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, padding: "6px 10px", background: "#f0f4ff", borderBottom: "1px solid #c8d8f8" } }, /* @__PURE__ */ React.createElement(IcoLink, null), /* @__PURE__ */ React.createElement(
308
- "input",
309
- {
310
- autoFocus: true,
311
- type: "text",
312
- value: linkUrl,
313
- onChange: (e) => setLinkUrl(e.target.value),
314
- onKeyDown: (e) => {
315
- if (e.key === "Enter") applyLink();
316
- if (e.key === "Escape") setLinkBar(false);
317
- },
318
- placeholder: "https://example.com",
319
- style: { flex: 1, height: 26, border: "1px solid #b0c4f0", borderRadius: 3, padding: "0 8px", fontSize: 13, outline: "none", fontFamily: "var(--font-sans)" }
320
- }
321
- ), /* @__PURE__ */ React.createElement(
322
- "button",
323
- {
324
- onClick: applyLink,
325
- style: { height: 26, padding: "0 12px", background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 3, fontSize: 12, fontWeight: 600, cursor: "pointer" }
326
- },
327
- "Apply"
328
- ), /* @__PURE__ */ React.createElement(
329
- "button",
330
- {
331
- onClick: () => setLinkBar(false),
332
- style: { height: 26, padding: "0 10px", background: "transparent", color: "#666", border: "1px solid #ccc", borderRadius: 3, fontSize: 12, cursor: "pointer" }
333
- },
334
- "Cancel"
335
- )), /* @__PURE__ */ React.createElement("div", { className: "rte-wrap" }, /* @__PURE__ */ React.createElement(
336
- "textarea",
337
- {
338
- className: "rte-code",
339
- value: codeVal,
340
- onChange: (e) => setCodeVal(e.target.value),
341
- spellCheck: false,
342
- style: { display: isCode ? "block" : "none" }
343
- }
344
- ), /* @__PURE__ */ React.createElement(
345
- "div",
346
- {
347
- ref: editorRef,
348
- className: "rte-body",
349
- contentEditable: true,
350
- suppressContentEditableWarning: true,
351
- "data-ph": "Start typing...",
352
- onKeyDown: handleKeyDown,
353
- onKeyUp: refresh,
354
- onMouseUp: refresh,
355
- onSelect: refresh,
356
- style: { display: isCode ? "none" : "block" }
357
- }
358
- )), !isCode && /* @__PURE__ */ React.createElement("div", { className: "rte-footer" }, "Inside a list: ", /* @__PURE__ */ React.createElement("strong", null, "Tab"), " to go deeper \xA0\xB7\xA0 ", /* @__PURE__ */ React.createElement("strong", null, "Shift+Tab"), " to go back up")));
300
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "1rem 0" }, children: [
301
+ /* @__PURE__ */ jsx("style", { children: CSS }),
302
+ /* @__PURE__ */ jsxs("div", { style: { border: "1px solid #d8d8d8", borderRadius: 4, overflow: "hidden", boxShadow: "0 1px 3px rgba(0,0,0,0.06)" }, children: [
303
+ /* @__PURE__ */ jsxs("div", { className: "rte-toolbar", children: [
304
+ !isCode && /* @__PURE__ */ jsxs(Fragment, { children: [
305
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("bold"), title: "Bold (Ctrl+B)", active: fmt.bold, style: { fontWeight: 800, fontFamily: "Georgia,serif" }, children: "B" }),
306
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("italic"), title: "Italic (Ctrl+I)", active: fmt.italic, style: { fontStyle: "italic", fontFamily: "Georgia,serif" }, children: "I" }),
307
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("underline"), title: "Underline (Ctrl+U)", active: fmt.underline, style: { textDecoration: "underline" }, children: "U" }),
308
+ /* @__PURE__ */ jsxs(Btn, { onClick: () => {
309
+ var _a2;
310
+ return (_a2 = colorRef.current) == null ? void 0 : _a2.click();
311
+ }, title: "Text color", style: { flexDirection: "column", gap: 1, padding: "3px 6px" }, children: [
312
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 800, fontFamily: "Georgia,serif", lineHeight: 1, color: "#222" }, children: "A" }),
313
+ /* @__PURE__ */ jsx("span", { className: "rte-swatch", style: { background: color } })
314
+ ] }),
315
+ /* @__PURE__ */ jsx(
316
+ "input",
317
+ {
318
+ ref: colorRef,
319
+ type: "color",
320
+ value: color,
321
+ onChange: (e) => {
322
+ setColor(e.target.value);
323
+ exec("foreColor", e.target.value);
324
+ },
325
+ style: { position: "absolute", opacity: 0, width: 0, height: 0, pointerEvents: "none" }
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx(Sep, {}),
329
+ /* @__PURE__ */ jsxs(
330
+ "select",
331
+ {
332
+ className: "rte-select",
333
+ value: (_a = fmt.block) != null ? _a : "p",
334
+ onMouseDown: () => {
335
+ var _a2;
336
+ return (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
337
+ },
338
+ onChange: (e) => {
339
+ var _a2;
340
+ (_a2 = editorRef.current) == null ? void 0 : _a2.focus();
341
+ document.execCommand("formatBlock", false, e.target.value);
342
+ refresh();
343
+ },
344
+ children: [
345
+ /* @__PURE__ */ jsx("option", { value: "p", children: "Paragraph" }),
346
+ /* @__PURE__ */ jsx("option", { value: "h1", children: "Heading 1" }),
347
+ /* @__PURE__ */ jsx("option", { value: "h2", children: "Heading 2" }),
348
+ /* @__PURE__ */ jsx("option", { value: "h3", children: "Heading 3" })
349
+ ]
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsx(Sep, {}),
353
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyLeft"), title: "Align left", active: fmt.aL, children: /* @__PURE__ */ jsx(AlignIco, { t: "left" }) }),
354
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyCenter"), title: "Align center", active: fmt.aC, children: /* @__PURE__ */ jsx(AlignIco, { t: "center" }) }),
355
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyRight"), title: "Align right", active: fmt.aR, children: /* @__PURE__ */ jsx(AlignIco, { t: "right" }) }),
356
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("justifyFull"), title: "Justify", active: fmt.aJ, children: /* @__PURE__ */ jsx(AlignIco, { t: "justify" }) }),
357
+ /* @__PURE__ */ jsx(Sep, {}),
358
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("insertUnorderedList"), title: "Bullet list", active: fmt.ul, children: /* @__PURE__ */ jsx(IcoUL, {}) }),
359
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("insertOrderedList"), title: "Numbered list", active: fmt.ol, children: /* @__PURE__ */ jsx(IcoOL, {}) }),
360
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("indent"), title: "Indent (Tab)", children: /* @__PURE__ */ jsx(IcoIndent, {}) }),
361
+ /* @__PURE__ */ jsx(Btn, { onClick: () => exec("outdent"), title: "Outdent (Shift+Tab)", children: /* @__PURE__ */ jsx(IcoOutdent, {}) }),
362
+ /* @__PURE__ */ jsx(Sep, {}),
363
+ /* @__PURE__ */ jsx(Btn, { onClick: openLinkBar, title: "Insert link", active: linkBar, children: /* @__PURE__ */ jsx(IcoLink, {}) }),
364
+ /* @__PURE__ */ jsx(Btn, { onClick: insertCodeBlock, title: "Code block", children: /* @__PURE__ */ jsx(IcoCode, {}) }),
365
+ /* @__PURE__ */ jsx(Sep, {})
366
+ ] }),
367
+ /* @__PURE__ */ jsx(
368
+ Btn,
369
+ {
370
+ onClick: isCode ? toVisual : toCode,
371
+ title: "Toggle HTML source",
372
+ active: isCode,
373
+ style: { fontSize: 12, fontWeight: 600, letterSpacing: "0.03em", padding: "0 8px" },
374
+ children: isCode ? "Visual" : "HTML"
375
+ }
376
+ ),
377
+ /* @__PURE__ */ jsx(Btn, { onClick: copyHtml, title: "Copy HTML", children: /* @__PURE__ */ jsx(IcoCopy, {}) }),
378
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
379
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "#aaa" }, children: [
380
+ words,
381
+ " ",
382
+ words === 1 ? "word" : "words"
383
+ ] })
384
+ ] }),
385
+ linkBar && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, padding: "6px 10px", background: "#f0f4ff", borderBottom: "1px solid #c8d8f8" }, children: [
386
+ /* @__PURE__ */ jsx(IcoLink, {}),
387
+ /* @__PURE__ */ jsx(
388
+ "input",
389
+ {
390
+ autoFocus: true,
391
+ type: "text",
392
+ value: linkUrl,
393
+ onChange: (e) => setLinkUrl(e.target.value),
394
+ onKeyDown: (e) => {
395
+ if (e.key === "Enter") applyLink();
396
+ if (e.key === "Escape") setLinkBar(false);
397
+ },
398
+ placeholder: "https://example.com",
399
+ style: { flex: 1, height: 26, border: "1px solid #b0c4f0", borderRadius: 3, padding: "0 8px", fontSize: 13, outline: "none", fontFamily: "var(--font-sans)" }
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsx(
403
+ "button",
404
+ {
405
+ onClick: applyLink,
406
+ style: { height: 26, padding: "0 12px", background: "#1a6fc4", color: "#fff", border: "none", borderRadius: 3, fontSize: 12, fontWeight: 600, cursor: "pointer" },
407
+ children: "Apply"
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx(
411
+ "button",
412
+ {
413
+ onClick: () => setLinkBar(false),
414
+ style: { height: 26, padding: "0 10px", background: "transparent", color: "#666", border: "1px solid #ccc", borderRadius: 3, fontSize: 12, cursor: "pointer" },
415
+ children: "Cancel"
416
+ }
417
+ )
418
+ ] }),
419
+ /* @__PURE__ */ jsxs("div", { className: "rte-wrap", children: [
420
+ /* @__PURE__ */ jsx(
421
+ "textarea",
422
+ {
423
+ className: "rte-code",
424
+ value: codeVal,
425
+ onChange: (e) => setCodeVal(e.target.value),
426
+ spellCheck: false,
427
+ style: { display: isCode ? "block" : "none" }
428
+ }
429
+ ),
430
+ /* @__PURE__ */ jsx(
431
+ "div",
432
+ {
433
+ ref: editorRef,
434
+ className: "rte-body",
435
+ contentEditable: true,
436
+ suppressContentEditableWarning: true,
437
+ "data-ph": "Start typing...",
438
+ onKeyDown: handleKeyDown,
439
+ onKeyUp: refresh,
440
+ onMouseUp: refresh,
441
+ onSelect: refresh,
442
+ style: { display: isCode ? "none" : "block" }
443
+ }
444
+ )
445
+ ] }),
446
+ !isCode && /* @__PURE__ */ jsxs("div", { className: "rte-footer", children: [
447
+ "Inside a list: ",
448
+ /* @__PURE__ */ jsx("strong", { children: "Tab" }),
449
+ " to go deeper \xA0\xB7\xA0 ",
450
+ /* @__PURE__ */ jsx("strong", { children: "Shift+Tab" }),
451
+ " to go back up"
452
+ ] })
453
+ ] })
454
+ ] });
359
455
  }
360
456
  export {
361
457
  RichTextEditor