@overlap/rte 0.1.0

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.
Files changed (75) hide show
  1. package/README.md +269 -0
  2. package/dist/components/Dropdown.d.ts +19 -0
  3. package/dist/components/Dropdown.d.ts.map +1 -0
  4. package/dist/components/Editor.d.ts +4 -0
  5. package/dist/components/Editor.d.ts.map +1 -0
  6. package/dist/components/FloatingToolbar.d.ts +10 -0
  7. package/dist/components/FloatingToolbar.d.ts.map +1 -0
  8. package/dist/components/IconWrapper.d.ts +10 -0
  9. package/dist/components/IconWrapper.d.ts.map +1 -0
  10. package/dist/components/Icons.d.ts +32 -0
  11. package/dist/components/Icons.d.ts.map +1 -0
  12. package/dist/components/Toolbar.d.ts +10 -0
  13. package/dist/components/Toolbar.d.ts.map +1 -0
  14. package/dist/components/index.d.ts +3 -0
  15. package/dist/components/index.d.ts.map +1 -0
  16. package/dist/index.d.ts +208 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.esm.js +2080 -0
  19. package/dist/index.esm.js.map +1 -0
  20. package/dist/index.js +2116 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/plugins/base.d.ts +10 -0
  23. package/dist/plugins/base.d.ts.map +1 -0
  24. package/dist/plugins/clearFormatting.d.ts +6 -0
  25. package/dist/plugins/clearFormatting.d.ts.map +1 -0
  26. package/dist/plugins/colors.d.ts +4 -0
  27. package/dist/plugins/colors.d.ts.map +1 -0
  28. package/dist/plugins/fontSize.d.ts +3 -0
  29. package/dist/plugins/fontSize.d.ts.map +1 -0
  30. package/dist/plugins/headings.d.ts +3 -0
  31. package/dist/plugins/headings.d.ts.map +1 -0
  32. package/dist/plugins/image.d.ts +6 -0
  33. package/dist/plugins/image.d.ts.map +1 -0
  34. package/dist/plugins/index.d.ts +14 -0
  35. package/dist/plugins/index.d.ts.map +1 -0
  36. package/dist/plugins/optional.d.ts +19 -0
  37. package/dist/plugins/optional.d.ts.map +1 -0
  38. package/dist/styles.css +638 -0
  39. package/dist/types.d.ts +81 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/utils/clearFormatting.d.ts +21 -0
  42. package/dist/utils/clearFormatting.d.ts.map +1 -0
  43. package/dist/utils/content.d.ts +12 -0
  44. package/dist/utils/content.d.ts.map +1 -0
  45. package/dist/utils/history.d.ts +14 -0
  46. package/dist/utils/history.d.ts.map +1 -0
  47. package/dist/utils/listIndent.d.ts +9 -0
  48. package/dist/utils/listIndent.d.ts.map +1 -0
  49. package/dist/utils/stateReflection.d.ts +18 -0
  50. package/dist/utils/stateReflection.d.ts.map +1 -0
  51. package/package.json +48 -0
  52. package/src/components/Dropdown.tsx +103 -0
  53. package/src/components/Editor.css +2 -0
  54. package/src/components/Editor.tsx +785 -0
  55. package/src/components/FloatingToolbar.tsx +214 -0
  56. package/src/components/IconWrapper.tsx +14 -0
  57. package/src/components/Icons.tsx +145 -0
  58. package/src/components/Toolbar.tsx +137 -0
  59. package/src/components/index.ts +3 -0
  60. package/src/index.ts +19 -0
  61. package/src/plugins/base.tsx +91 -0
  62. package/src/plugins/clearFormatting.tsx +31 -0
  63. package/src/plugins/colors.tsx +122 -0
  64. package/src/plugins/fontSize.tsx +81 -0
  65. package/src/plugins/headings.tsx +76 -0
  66. package/src/plugins/image.tsx +189 -0
  67. package/src/plugins/index.ts +54 -0
  68. package/src/plugins/optional.tsx +221 -0
  69. package/src/styles.css +638 -0
  70. package/src/types.ts +92 -0
  71. package/src/utils/clearFormatting.ts +244 -0
  72. package/src/utils/content.ts +290 -0
  73. package/src/utils/history.ts +59 -0
  74. package/src/utils/listIndent.ts +171 -0
  75. package/src/utils/stateReflection.ts +175 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Erhöht den Einrückungs-Level eines List-Items (Tab)
