@overlap/rte 0.1.2 → 0.1.4

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.
@@ -1,214 +0,0 @@
1
- import React, { useEffect, useState, useRef } from "react";
2
- import { ButtonProps, EditorAPI, Plugin } from "../types";
3
-
4
- interface FloatingToolbarProps {
5
- plugins: Plugin[];
6
- editorAPI: EditorAPI;
7
- editorElement: HTMLElement | null;
8
- }
9
-
10
- interface Position {
11
- top: number;
12
- left: number;
13
- visible: boolean;
14
- }
15
-
16
- export const FloatingToolbar: React.FC<FloatingToolbarProps> = ({
17
- plugins,
18
- editorAPI,
19
- editorElement,
20
- }) => {
21
- const [position, setPosition] = useState<Position>({
22
- top: 0,
23
- left: 0,
24
- visible: false,
25
- });
26
- const toolbarRef = useRef<HTMLDivElement>(null);
27
- const updateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
28
-
29
- const inlinePlugins = plugins.filter(
30
- (p) => p.type === "inline" && p.name !== "clearFormatting"
31
- );
32
-
33
- const updatePosition = React.useCallback(() => {
34
- if (typeof window === 'undefined') {
35
- setPosition((prev) => ({ ...prev, visible: false }));
36
- return;
37
- }
38
- const selection = window.getSelection();
39
- if (!selection || selection.rangeCount === 0 || !editorElement) {
40
- setPosition((prev) => ({ ...prev, visible: false }));
41
- return;
42
- }
43
-
44
- const range = selection.getRangeAt(0);
45
-
46
- if (range.collapsed) {
47
- setPosition((prev) => ({ ...prev, visible: false }));
48
- return;
49
- }
50
-
51
- if (!editorElement.contains(range.commonAncestorContainer)) {
52
- setPosition((prev) => ({ ...prev, visible: false }));
53
- return;
54
- }
55
-
56
- const rect = range.getBoundingClientRect();
57
- const editorRect = editorElement.getBoundingClientRect();
58
- const toolbarHeight = toolbarRef.current?.offsetHeight || 40;
59
- const toolbarWidth = toolbarRef.current?.offsetWidth || 200;
60
-
61
- let viewportTop = rect.top - toolbarHeight - 8;
62
- let viewportLeft = rect.left + rect.width / 2 - toolbarWidth / 2;
63
-
64
- const viewportWidth = window.innerWidth;
65
- const viewportHeight = window.innerHeight;
66
-
67
- if (viewportLeft < 8) {
68
- viewportLeft = 8;
69
- }
70
- if (viewportLeft + toolbarWidth > viewportWidth - 8) {
71
- viewportLeft = viewportWidth - toolbarWidth - 8;
72
- }
73
-
74
- if (viewportTop < 8) {
75
- viewportTop = rect.bottom + 8;
76
- }
77
- if (viewportTop + toolbarHeight > viewportHeight - 8) {
78
- viewportTop = rect.top - toolbarHeight - 8;
79
- if (viewportTop < 8) {
80
- viewportTop = 8;
81
- }
82
- }
83
-
84
- const relativeTop = viewportTop - editorRect.top + editorElement.scrollTop;
85
- const relativeLeft = viewportLeft - editorRect.left + editorElement.scrollLeft;
86
-
87
- setPosition({
88
- top: relativeTop,
89
- left: relativeLeft,
90
- visible: true,
91
- });
92
- }, [editorElement]);
93
-
94
- const scheduleUpdate = React.useCallback(() => {
95
- if (updateTimeoutRef.current) {
96
- clearTimeout(updateTimeoutRef.current);
97
- }
98
- updateTimeoutRef.current = setTimeout(() => {
99
- updatePosition();
100
- }, 10);
101
- }, [updatePosition]);
102
-
103
- useEffect(() => {
104
- const handleSelectionChange = () => {
105
- scheduleUpdate();
106
- };
107
-
108
- const handleMouseUp = () => {
109
- scheduleUpdate();
110
- };
111
-
112
- const handleKeyUp = () => {
113
- scheduleUpdate();
114
- };
115
-
116
- const handleScroll = () => {
117
- if (position.visible) {
118
- scheduleUpdate();
119
- }
120
- };
121
-
122
- const handleResize = () => {
123
- if (position.visible) {
124
- scheduleUpdate();
125
- }
126
- };
127
-
128
- document.addEventListener("selectionchange", handleSelectionChange);
129
- document.addEventListener("mouseup", handleMouseUp);
130
- document.addEventListener("keyup", handleKeyUp);
131
- window.addEventListener("scroll", handleScroll, true);
132
- window.addEventListener("resize", handleResize);
133
-
134
- return () => {
135
- document.removeEventListener("selectionchange", handleSelectionChange);
136
- document.removeEventListener("mouseup", handleMouseUp);
137
- document.removeEventListener("keyup", handleKeyUp);
138
- window.removeEventListener("scroll", handleScroll, true);
139
- window.removeEventListener("resize", handleResize);
140
- if (updateTimeoutRef.current) {
141
- clearTimeout(updateTimeoutRef.current);
142
- }
143
- };
144
- }, [position.visible, scheduleUpdate]);
145
-
146
- useEffect(() => {
147
- scheduleUpdate();
148
- }, [scheduleUpdate]);
149
-
150
- const handlePluginClick = (plugin: Plugin, value?: string) => {
151
- if (plugin.canExecute?.(editorAPI) !== false) {
152
- if (plugin.execute) {
153
- plugin.execute(editorAPI, value);
154
- } else if (plugin.command && value !== undefined) {
155
- editorAPI.executeCommand(plugin.command, value);
156
- } else if (plugin.command) {
157
- editorAPI.executeCommand(plugin.command);
158
- }
159
- setTimeout(() => {
160
- scheduleUpdate();
161
- }, 50);
162
- }
163
- };
164
-
165
- if (!position.visible || inlinePlugins.length === 0) {
166
- return null;
167
- }
168
-
169
- return (
170
- <div
171
- ref={toolbarRef}
172
- className="rte-floating-toolbar"
173
- style={{
174
- position: "absolute",
175
- top: `${position.top}px`,
176
- left: `${position.left}px`,
177
- }}
178
- >
179
- <div className="rte-floating-toolbar-content">
180
- {inlinePlugins.map((plugin) => {
181
- if (!plugin.renderButton) return null;
182
-
183
- const isActive = plugin.isActive
184
- ? plugin.isActive(editorAPI)
185
- : false;
186
- const canExecute = plugin.canExecute
187
- ? plugin.canExecute(editorAPI)
188
- : true;
189
-
190
- const currentValue = plugin.getCurrentValue
191
- ? plugin.getCurrentValue(editorAPI)
192
- : undefined;
193
-
194
- const buttonProps: ButtonProps & { [key: string]: any } = {
195
- isActive,
196
- onClick: () => handlePluginClick(plugin),
197
- disabled: !canExecute,
198
- onSelect: (value: string) =>
199
- handlePluginClick(plugin, value),
200
- editorAPI,
201
- currentValue,
202
- };
203
-
204
- return (
205
- <React.Fragment key={plugin.name}>
206
- {plugin.renderButton(buttonProps)}
207
- </React.Fragment>
208
- );
209
- })}
210
- </div>
211
- </div>
212
- );
213
- };
214
-
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import { Icon } from './Icons';
3
-
4
- interface IconWrapperProps {
5
- icon: string;
6
- width?: number;
7
- height?: number;
8
- className?: string;
9
- }
10
-
11
- export const IconWrapper: React.FC<IconWrapperProps> = ({ icon, width = 18, height = 18, className }) => {
12
- return <Icon icon={icon} width={width} height={height} className={className} />;
13
- };
14
-
@@ -1,374 +0,0 @@
1
- import React from "react";
2
-
3
- interface IconProps {
4
- width?: number;
5
- height?: number;
6
- className?: string;
7
- }
8
-
9
- export const BoldIcon: React.FC<IconProps> = ({
10
- width = 18,
11
- height = 18,
12
- className,
13
- }) => (
14
- <svg
15
- width={width}
16
- height={height}
17
- viewBox="0 0 24 24"
18
- fill="currentColor"
19
- className={className}
20
- >
21
- <path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
22
- </svg>
23
- );
24
-
25
- export const ItalicIcon: React.FC<IconProps> = ({
26
- width = 18,
27
- height = 18,
28
- className,
29
- }) => (
30
- <svg
31
- width={width}
32
- height={height}
33
- viewBox="0 0 24 24"
34
- fill="currentColor"
35
- className={className}
36
- >
37
- <path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" />
38
- </svg>
39
- );
40
-
41
- export const UnderlineIcon: React.FC<IconProps> = ({
42
- width = 18,
43
- height = 18,
44
- className,
45
- }) => (
46
- <svg
47
- width={width}
48
- height={height}
49
- viewBox="0 0 24 24"
50
- fill="currentColor"
51
- className={className}
52
- >
53
- <path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" />
54
- </svg>
55
- );
56
-
57
- export const UndoIcon: React.FC<IconProps> = ({
58
- width = 18,
59
- height = 18,
60
- className,
61
- }) => (
62
- <svg
63
- width={width}
64
- height={height}
65
- viewBox="0 0 24 24"
66
- fill="currentColor"
67
- className={className}
68
- >
69
- <path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" />
70
- </svg>
71
- );
72
-
73
- export const RedoIcon: React.FC<IconProps> = ({
74
- width = 18,
75
- height = 18,
76
- className,
77
- }) => (
78
- <svg
79
- width={width}
80
- height={height}
81
- viewBox="0 0 24 24"
82
- fill="currentColor"
83
- className={className}
84
- >
85
- <path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" />
86
- </svg>
87
- );
88
-
89
- export const ClearFormattingIcon: React.FC<IconProps> = ({
90
- width = 18,
91
- height = 18,
92
- className,
93
- }) => (
94
- <svg
95
- width={width}
96
- height={height}
97
- viewBox="0 0 24 24"
98
- fill="currentColor"
99
- className={className}
100
- >
101
- <path d="M6 5v3h5v11h2V8h5V5H6z" />
102
- <path d="M20.5 3.5L3.5 20.5l1.06 1.06L21.56 4.56z" />
103
- </svg>
104
- );
105
-
106
- export const LinkIcon: React.FC<IconProps> = ({
107
- width = 18,
108
- height = 18,
109
- className,
110
- }) => (
111
- <svg
112
- width={width}
113
- height={height}
114
- viewBox="0 0 24 24"
115
- fill="currentColor"
116
- className={className}
117
- >
118
- <path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" />
119
- </svg>
120
- );
121
-
122
- export const QuoteIcon: React.FC<IconProps> = ({
123
- width = 18,
124
- height = 18,
125
- className,
126
- }) => (
127
- <svg
128
- width={width}
129
- height={height}
130
- viewBox="0 0 24 24"
131
- fill="currentColor"
132
- className={className}
133
- >
134
- <path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z" />
135
- </svg>
136
- );
137
-
138
- export const BulletListIcon: React.FC<IconProps> = ({
139
- width = 18,
140
- height = 18,
141
- className,
142
- }) => (
143
- <svg
144
- width={width}
145
- height={height}
146
- viewBox="0 0 24 24"
147
- fill="currentColor"
148
- className={className}
149
- >
150
- <path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z" />
151
- </svg>
152
- );
153
-
154
- export const NumberedListIcon: React.FC<IconProps> = ({
155
- width = 18,
156
- height = 18,
157
- className,
158
- }) => (
159
- <svg
160
- width={width}
161
- height={height}
162
- viewBox="0 0 24 24"
163
- fill="currentColor"
164
- className={className}
165
- >
166
- <path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 11.9V11H2zm6-5v2h14V6H8zm0 14h14v-2H8v2zm0-6h14v-2H8v2z" />
167
- </svg>
168
- );
169
-
170
- export const TextColorIcon: React.FC<IconProps> = ({
171
- width = 18,
172
- height = 18,
173
- className,
174
- }) => (
175
- <svg
176
- width={width}
177
- height={height}
178
- viewBox="0 0 24 24"
179
- fill="currentColor"
180
- className={className}
181
- >
182
- <path d="M2 20h20v4H2v-4zm3.49-3h2.42l1.27-3.58h5.64L16.09 17h2.42L13.25 3h-2.5L5.49 17zm4.22-5.61l2.03-5.79h.12l2.03 5.79H9.71z" />
183
- </svg>
184
- );
185
-
186
- export const BackgroundColorIcon: React.FC<IconProps> = ({
187
- width = 18,
188
- height = 18,
189
- className,
190
- }) => (
191
- <svg
192
- width={width}
193
- height={height}
194
- viewBox="0 0 24 24"
195
- fill="currentColor"
196
- className={className}
197
- >
198
- <path d="M17.5 4.5c-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5-1.45 0-2.99.22-4.28.79C1.49 5.62 1 6.33 1 7.14v11.28c0 1.3 1.22 2.26 2.48 1.94.98-.25 2.02-.36 3.02-.36 1.56 0 3.22.26 4.56.92.6.3 1.28.3 1.88 0 1.34-.67 3-.92 4.56-.92 1 0 2.04.11 3.02.36C22.78 20.68 24 19.72 24 18.42V7.14c0-.81-.49-1.52-1.22-1.85-1.29-.57-2.83-.79-4.28-.79zM21 17.23c0 .63-.58 1.09-1.2.98-.75-.14-1.53-.2-2.3-.2-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5.77 0 1.55.06 2.3.2.62.11 1.2.58 1.2 1.18v9.35z" />
199
- <rect
200
- x="4"
201
- y="13"
202
- width="16"
203
- height="6"
204
- fill="currentColor"
205
- opacity="0.5"
206
- />
207
- </svg>
208
- );
209
-
210
- export const HeadingIcon: React.FC<IconProps> = ({
211
- width = 18,
212
- height = 18,
213
- className,
214
- }) => (
215
- <svg
216
- width={width}
217
- height={height}
218
- viewBox="0 0 24 24"
219
- fill="currentColor"
220
- className={className}
221
- >
222
- <path d="M5 4v3h5.5v12h3V7H19V4H5z" />
223
- </svg>
224
- );
225
-
226
- export const FontSizeIcon: React.FC<IconProps> = ({
227
- width = 18,
228
- height = 18,
229
- className,
230
- }) => (
231
- <svg
232
- width={width}
233
- height={height}
234
- viewBox="0 0 24 24"
235
- fill="currentColor"
236
- className={className}
237
- >
238
- <path d="M9 4v3h5v12h3V7h5V4H9zm-6 8h3v8h3v-8h3V10H3z" />
239
- </svg>
240
- );
241
-
242
- export const ImageIcon: React.FC<IconProps> = ({
243
- width = 18,
244
- height = 18,
245
- className,
246
- }) => (
247
- <svg
248
- width={width}
249
- height={height}
250
- viewBox="0 0 24 24"
251
- fill="currentColor"
252
- className={className}
253
- >
254
- <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" />
255
- </svg>
256
- );
257
-
258
- export const CloseIcon: React.FC<IconProps> = ({
259
- width = 18,
260
- height = 18,
261
- className,
262
- }) => (
263
- <svg
264
- width={width}
265
- height={height}
266
- viewBox="0 0 24 24"
267
- fill="currentColor"
268
- className={className}
269
- >
270
- <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
271
- </svg>
272
- );
273
-
274
- export const LoadingIcon: React.FC<IconProps> = ({
275
- width = 18,
276
- height = 18,
277
- className,
278
- }) => (
279
- <svg
280
- width={width}
281
- height={height}
282
- viewBox="0 0 24 24"
283
- fill="currentColor"
284
- className={className}
285
- >
286
- <path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" />
287
- </svg>
288
- );
289
-
290
- export const UploadIcon: React.FC<IconProps> = ({
291
- width = 18,
292
- height = 18,
293
- className,
294
- }) => (
295
- <svg
296
- width={width}
297
- height={height}
298
- viewBox="0 0 24 24"
299
- fill="currentColor"
300
- className={className}
301
- >
302
- <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" />
303
- </svg>
304
- );
305
-
306
- export const IndentIcon: React.FC<IconProps> = ({
307
- width = 18,
308
- height = 18,
309
- className,
310
- }) => (
311
- <svg
312
- width={width}
313
- height={height}
314
- viewBox="0 0 24 24"
315
- fill="currentColor"
316
- className={className}
317
- >
318
- <path d="M3 21h18v-2H3v2zM3 8l4 4-4 4V8zm8 9h10v-2H11v2zM3 3v2h18V3H3zm8 6h10V7H11v2zm0 4h10v-2H11v2z" />
319
- </svg>
320
- );
321
-
322
- export const OutdentIcon: React.FC<IconProps> = ({
323
- width = 18,
324
- height = 18,
325
- className,
326
- }) => (
327
- <svg
328
- width={width}
329
- height={height}
330
- viewBox="0 0 24 24"
331
- fill="currentColor"
332
- className={className}
333
- >
334
- <path d="M3 21h18v-2H3v2zM11 8l4 4-4 4V8zM3 3v2h18V3H3zm0 4h10v2H3V7zm0 4h10v2H3v-2zm0 4h18v2H3v-2z" />
335
- </svg>
336
- );
337
-
338
- const iconMap: Record<string, React.FC<IconProps>> = {
339
- "mdi:format-bold": BoldIcon,
340
- "mdi:format-italic": ItalicIcon,
341
- "mdi:format-underline": UnderlineIcon,
342
- "mdi:undo": UndoIcon,
343
- "mdi:redo": RedoIcon,
344
- "mdi:format-clear": ClearFormattingIcon,
345
- "mdi:link": LinkIcon,
346
- "mdi:format-quote-close": QuoteIcon,
347
- "mdi:format-list-bulleted": BulletListIcon,
348
- "mdi:format-list-numbered": NumberedListIcon,
349
- "mdi:format-color-text": TextColorIcon,
350
- "mdi:format-color-fill": BackgroundColorIcon,
351
- "mdi:format-header-1": HeadingIcon,
352
- "mdi:format-size": FontSizeIcon,
353
- "mdi:image": ImageIcon,
354
- "mdi:close": CloseIcon,
355
- "mdi:loading": LoadingIcon,
356
- "mdi:upload": UploadIcon,
357
- "mdi:format-indent-increase": IndentIcon,
358
- "mdi:format-indent-decrease": OutdentIcon,
359
- };
360
-
361
- export const Icon: React.FC<{
362
- icon: string;
363
- width?: number;
364
- height?: number;
365
- className?: string;
366
- }> = ({ icon, width = 18, height = 18, className }) => {
367
- const IconComponent = iconMap[icon];
368
- if (!IconComponent) {
369
- return <span style={{ width, height, display: "inline-block" }} />;
370
- }
371
- return (
372
- <IconComponent width={width} height={height} className={className} />
373
- );
374
- };