smartrte-react 0.1.7 → 0.1.8

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.
@@ -21,6 +21,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
21
21
  const selectingRef = useRef(null);
22
22
  const [imageMenu, setImageMenu] = useState(null);
23
23
  const [showMediaManager, setShowMediaManager] = useState(false);
24
+ const [showColorPicker, setShowColorPicker] = useState(false);
25
+ const [colorPickerType, setColorPickerType] = useState('text');
26
+ const savedRangeRef = useRef(null);
27
+ const [currentFontSize, setCurrentFontSize] = useState("11");
24
28
  useEffect(() => {
25
29
  const el = editableRef.current;
26
30
  if (!el)
@@ -42,6 +46,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
42
46
  el.removeEventListener("contextmenu", onCtx, { capture: true });
43
47
  };
44
48
  }, [value]);
49
+ // Save selection whenever it changes
50
+ useEffect(() => {
51
+ const saveSelection = () => {
52
+ const sel = window.getSelection();
53
+ if (sel && sel.rangeCount > 0) {
54
+ const range = sel.getRangeAt(0);
55
+ const editor = editableRef.current;
56
+ if (editor && editor.contains(range.commonAncestorContainer)) {
57
+ savedRangeRef.current = range.cloneRange();
58
+ }
59
+ }
60
+ };
61
+ document.addEventListener('selectionchange', saveSelection);
62
+ return () => {
63
+ document.removeEventListener('selectionchange', saveSelection);
64
+ };
65
+ }, []);
45
66
  const exec = (command, valueArg) => {
46
67
  try {
47
68
  document.execCommand(command, false, valueArg);
@@ -66,6 +87,76 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
66
87
  return;
67
88
  exec("createLink", url);
68
89
  };
90
+ const applyFontSize = (size) => {
91
+ try {
92
+ // Update current font size state
93
+ setCurrentFontSize(size);
94
+ const editor = editableRef.current;
95
+ if (!editor)
96
+ return;
97
+ editor.focus();
98
+ // Try to get current selection, or use saved range
99
+ let range = null;
100
+ const sel = window.getSelection();
101
+ if (sel && sel.rangeCount > 0) {
102
+ const currentRange = sel.getRangeAt(0);
103
+ // Use current range if it's within our editor
104
+ if (editor.contains(currentRange.commonAncestorContainer)) {
105
+ range = currentRange;
106
+ }
107
+ }
108
+ // Fallback to saved range if current range is not available
109
+ if (!range && savedRangeRef.current) {
110
+ range = savedRangeRef.current.cloneRange();
111
+ }
112
+ // If no range at all, just update state for future typing
113
+ if (!range)
114
+ return;
115
+ // If range is collapsed (cursor position, no selection), insert an invisible span
116
+ if (range.collapsed) {
117
+ // Create a span with zero-width space that will capture future typing
118
+ const span = document.createElement('span');
119
+ span.style.fontSize = size + 'pt';
120
+ span.textContent = '\u200B'; // Zero-width space
121
+ range.insertNode(span);
122
+ // Position cursor inside the span
123
+ const newRange = document.createRange();
124
+ newRange.setStart(span.firstChild, 1);
125
+ newRange.collapse(true);
126
+ if (sel) {
127
+ sel.removeAllRanges();
128
+ sel.addRange(newRange);
129
+ }
130
+ handleInput();
131
+ return;
132
+ }
133
+ // If there's selected text, wrap it
134
+ const span = document.createElement('span');
135
+ span.style.fontSize = size + 'pt';
136
+ // Extract the selected content and wrap it in the span
137
+ const fragment = range.extractContents();
138
+ span.appendChild(fragment);
139
+ // Insert the span at the current position
140
+ range.insertNode(span);
141
+ // Update selection to show what was changed
142
+ if (sel) {
143
+ range.selectNodeContents(span);
144
+ sel.removeAllRanges();
145
+ sel.addRange(range);
146
+ }
147
+ // Trigger change event
148
+ handleInput();
149
+ }
150
+ catch (error) {
151
+ console.error('Error applying font size:', error);
152
+ }
153
+ };
154
+ const applyTextColor = (color) => {
155
+ exec("foreColor", color);
156
+ };
157
+ const applyBackgroundColor = (color) => {
158
+ exec("hiliteColor", color);
159
+ };
69
160
  const insertImage = () => {
70
161
  if (!media)
71
162
  return;
@@ -739,7 +830,56 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
739
830
  background: "#fff",
740
831
  textDecoration: "line-through",
741
832
  color: "#111",
742
- }, children: "S" }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
833
+ }, children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
834
+ // Save selection before dropdown interaction
835
+ const sel = window.getSelection();
836
+ if (sel && sel.rangeCount > 0) {
837
+ const range = sel.getRangeAt(0);
838
+ const editor = editableRef.current;
839
+ if (editor && editor.contains(range.commonAncestorContainer) && !range.collapsed) {
840
+ savedRangeRef.current = range.cloneRange();
841
+ }
842
+ }
843
+ }, onChange: (e) => applyFontSize(e.target.value), title: "Font Size", style: {
844
+ height: 32,
845
+ padding: "0 8px",
846
+ border: "1px solid #e5e7eb",
847
+ borderRadius: 6,
848
+ background: "#fff",
849
+ color: "#111",
850
+ }, children: [_jsx("option", { value: "8", children: "8" }), _jsx("option", { value: "9", children: "9" }), _jsx("option", { value: "10", children: "10" }), _jsx("option", { value: "11", children: "11" }), _jsx("option", { value: "12", children: "12" }), _jsx("option", { value: "14", children: "14" }), _jsx("option", { value: "18", children: "18" }), _jsx("option", { value: "24", children: "24" }), _jsx("option", { value: "30", children: "30" }), _jsx("option", { value: "36", children: "36" }), _jsx("option", { value: "48", children: "48" }), _jsx("option", { value: "60", children: "60" }), _jsx("option", { value: "72", children: "72" }), _jsx("option", { value: "96", children: "96" })] }), _jsxs("button", { title: "Text Color", onClick: () => {
851
+ setColorPickerType('text');
852
+ setShowColorPicker(true);
853
+ }, style: {
854
+ height: 32,
855
+ minWidth: 32,
856
+ padding: "0 8px",
857
+ border: "1px solid #e5e7eb",
858
+ borderRadius: 6,
859
+ background: "#fff",
860
+ color: "#111",
861
+ position: "relative",
862
+ }, children: [_jsx("span", { style: { fontWeight: 700 }, children: "A" }), _jsx("div", { style: {
863
+ position: "absolute",
864
+ bottom: 4,
865
+ left: "50%",
866
+ transform: "translateX(-50%)",
867
+ width: 16,
868
+ height: 3,
869
+ background: "#000",
870
+ borderRadius: 1,
871
+ } })] }), _jsx("button", { title: "Background Color", onClick: () => {
872
+ setColorPickerType('background');
873
+ setShowColorPicker(true);
874
+ }, style: {
875
+ height: 32,
876
+ minWidth: 32,
877
+ padding: "0 8px",
878
+ border: "1px solid #e5e7eb",
879
+ borderRadius: 6,
880
+ background: "#fff",
881
+ color: "#111",
882
+ }, children: _jsx("span", { style: { fontWeight: 700, background: "#ffeb3b", padding: "2px 4px" }, children: "A" }) }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
743
883
  height: 32,
744
884
  padding: "0 10px",
745
885
  border: "1px solid #e5e7eb",
@@ -929,7 +1069,72 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
929
1069
  }, children: [_jsx("button", { onClick: () => setShowTableDialog(false), children: "Cancel" }), _jsx("button", { onClick: () => {
930
1070
  insertTable();
931
1071
  setShowTableDialog(false);
932
- }, children: "Insert" })] })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
1072
+ }, children: "Insert" })] })] }) })), showColorPicker && (_jsx("div", { style: {
1073
+ position: "fixed",
1074
+ inset: 0,
1075
+ background: "rgba(0,0,0,0.35)",
1076
+ display: "flex",
1077
+ alignItems: "center",
1078
+ justifyContent: "center",
1079
+ zIndex: 50,
1080
+ }, onClick: () => setShowColorPicker(false), children: _jsxs("div", { style: {
1081
+ background: "#fff",
1082
+ padding: 16,
1083
+ borderRadius: 8,
1084
+ minWidth: 320,
1085
+ color: "#000",
1086
+ }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: colorPickerType === 'text' ? 'Select Text Color' : 'Select Background Color' }), _jsx("div", { style: {
1087
+ display: "grid",
1088
+ gridTemplateColumns: "repeat(8, 1fr)",
1089
+ gap: 8,
1090
+ marginBottom: 12,
1091
+ }, children: [
1092
+ '#000000', '#434343', '#666666', '#999999',
1093
+ '#b7b7b7', '#cccccc', '#d9d9d9', '#efefef',
1094
+ '#f3f3f3', '#ffffff', '#980000', '#ff0000',
1095
+ '#ff9900', '#ffff00', '#00ff00', '#00ffff',
1096
+ '#4a86e8', '#0000ff', '#9900ff', '#ff00ff',
1097
+ '#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc',
1098
+ '#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3',
1099
+ '#d9d2e9', '#ead1dc', '#dd7e6b', '#ea9999',
1100
+ '#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9',
1101
+ '#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',
1102
+ ].map((color) => (_jsx("button", { onClick: () => {
1103
+ if (colorPickerType === 'text') {
1104
+ applyTextColor(color);
1105
+ }
1106
+ else {
1107
+ applyBackgroundColor(color);
1108
+ }
1109
+ setShowColorPicker(false);
1110
+ }, style: {
1111
+ width: 32,
1112
+ height: 32,
1113
+ border: color === '#ffffff' ? '1px solid #ddd' : 'none',
1114
+ borderRadius: 4,
1115
+ background: color,
1116
+ cursor: 'pointer',
1117
+ }, title: color }, color))) }), _jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("label", { style: { display: 'block', marginBottom: 6, fontSize: 12 }, children: "Custom color:" }), _jsx("input", { type: "color", onChange: (e) => {
1118
+ if (colorPickerType === 'text') {
1119
+ applyTextColor(e.target.value);
1120
+ }
1121
+ else {
1122
+ applyBackgroundColor(e.target.value);
1123
+ }
1124
+ }, style: {
1125
+ width: '100%',
1126
+ height: 40,
1127
+ border: '1px solid #ddd',
1128
+ borderRadius: 6,
1129
+ cursor: 'pointer',
1130
+ } })] }), _jsx("div", { style: { display: 'flex', justifyContent: 'end' }, children: _jsx("button", { onClick: () => setShowColorPicker(false), style: {
1131
+ padding: '6px 16px',
1132
+ border: '1px solid #e5e7eb',
1133
+ borderRadius: 6,
1134
+ background: '#fff',
1135
+ color: '#111',
1136
+ cursor: 'pointer',
1137
+ }, children: "Close" }) })] }) })), formula && showFormulaDialog && (_jsx("div", { style: {
933
1138
  position: "fixed",
934
1139
  inset: 0,
935
1140
  background: "rgba(0,0,0,0.35)",