3
+ */
4
+ export function indentListItem(selection: Selection): boolean {
5
+ if (!selection || selection.rangeCount === 0) return false;
6
+
7
+ const range = selection.getRangeAt(0);
8
+ const container = range.commonAncestorContainer;
9
+ const listItem = container.nodeType === Node.TEXT_NODE
10
+ ? container.parentElement?.closest('li')
11
+ : (container as HTMLElement).closest('li');
12
+
13
+ if (!listItem) return false;
14
+
15
+ const list = listItem.parentElement;
16
+ if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL')) return false;
17
+
18
+ // Prüfe ob bereits verschachtelt (max depth check)
19
+ let depth = 0;
20
+ let current: HTMLElement | null = listItem;
21
+ while (current) {
22
+ const parent: HTMLElement | null = current.parentElement;
23
+ if (parent && (parent.tagName === 'UL' || parent.tagName === 'OL')) {
24
+ depth++;
25
+ current = parent.closest('li');
26
+ } else {
27
+ break;
28
+ }
29
+ }
30
+
31
+ // Max depth: 6 (wie in HTML Standard)
32
+ if (depth >= 6) return false;
33
+
34
+ // Finde vorheriges List-Item
35
+ const previousItem = listItem.previousElementSibling as HTMLElement | null;
36
+
37
+ if (previousItem && previousItem.tagName === 'LI') {
38
+ // Erstelle verschachtelte Liste im vorherigen Item
39
+ let nestedList = previousItem.querySelector('ul, ol');
40
+
41
+ if (!nestedList) {
42
+ // Erstelle neue verschachtelte Liste
43
+ nestedList = document.createElement(list.tagName.toLowerCase() as 'ul' | 'ol');
44
+ previousItem.appendChild(nestedList);
45
+ }
46
+
47
+ // Verschiebe aktuelles Item in verschachtelte Liste
48
+ nestedList.appendChild(listItem);
49
+
50
+ // Cursor setzen
51
+ const textNode = listItem.firstChild;
52
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
53
+ range.setStart(textNode, 0);
54
+ range.collapse(true);
55
+ } else if (listItem.firstChild) {
56
+ range.setStart(listItem.firstChild, 0);
57
+ range.collapse(true);
58
+ } else {
59
+ const newText = document.createTextNode('');
60
+ listItem.appendChild(newText);
61
+ range.setStart(newText, 0);
62
+ range.collapse(true);
63
+ }
64
+ selection.removeAllRanges();
65
+ selection.addRange(range);
66
+
67
+ return true;
68
+ } else {
69
+ // Kein vorheriges Item - erstelle neue verschachtelte Liste im aktuellen Item
70
+ const nestedList = document.createElement(list.tagName.toLowerCase() as 'ul' | 'ol');
71
+
72
+ // Verschiebe alle nachfolgenden Items in die verschachtelte Liste
73
+ let nextSibling = listItem.nextElementSibling;
74
+ while (nextSibling && nextSibling.tagName === 'LI') {
75
+ const toMove = nextSibling;
76
+ nextSibling = nextSibling.nextElementSibling;
77
+ nestedList.appendChild(toMove);
78
+ }
79
+
80
+ if (nestedList.children.length > 0) {
81
+ listItem.appendChild(nestedList);
82
+ } else {
83
+ // Wenn keine nachfolgenden Items, erstelle leeres Sub-Item
84
+ const subItem = document.createElement('li');
85
+ nestedList.appendChild(subItem);
86
+ listItem.appendChild(nestedList);
87
+
88
+ // Cursor ins Sub-Item setzen
89
+ const newText = document.createTextNode('');
90
+ subItem.appendChild(newText);
91
+ range.setStart(newText, 0);
92
+ range.collapse(true);
93
+ selection.removeAllRanges();
94
+ selection.addRange(range);
95
+ }
96
+
97
+ return true;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Reduziert den Einrückungs-Level eines List-Items (Shift+Tab)
103
+ */
104
+ export function outdentListItem(selection: Selection): boolean {
105
+ if (!selection || selection.rangeCount === 0) return false;
106
+
107
+ const range = selection.getRangeAt(0);
108
+ const container = range.commonAncestorContainer;
109
+ const listItem = container.nodeType === Node.TEXT_NODE
110
+ ? container.parentElement?.closest('li')
111
+ : (container as HTMLElement).closest('li');
112
+
113
+ if (!listItem) return false;
114
+
115
+ const list = listItem.parentElement;
116
+ if (!list || (list.tagName !== 'UL' && list.tagName !== 'OL')) return false;
117
+
118
+ // Prüfe ob in verschachtelter Liste
119
+ const parentListItem = list.parentElement;
120
+ if (!parentListItem || parentListItem.tagName !== 'LI') {
121
+ // Bereits auf oberstem Level
122
+ return false;
123
+ }
124
+
125
+ const parentList = parentListItem.parentElement;
126
+ if (!parentList || (parentList.tagName !== 'UL' && parentList.tagName !== 'OL')) {
127
+ return false;
128
+ }
129
+
130
+ // Verschiebe Item auf oberes Level
131
+ // Finde Position nach dem Parent-Item
132
+ const insertAfter = parentListItem;
133
+
134
+ // Verschiebe Item und alle nachfolgenden Items aus der verschachtelten Liste
135
+ const itemsToMove: HTMLElement[] = [listItem];
136
+ let nextSibling = listItem.nextElementSibling;
137
+ while (nextSibling && nextSibling.tagName === 'LI') {
138
+ itemsToMove.push(nextSibling as HTMLElement);
139
+ nextSibling = nextSibling.nextElementSibling;
140
+ }
141
+
142
+ // Füge Items nach dem Parent-Item ein
143
+ itemsToMove.forEach(item => {
144
+ parentList.insertBefore(item, insertAfter.nextSibling);
145
+ });
146
+
147
+ // Entferne leere verschachtelte Liste
148
+ if (list.children.length === 0) {
149
+ list.remove();
150
+ }
151
+
152
+ // Cursor setzen
153
+ const textNode = listItem.firstChild;
154
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
155
+ range.setStart(textNode, 0);
156
+ range.collapse(true);
157
+ } else if (listItem.firstChild) {
158
+ range.setStart(listItem.firstChild, 0);
159
+ range.collapse(true);
160
+ } else {
161
+ const newText = document.createTextNode('');
162
+ listItem.appendChild(newText);
163
+ range.setStart(newText, 0);
164
+ range.collapse(true);
165
+ }
166
+ selection.removeAllRanges();
167
+ selection.addRange(range);
168
+
169
+ return true;
170
+ }
171
+
@@ -0,0 +1,175 @@
1
+ import { EditorAPI } from '../types';
2
+
3
+ /**
4
+ * Liest die aktuelle Font-Size aus dem DOM an der Cursor-Position
5
+ */
6
+ export function getCurrentFontSize(editor: EditorAPI): string | undefined {
7
+ const selection = editor.getSelection();
8
+ if (!selection || selection.rangeCount === 0) return undefined;
9
+
10
+ const range = selection.getRangeAt(0);
11
+ const container = range.commonAncestorContainer;
12
+ const element = container.nodeType === Node.TEXT_NODE
13
+ ? container.parentElement
14
+ : container as HTMLElement;
15
+
16
+ if (!element) return undefined;
17
+
18
+ // Suche nach dem nächsten Element mit fontSize
19
+ let current: HTMLElement | null = element;
20
+ while (current && current !== document.body) {
21
+ const fontSize = window.getComputedStyle(current).fontSize;
22
+ if (fontSize && fontSize !== 'inherit' && fontSize !== 'initial') {
23
+ // Konvertiere "16px" zu "16"
24
+ const size = parseInt(fontSize, 10);
25
+ if (!isNaN(size)) {
26
+ return size.toString();
27
+ }
28
+ }
29
+ current = current.parentElement;
30
+ }
31
+
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * Liest die aktuelle Textfarbe aus dem DOM an der Cursor-Position
37
+ */
38
+ export function getCurrentTextColor(editor: EditorAPI): string | undefined {
39
+ const selection = editor.getSelection();
40
+ if (!selection || selection.rangeCount === 0) return undefined;
41
+
42
+ const range = selection.getRangeAt(0);
43
+ const container = range.commonAncestorContainer;
44
+ const element = container.nodeType === Node.TEXT_NODE
45
+ ? container.parentElement
46
+ : container as HTMLElement;
47
+
48
+ if (!element) return undefined;
49
+
50
+ // Suche nach dem nächsten Element mit color
51
+ let current: HTMLElement | null = element;
52
+ while (current && current !== document.body) {
53
+ // Prüfe zuerst inline style (hat Priorität)
54
+ const inlineColor = current.style.color;
55
+ if (inlineColor && inlineColor.trim()) {
56
+ // Wenn bereits Hex, direkt zurückgeben
57
+ if (inlineColor.startsWith('#')) {
58
+ return inlineColor;
59
+ }
60
+ // Wenn RGB/RGBA, konvertieren
61
+ const rgbMatch = inlineColor.match(/\d+/g);
62
+ if (rgbMatch && rgbMatch.length >= 3) {
63
+ const hex = '#' + rgbMatch.slice(0, 3).map(x => {
64
+ const hex = parseInt(x, 10).toString(16);
65
+ return hex.length === 1 ? '0' + hex : hex;
66
+ }).join('');
67
+ return hex;
68
+ }
69
+ }
70
+
71
+ // Dann computed style
72
+ const color = window.getComputedStyle(current).color;
73
+ if (color && color !== 'inherit' && color !== 'initial' && color !== 'rgb(0, 0, 0)' && color !== 'rgba(0, 0, 0, 0)') {
74
+ // Konvertiere RGB zu Hex
75
+ const rgb = color.match(/\d+/g);
76
+ if (rgb && rgb.length >= 3) {
77
+ const hex = '#' + rgb.slice(0, 3).map(x => {
78
+ const hex = parseInt(x, 10).toString(16);
79
+ return hex.length === 1 ? '0' + hex : hex;
80
+ }).join('');
81
+ return hex;
82
+ }
83
+ }
84
+ current = current.parentElement;
85
+ }
86
+
87
+ return undefined;
88
+ }
89
+
90
+ /**
91
+ * Liest die aktuelle Hintergrundfarbe aus dem DOM an der Cursor-Position
92
+ */
93
+ export function getCurrentBackgroundColor(editor: EditorAPI): string | undefined {
94
+ const selection = editor.getSelection();
95
+ if (!selection || selection.rangeCount === 0) return undefined;
96
+
97
+ const range = selection.getRangeAt(0);
98
+ const container = range.commonAncestorContainer;
99
+ const element = container.nodeType === Node.TEXT_NODE
100
+ ? container.parentElement
101
+ : container as HTMLElement;
102
+
103
+ if (!element) return undefined;
104
+
105
+ // Suche nach dem nächsten Element mit backgroundColor
106
+ let current: HTMLElement | null = element;
107
+ while (current && current !== document.body) {
108
+ // Prüfe zuerst inline style (hat Priorität)
109
+ const inlineBgColor = current.style.backgroundColor;
110
+ if (inlineBgColor && inlineBgColor.trim()) {
111
+ // Wenn bereits Hex, direkt zurückgeben
112
+ if (inlineBgColor.startsWith('#')) {
113
+ return inlineBgColor;
114
+ }
115
+ // Wenn RGB/RGBA, konvertieren
116
+ const rgbMatch = inlineBgColor.match(/\d+/g);
117
+ if (rgbMatch && rgbMatch.length >= 3) {
118
+ const hex = '#' + rgbMatch.slice(0, 3).map(x => {
119
+ const hex = parseInt(x, 10).toString(16);
120
+ return hex.length === 1 ? '0' + hex : hex;
121
+ }).join('');
122
+ return hex;
123
+ }
124
+ }
125
+
126
+ // Dann computed style
127
+ const bgColor = window.getComputedStyle(current).backgroundColor;
128
+ if (bgColor && bgColor !== 'inherit' && bgColor !== 'initial' && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
129
+ // Konvertiere RGB zu Hex
130
+ const rgb = bgColor.match(/\d+/g);
131
+ if (rgb && rgb.length >= 3) {
132
+ const hex = '#' + rgb.slice(0, 3).map(x => {
133
+ const hex = parseInt(x, 10).toString(16);
134
+ return hex.length === 1 ? '0' + hex : hex;
135
+ }).join('');
136
+ return hex;
137
+ }
138
+ }
139
+ current = current.parentElement;
140
+ }
141
+
142
+ return undefined;
143
+ }
144
+
145
+ /**
146
+ * Liest das aktuelle Heading-Level aus dem DOM an der Cursor-Position
147
+ */
148
+ export function getCurrentHeading(editor: EditorAPI, availableHeadings: string[]): string | undefined {
149
+ const selection = editor.getSelection();
150
+ if (!selection || selection.rangeCount === 0) return undefined;
151
+
152
+ const range = selection.getRangeAt(0);
153
+ const container = range.commonAncestorContainer;
154
+ const element = container.nodeType === Node.TEXT_NODE
155
+ ? container.parentElement
156
+ : container as HTMLElement;
157
+
158
+ if (!element) return undefined;
159
+
160
+ // Suche nach dem nächsten Block-Element
161
+ let current: HTMLElement | null = element;
162
+ while (current && current !== document.body) {
163
+ const tagName = current.tagName.toLowerCase();
164
+ if (availableHeadings.includes(tagName)) {
165
+ return tagName;
166
+ }
167
+ if (tagName === 'p') {
168
+ return 'p';
169
+ }
170
+ current = current.parentElement;
171
+ }
172
+
173
+ return undefined;
174
+ }
175
+