funuicss 3.10.2 → 3.10.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.
- package/package.json +1 -1
- package/ui/accordion/Accordion.js +1 -1
- package/ui/drop/Dropdown.js +79 -21
- package/ui/richtext/RichText.js +689 -228
- package/ui/sidebar/SideBar.js +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.10.
|
|
2
|
+
"version": "3.10.4",
|
|
3
3
|
"name": "funuicss",
|
|
4
4
|
"description": "React and Next.js component UI Library for creating Easy and good looking websites with fewer lines of code. Elevate your web development experience with our cutting-edge React/Next.js component UI Library. Craft stunning websites effortlessly, boasting both seamless functionality and aesthetic appeal—all achieved with minimal lines of code. Unleash the power of simplicity and style in your projects!",
|
|
5
5
|
"main": "index.js",
|
|
@@ -313,7 +313,7 @@ var AccordionItem = function (_a) {
|
|
|
313
313
|
react_1.default.createElement("div", { style: { lineHeight: 0 } },
|
|
314
314
|
react_1.default.createElement(AccordionIcon, { isExpandIcon: true, expandIcon: expandIcon, expandIconColor: expandIconColor, expandIconSize: expandIconSize, expandIconClassName: expandIconClassName, isOpen: isOpen, rotate: expandIconRotate }))),
|
|
315
315
|
react_1.default.createElement("div", { className: "accordion-content ".concat(contentClass || '', " ").concat(isOpen ? 'open' : ''), style: {
|
|
316
|
-
maxHeight: isOpen ? '
|
|
316
|
+
maxHeight: isOpen ? '999999999999999px' : '0',
|
|
317
317
|
overflow: 'visible',
|
|
318
318
|
opacity: isOpen ? 1 : 0,
|
|
319
319
|
visibility: isOpen ? 'visible' : 'hidden',
|
package/ui/drop/Dropdown.js
CHANGED
|
@@ -50,9 +50,8 @@ var Dropdown = function (_a) {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
var calculatePosition = function () {
|
|
53
|
+
var _a;
|
|
53
54
|
var rect = triggerRef.current.getBoundingClientRect();
|
|
54
|
-
var scrollY = window.scrollY;
|
|
55
|
-
var scrollX = window.scrollX;
|
|
56
55
|
var styles = {
|
|
57
56
|
width: width || undefined,
|
|
58
57
|
minWidth: minWidth || undefined,
|
|
@@ -64,44 +63,103 @@ var Dropdown = function (_a) {
|
|
|
64
63
|
};
|
|
65
64
|
if (usePortal) {
|
|
66
65
|
styles.position = 'fixed';
|
|
67
|
-
//
|
|
66
|
+
// Use viewport‑relative coordinates – NO scrollY/scrollX added
|
|
68
67
|
switch (position) {
|
|
69
68
|
case 'top':
|
|
70
|
-
styles.top = rect.top
|
|
71
|
-
styles.left = rect.left +
|
|
72
|
-
styles.transform = 'translateX(-50%)';
|
|
69
|
+
styles.top = rect.top;
|
|
70
|
+
styles.left = rect.left + rect.width / 2;
|
|
71
|
+
styles.transform = 'translateX(-50%) translateY(-100%)'; // place above, centered
|
|
73
72
|
break;
|
|
74
73
|
case 'bottom':
|
|
75
|
-
styles.top = rect.bottom
|
|
76
|
-
styles.left = rect.left +
|
|
74
|
+
styles.top = rect.bottom;
|
|
75
|
+
styles.left = rect.left + rect.width / 2;
|
|
77
76
|
styles.transform = 'translateX(-50%)';
|
|
78
77
|
break;
|
|
79
78
|
case 'top-left':
|
|
80
|
-
styles.top = rect.top
|
|
81
|
-
styles.left = rect.left
|
|
79
|
+
styles.top = rect.top;
|
|
80
|
+
styles.left = rect.left;
|
|
81
|
+
styles.transform = 'translateY(-100%)'; // place above
|
|
82
82
|
break;
|
|
83
83
|
case 'top-right':
|
|
84
|
-
styles.top = rect.top
|
|
85
|
-
styles.left = rect.right
|
|
84
|
+
styles.top = rect.top;
|
|
85
|
+
styles.left = rect.right;
|
|
86
|
+
styles.transform = 'translateY(-100%)'; // place above
|
|
86
87
|
break;
|
|
87
88
|
case 'bottom-left':
|
|
88
|
-
styles.top = rect.bottom
|
|
89
|
-
styles.left = rect.left
|
|
89
|
+
styles.top = rect.bottom;
|
|
90
|
+
styles.left = rect.left;
|
|
90
91
|
break;
|
|
91
92
|
case 'bottom-right':
|
|
92
|
-
styles.top = rect.bottom
|
|
93
|
-
styles.left = rect.right
|
|
93
|
+
styles.top = rect.bottom;
|
|
94
|
+
styles.left = rect.right;
|
|
94
95
|
break;
|
|
95
96
|
case 'left':
|
|
96
|
-
styles.top = rect.top
|
|
97
|
-
styles.left = rect.left
|
|
97
|
+
styles.top = rect.top;
|
|
98
|
+
styles.left = rect.left;
|
|
99
|
+
styles.transform = 'translateX(-100%)'; // place to the left
|
|
98
100
|
break;
|
|
99
101
|
case 'right':
|
|
100
|
-
styles.top = rect.top
|
|
101
|
-
styles.left = rect.right
|
|
102
|
+
styles.top = rect.top;
|
|
103
|
+
styles.left = rect.right;
|
|
102
104
|
break;
|
|
103
105
|
}
|
|
104
106
|
}
|
|
107
|
+
else {
|
|
108
|
+
// Non‑portal mode: use absolute positioning inside the (assumed) relative container
|
|
109
|
+
// You might need to ensure the container has position: relative.
|
|
110
|
+
styles.position = 'absolute';
|
|
111
|
+
// Here you would use offset values relative to the container.
|
|
112
|
+
// For simplicity, we'll just copy the same logic without scroll offsets,
|
|
113
|
+
// but note that getBoundingClientRect gives viewport coordinates,
|
|
114
|
+
// which are not directly usable with absolute positioning unless the container is fixed/absolute.
|
|
115
|
+
// A production version would need a more robust approach.
|
|
116
|
+
// For now, we keep the original (flawed) logic, but you should consider
|
|
117
|
+
// using a proper positioning library or always using the portal.
|
|
118
|
+
var containerRect = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
119
|
+
if (containerRect) {
|
|
120
|
+
var relativeTop = rect.top - containerRect.top;
|
|
121
|
+
var relativeLeft = rect.left - containerRect.left;
|
|
122
|
+
switch (position) {
|
|
123
|
+
case 'top':
|
|
124
|
+
styles.top = relativeTop;
|
|
125
|
+
styles.left = relativeLeft + rect.width / 2;
|
|
126
|
+
styles.transform = 'translateX(-50%) translateY(-100%)';
|
|
127
|
+
break;
|
|
128
|
+
case 'bottom':
|
|
129
|
+
styles.top = rect.bottom - containerRect.top;
|
|
130
|
+
styles.left = relativeLeft + rect.width / 2;
|
|
131
|
+
styles.transform = 'translateX(-50%)';
|
|
132
|
+
break;
|
|
133
|
+
case 'top-left':
|
|
134
|
+
styles.top = relativeTop;
|
|
135
|
+
styles.left = relativeLeft;
|
|
136
|
+
styles.transform = 'translateY(-100%)';
|
|
137
|
+
break;
|
|
138
|
+
case 'top-right':
|
|
139
|
+
styles.top = relativeTop;
|
|
140
|
+
styles.left = rect.right - containerRect.left;
|
|
141
|
+
styles.transform = 'translateY(-100%)';
|
|
142
|
+
break;
|
|
143
|
+
case 'bottom-left':
|
|
144
|
+
styles.top = rect.bottom - containerRect.top;
|
|
145
|
+
styles.left = relativeLeft;
|
|
146
|
+
break;
|
|
147
|
+
case 'bottom-right':
|
|
148
|
+
styles.top = rect.bottom - containerRect.top;
|
|
149
|
+
styles.left = rect.right - containerRect.left;
|
|
150
|
+
break;
|
|
151
|
+
case 'left':
|
|
152
|
+
styles.top = relativeTop;
|
|
153
|
+
styles.left = relativeLeft;
|
|
154
|
+
styles.transform = 'translateX(-100%)';
|
|
155
|
+
break;
|
|
156
|
+
case 'right':
|
|
157
|
+
styles.top = relativeTop;
|
|
158
|
+
styles.left = rect.right - containerRect.left;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
105
163
|
return styles;
|
|
106
164
|
};
|
|
107
165
|
setMenuStyle(calculatePosition());
|
|
@@ -135,7 +193,7 @@ var Dropdown = function (_a) {
|
|
|
135
193
|
}
|
|
136
194
|
return undefined;
|
|
137
195
|
}, [open]);
|
|
138
|
-
// Handle scroll to hide dropdown
|
|
196
|
+
// Handle scroll to hide dropdown (optional – you may remove this if you want it to stay open)
|
|
139
197
|
(0, react_1.useEffect)(function () {
|
|
140
198
|
var handleScroll = function () {
|
|
141
199
|
if (open) {
|
package/ui/richtext/RichText.js
CHANGED
|
@@ -1,3 +1,652 @@
|
|
|
1
|
+
// 'use client';
|
|
2
|
+
// import React, { useEffect, useRef, useCallback, useState, useMemo } from 'react';
|
|
3
|
+
// import { useQuill } from 'react-quilljs';
|
|
4
|
+
// import { MdOutlineEmojiEmotions } from 'react-icons/md';
|
|
5
|
+
// import { AllEmojis } from '../../utils/Emojis';
|
|
6
|
+
// import Dropdown from '../drop/Dropdown';
|
|
7
|
+
// import RowFlex from '../specials/RowFlex';
|
|
8
|
+
// import ToolTip from '../tooltip/ToolTip';
|
|
9
|
+
// import Circle from '../specials/Circle';
|
|
10
|
+
// import Tip from '../tooltip/Tip';
|
|
11
|
+
// import Flex from '../flex/Flex';
|
|
12
|
+
// type RangeStatic = {
|
|
13
|
+
// index: number;
|
|
14
|
+
// length: number;
|
|
15
|
+
// };
|
|
16
|
+
// interface RichTextProps {
|
|
17
|
+
// value: string;
|
|
18
|
+
// onChange: (content: string) => void;
|
|
19
|
+
// showEmojis?: boolean;
|
|
20
|
+
// placeholder?: string;
|
|
21
|
+
// afterEmoji?: React.ReactNode;
|
|
22
|
+
// funcss?: string;
|
|
23
|
+
// modules?: any;
|
|
24
|
+
// theme?: 'bubble' | 'snow';
|
|
25
|
+
// fontFamily?: string;
|
|
26
|
+
// maxValue?: number;
|
|
27
|
+
// }
|
|
28
|
+
// const RichText: React.FC<RichTextProps> = ({
|
|
29
|
+
// value,
|
|
30
|
+
// onChange,
|
|
31
|
+
// showEmojis = false,
|
|
32
|
+
// placeholder = 'Write something...',
|
|
33
|
+
// afterEmoji,
|
|
34
|
+
// funcss = '',
|
|
35
|
+
// modules,
|
|
36
|
+
// theme = 'bubble',
|
|
37
|
+
// fontFamily,
|
|
38
|
+
// maxValue,
|
|
39
|
+
// }) => {
|
|
40
|
+
// const savedRange = useRef<RangeStatic | null>(null);
|
|
41
|
+
// const onChangeRef = useRef(onChange);
|
|
42
|
+
// const maxValueRef = useRef(maxValue);
|
|
43
|
+
// const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
44
|
+
// const isInitialMount = useRef(true);
|
|
45
|
+
// const isTypingRef = useRef(false);
|
|
46
|
+
// const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
47
|
+
// const isUserChangeRef = useRef(false);
|
|
48
|
+
// const [isFocused, setIsFocused] = useState(false);
|
|
49
|
+
// const lastKnownValueRef = useRef(value);
|
|
50
|
+
// const isEditorInitializedRef = useRef(false);
|
|
51
|
+
// const isUpdatingFromPropsRef = useRef(false);
|
|
52
|
+
// const forceUpdateRef = useRef(0);
|
|
53
|
+
// // Store the value prop in a ref to compare with internal state
|
|
54
|
+
// const valuePropRef = useRef(value);
|
|
55
|
+
// // Update refs when props change
|
|
56
|
+
// useEffect(() => {
|
|
57
|
+
// onChangeRef.current = onChange;
|
|
58
|
+
// maxValueRef.current = maxValue;
|
|
59
|
+
// }, [onChange, maxValue]);
|
|
60
|
+
// const defaultModules = useMemo(() => ({
|
|
61
|
+
// toolbar: [['bold', 'italic', 'underline'], [{ list: 'bullet' }]],
|
|
62
|
+
// }), []);
|
|
63
|
+
// const { quill, quillRef } = useQuill({
|
|
64
|
+
// theme,
|
|
65
|
+
// placeholder,
|
|
66
|
+
// modules: modules || defaultModules,
|
|
67
|
+
// });
|
|
68
|
+
// // Clean HTML helper function
|
|
69
|
+
// const cleanHTML = useCallback((html: string): string => {
|
|
70
|
+
// if (!html) return '';
|
|
71
|
+
// return html
|
|
72
|
+
// .replace(/<p><br><\/p>/g, '')
|
|
73
|
+
// .replace(/\s+/g, ' ')
|
|
74
|
+
// .trim();
|
|
75
|
+
// }, []);
|
|
76
|
+
// // Debounced onChange handler
|
|
77
|
+
// const debouncedOnChange = useCallback((content: string) => {
|
|
78
|
+
// if (debounceTimeoutRef.current) {
|
|
79
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
80
|
+
// }
|
|
81
|
+
// debounceTimeoutRef.current = setTimeout(() => {
|
|
82
|
+
// onChangeRef.current(content);
|
|
83
|
+
// }, 300); // 300ms debounce delay
|
|
84
|
+
// }, []);
|
|
85
|
+
// // Handle text change with debouncing
|
|
86
|
+
// const handleTextChange = useCallback((delta: any, oldDelta: any, source: string) => {
|
|
87
|
+
// if (!quill || source !== 'user') return;
|
|
88
|
+
// // Skip if we're updating from props
|
|
89
|
+
// if (isUpdatingFromPropsRef.current) {
|
|
90
|
+
// isUpdatingFromPropsRef.current = false;
|
|
91
|
+
// return;
|
|
92
|
+
// }
|
|
93
|
+
// isUserChangeRef.current = true;
|
|
94
|
+
// isTypingRef.current = true;
|
|
95
|
+
// // Reset typing flag after 500ms of inactivity
|
|
96
|
+
// if (typingTimeoutRef.current) {
|
|
97
|
+
// clearTimeout(typingTimeoutRef.current);
|
|
98
|
+
// }
|
|
99
|
+
// typingTimeoutRef.current = setTimeout(() => {
|
|
100
|
+
// isTypingRef.current = false;
|
|
101
|
+
// }, 500);
|
|
102
|
+
// const plainText = quill.getText().trim();
|
|
103
|
+
// const currentHTML = quill.root.innerHTML;
|
|
104
|
+
// // --- Enforce maxValue if needed ---
|
|
105
|
+
// if (maxValueRef.current && plainText.length > maxValueRef.current) {
|
|
106
|
+
// // Store current selection
|
|
107
|
+
// const selection = quill.getSelection();
|
|
108
|
+
// // Remove the extra content
|
|
109
|
+
// quill.deleteText(maxValueRef.current, plainText.length - maxValueRef.current);
|
|
110
|
+
// // Restore selection if it was at the end
|
|
111
|
+
// if (selection && selection.index > maxValueRef.current) {
|
|
112
|
+
// quill.setSelection(maxValueRef.current);
|
|
113
|
+
// }
|
|
114
|
+
// return; // Don't trigger onChange for truncated text
|
|
115
|
+
// }
|
|
116
|
+
// // --- Clean the HTML output ---
|
|
117
|
+
// const cleanedHTML = cleanHTML(currentHTML);
|
|
118
|
+
// lastKnownValueRef.current = cleanedHTML;
|
|
119
|
+
// // Update value prop ref
|
|
120
|
+
// valuePropRef.current = cleanedHTML;
|
|
121
|
+
// debouncedOnChange(cleanedHTML);
|
|
122
|
+
// }, [quill, debouncedOnChange, cleanHTML]);
|
|
123
|
+
// // Handle selection change
|
|
124
|
+
// const handleSelectionChange = useCallback((range: RangeStatic | null) => {
|
|
125
|
+
// if (range) savedRange.current = range;
|
|
126
|
+
// }, []);
|
|
127
|
+
// // Handle focus
|
|
128
|
+
// const handleFocus = useCallback(() => {
|
|
129
|
+
// setIsFocused(true);
|
|
130
|
+
// isUserChangeRef.current = false;
|
|
131
|
+
// }, []);
|
|
132
|
+
// // Handle blur
|
|
133
|
+
// const handleBlur = useCallback(() => {
|
|
134
|
+
// setIsFocused(false);
|
|
135
|
+
// isTypingRef.current = false;
|
|
136
|
+
// isUserChangeRef.current = false;
|
|
137
|
+
// }, []);
|
|
138
|
+
// // Initialize editor with value
|
|
139
|
+
// const initializeEditorWithValue = useCallback(() => {
|
|
140
|
+
// if (!quill) return;
|
|
141
|
+
// const cleanedValue = cleanHTML(value);
|
|
142
|
+
// if (cleanedValue && cleanedValue !== '<p><br></p>' && cleanedValue !== '<p></p>') {
|
|
143
|
+
// quill.clipboard.dangerouslyPasteHTML(0, cleanedValue);
|
|
144
|
+
// }
|
|
145
|
+
// lastKnownValueRef.current = cleanedValue;
|
|
146
|
+
// valuePropRef.current = cleanedValue;
|
|
147
|
+
// }, [quill, value, cleanHTML]);
|
|
148
|
+
// // Update editor content from props
|
|
149
|
+
// const updateEditorFromProps = useCallback(() => {
|
|
150
|
+
// if (!quill || !isEditorInitializedRef.current) return;
|
|
151
|
+
// // Clean the incoming value
|
|
152
|
+
// const cleanedValue = cleanHTML(value);
|
|
153
|
+
// // Get current editor content
|
|
154
|
+
// const currentHTML = quill.root.innerHTML;
|
|
155
|
+
// const currentCleaned = cleanHTML(currentHTML);
|
|
156
|
+
// // Only update if values are different and not empty paragraphs
|
|
157
|
+
// if (cleanedValue !== currentCleaned && cleanedValue !== '<p><br></p>' && cleanedValue !== '<p></p>') {
|
|
158
|
+
// // Mark that we're updating from props
|
|
159
|
+
// isUpdatingFromPropsRef.current = true;
|
|
160
|
+
// // Store selection
|
|
161
|
+
// const selection = quill.getSelection();
|
|
162
|
+
// // Calculate position to set cursor
|
|
163
|
+
// const cursorPosition = Math.min(
|
|
164
|
+
// selection?.index || 0,
|
|
165
|
+
// cleanedValue.replace(/<[^>]*>/g, '').length
|
|
166
|
+
// );
|
|
167
|
+
// // Replace content
|
|
168
|
+
// quill.setText('');
|
|
169
|
+
// if (cleanedValue) {
|
|
170
|
+
// quill.clipboard.dangerouslyPasteHTML(0, cleanedValue);
|
|
171
|
+
// }
|
|
172
|
+
// // Update refs
|
|
173
|
+
// lastKnownValueRef.current = cleanedValue;
|
|
174
|
+
// valuePropRef.current = cleanedValue;
|
|
175
|
+
// // Restore cursor position
|
|
176
|
+
// if (quill.hasFocus()) {
|
|
177
|
+
// setTimeout(() => {
|
|
178
|
+
// quill.setSelection(cursorPosition);
|
|
179
|
+
// }, 0);
|
|
180
|
+
// }
|
|
181
|
+
// }
|
|
182
|
+
// }, [quill, value, cleanHTML]);
|
|
183
|
+
// // Set up event listeners
|
|
184
|
+
// useEffect(() => {
|
|
185
|
+
// if (!quill) return;
|
|
186
|
+
// const editor = quill.root;
|
|
187
|
+
// quill.on('selection-change', handleSelectionChange);
|
|
188
|
+
// quill.on('text-change', handleTextChange);
|
|
189
|
+
// // Add focus/blur event listeners
|
|
190
|
+
// editor.addEventListener('focus', handleFocus);
|
|
191
|
+
// editor.addEventListener('blur', handleBlur);
|
|
192
|
+
// // Initialize editor with initial value on first mount
|
|
193
|
+
// if (isInitialMount.current) {
|
|
194
|
+
// initializeEditorWithValue();
|
|
195
|
+
// isInitialMount.current = false;
|
|
196
|
+
// }
|
|
197
|
+
// isEditorInitializedRef.current = true;
|
|
198
|
+
// return () => {
|
|
199
|
+
// quill.off('selection-change', handleSelectionChange);
|
|
200
|
+
// quill.off('text-change', handleTextChange);
|
|
201
|
+
// // Remove focus/blur event listeners
|
|
202
|
+
// editor.removeEventListener('focus', handleFocus);
|
|
203
|
+
// editor.removeEventListener('blur', handleBlur);
|
|
204
|
+
// // Clean up timeouts
|
|
205
|
+
// if (debounceTimeoutRef.current) {
|
|
206
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
207
|
+
// }
|
|
208
|
+
// if (typingTimeoutRef.current) {
|
|
209
|
+
// clearTimeout(typingTimeoutRef.current);
|
|
210
|
+
// }
|
|
211
|
+
// };
|
|
212
|
+
// }, [quill, handleSelectionChange, handleTextChange, handleFocus, handleBlur, initializeEditorWithValue]);
|
|
213
|
+
// // Update editor when value prop changes (for external updates)
|
|
214
|
+
// useEffect(() => {
|
|
215
|
+
// if (!quill || !isEditorInitializedRef.current) return;
|
|
216
|
+
// // Skip if value hasn't changed
|
|
217
|
+
// if (value === valuePropRef.current) return;
|
|
218
|
+
// // Don't update while user is typing
|
|
219
|
+
// if (isTypingRef.current) {
|
|
220
|
+
// // If user is typing but value changed significantly (like from template selection),
|
|
221
|
+
// // we should still update
|
|
222
|
+
// const currentPlain = quill.getText();
|
|
223
|
+
// const newPlain = value.replace(/<[^>]*>/g, '');
|
|
224
|
+
// // If the plain text is significantly different, update anyway
|
|
225
|
+
// if (Math.abs(currentPlain.length - newPlain.length) > 10 ||
|
|
226
|
+
// !currentPlain.includes(newPlain.substring(0, 20)) &&
|
|
227
|
+
// !newPlain.includes(currentPlain.substring(0, 20))) {
|
|
228
|
+
// updateEditorFromProps();
|
|
229
|
+
// }
|
|
230
|
+
// return;
|
|
231
|
+
// }
|
|
232
|
+
// // Don't update if editor is focused and user recently made changes
|
|
233
|
+
// if (isFocused && isUserChangeRef.current) {
|
|
234
|
+
// return;
|
|
235
|
+
// }
|
|
236
|
+
// updateEditorFromProps();
|
|
237
|
+
// }, [quill, value, isFocused, updateEditorFromProps]);
|
|
238
|
+
// // Force update when forceUpdateRef changes (for external reset)
|
|
239
|
+
// useEffect(() => {
|
|
240
|
+
// if (quill && isEditorInitializedRef.current) {
|
|
241
|
+
// updateEditorFromProps();
|
|
242
|
+
// }
|
|
243
|
+
// }, [forceUpdateRef.current, quill, updateEditorFromProps]);
|
|
244
|
+
// const insertEmoji = useCallback((emoji: string) => {
|
|
245
|
+
// if (quill) {
|
|
246
|
+
// // Use current selection if available, otherwise use saved range or end of text
|
|
247
|
+
// const selection = quill.getSelection();
|
|
248
|
+
// const plainText = quill.getText().trim();
|
|
249
|
+
// if (!maxValueRef.current || plainText.length + emoji.length <= maxValueRef.current) {
|
|
250
|
+
// let insertIndex;
|
|
251
|
+
// if (selection) {
|
|
252
|
+
// insertIndex = selection.index;
|
|
253
|
+
// } else if (savedRange.current) {
|
|
254
|
+
// insertIndex = savedRange.current.index;
|
|
255
|
+
// } else {
|
|
256
|
+
// insertIndex = quill.getLength();
|
|
257
|
+
// }
|
|
258
|
+
// // Mark as user change
|
|
259
|
+
// isUserChangeRef.current = true;
|
|
260
|
+
// quill.insertText(insertIndex, emoji);
|
|
261
|
+
// quill.setSelection(insertIndex + emoji.length);
|
|
262
|
+
// // Update saved range
|
|
263
|
+
// savedRange.current = {
|
|
264
|
+
// index: insertIndex + emoji.length,
|
|
265
|
+
// length: 0
|
|
266
|
+
// };
|
|
267
|
+
// // Focus the editor
|
|
268
|
+
// quill.focus();
|
|
269
|
+
// // Update refs
|
|
270
|
+
// const currentHTML = quill.root.innerHTML;
|
|
271
|
+
// const cleanedHTML = cleanHTML(currentHTML);
|
|
272
|
+
// lastKnownValueRef.current = cleanedHTML;
|
|
273
|
+
// valuePropRef.current = cleanedHTML;
|
|
274
|
+
// debouncedOnChange(cleanedHTML);
|
|
275
|
+
// }
|
|
276
|
+
// }
|
|
277
|
+
// }, [quill, cleanHTML, debouncedOnChange]);
|
|
278
|
+
// const renderEmojiSection = useCallback((title: string, emojis: string[]) => (
|
|
279
|
+
// <>
|
|
280
|
+
// <div className="mb-2 mt-2 text-sm">{title}</div>
|
|
281
|
+
// <RowFlex gap={0.3}>
|
|
282
|
+
// {emojis.map((emoji, i) => (
|
|
283
|
+
// <span
|
|
284
|
+
// key={i}
|
|
285
|
+
// className="h6 pointer"
|
|
286
|
+
// onClick={() => insertEmoji(emoji)}
|
|
287
|
+
// >
|
|
288
|
+
// {emoji}
|
|
289
|
+
// </span>
|
|
290
|
+
// ))}
|
|
291
|
+
// </RowFlex>
|
|
292
|
+
// </>
|
|
293
|
+
// ), [insertEmoji]);
|
|
294
|
+
// return (
|
|
295
|
+
// <div
|
|
296
|
+
// className={`fit round-edge ${funcss}`}
|
|
297
|
+
// style={{ position: 'relative', overflow: 'visible' }}
|
|
298
|
+
// >
|
|
299
|
+
// <div id="editor-container" className="bubble-editor-container p-0">
|
|
300
|
+
// <div
|
|
301
|
+
// ref={quillRef}
|
|
302
|
+
// className={theme === 'bubble' ? 'bubble-editor' : 'snow-editor'}
|
|
303
|
+
// style={{
|
|
304
|
+
// fontFamily: fontFamily || 'inherit',
|
|
305
|
+
// }}
|
|
306
|
+
// />
|
|
307
|
+
// </div>
|
|
308
|
+
// {(showEmojis || maxValue) && (
|
|
309
|
+
// <div
|
|
310
|
+
// className="p-1"
|
|
311
|
+
// style={{ height: 'fit-content', top: `calc(100%)`, width: '100%' }}
|
|
312
|
+
// >
|
|
313
|
+
// <Flex justify="space-between" gap={1} alignItems="center" width="100%">
|
|
314
|
+
// {(showEmojis || afterEmoji) ? (
|
|
315
|
+
// <div>
|
|
316
|
+
// <Flex width="100%" gap={0.5} alignItems="center">
|
|
317
|
+
// {showEmojis && (
|
|
318
|
+
// <Dropdown
|
|
319
|
+
// closableOnlyOutside
|
|
320
|
+
// button={
|
|
321
|
+
// <ToolTip>
|
|
322
|
+
// <Circle size={2} funcss="bg border">
|
|
323
|
+
// <MdOutlineEmojiEmotions />
|
|
324
|
+
// </Circle>
|
|
325
|
+
// <Tip
|
|
326
|
+
// tip="top"
|
|
327
|
+
// animation="ScaleUp"
|
|
328
|
+
// duration={0.5}
|
|
329
|
+
// content="Emojis"
|
|
330
|
+
// />
|
|
331
|
+
// </ToolTip>
|
|
332
|
+
// }
|
|
333
|
+
// items={[
|
|
334
|
+
// {
|
|
335
|
+
// label: (
|
|
336
|
+
// <div
|
|
337
|
+
// className="w-200 h-200"
|
|
338
|
+
// style={{ overflowY: 'auto' }}
|
|
339
|
+
// >
|
|
340
|
+
// {renderEmojiSection('❤️ Smileys & People', AllEmojis.Smiley)}
|
|
341
|
+
// {renderEmojiSection('👍 Gestures & Body Parts', AllEmojis.Gesture)}
|
|
342
|
+
// {renderEmojiSection('🔥 Symbols & Expressions', AllEmojis.Symbols)}
|
|
343
|
+
// {renderEmojiSection('🚀 Travel, Objects & Activities', AllEmojis.Travel)}
|
|
344
|
+
// {renderEmojiSection('👨👩👧👦 People & Professions', AllEmojis.People)}
|
|
345
|
+
// {renderEmojiSection('🐶 Animals & Nature', AllEmojis.Animals)}
|
|
346
|
+
// </div>
|
|
347
|
+
// ),
|
|
348
|
+
// },
|
|
349
|
+
// ]}
|
|
350
|
+
// />
|
|
351
|
+
// )}
|
|
352
|
+
// {afterEmoji}
|
|
353
|
+
// </Flex>
|
|
354
|
+
// </div>
|
|
355
|
+
// ) : (
|
|
356
|
+
// <div />
|
|
357
|
+
// )}
|
|
358
|
+
// {maxValue && quill ? (
|
|
359
|
+
// <div className="text-xs text-right">
|
|
360
|
+
// <span className="text-primary">
|
|
361
|
+
// {quill.getText().trim().length}
|
|
362
|
+
// </span>
|
|
363
|
+
// /{maxValue}
|
|
364
|
+
// </div>
|
|
365
|
+
// ) : (
|
|
366
|
+
// <div />
|
|
367
|
+
// )}
|
|
368
|
+
// </Flex>
|
|
369
|
+
// </div>
|
|
370
|
+
// )}
|
|
371
|
+
// </div>
|
|
372
|
+
// );
|
|
373
|
+
// };
|
|
374
|
+
// export default RichText;
|
|
375
|
+
// 'use client';
|
|
376
|
+
// import React, { useEffect, useRef, useCallback, useState } from 'react';
|
|
377
|
+
// import { useQuill } from 'react-quilljs';
|
|
378
|
+
// import { MdOutlineEmojiEmotions } from 'react-icons/md';
|
|
379
|
+
// import { AllEmojis } from '../../utils/Emojis';
|
|
380
|
+
// import Dropdown from '../drop/Dropdown';
|
|
381
|
+
// import RowFlex from '../specials/RowFlex';
|
|
382
|
+
// import ToolTip from '../tooltip/ToolTip';
|
|
383
|
+
// import Circle from '../specials/Circle';
|
|
384
|
+
// import Tip from '../tooltip/Tip';
|
|
385
|
+
// import Flex from '../flex/Flex';
|
|
386
|
+
// type RangeStatic = {
|
|
387
|
+
// index: number;
|
|
388
|
+
// length: number;
|
|
389
|
+
// };
|
|
390
|
+
// interface RichTextProps {
|
|
391
|
+
// value: string;
|
|
392
|
+
// onChange: (content: string) => void;
|
|
393
|
+
// showEmojis?: boolean;
|
|
394
|
+
// placeholder?: string;
|
|
395
|
+
// afterEmoji?: React.ReactNode;
|
|
396
|
+
// funcss?: string;
|
|
397
|
+
// modules?: any;
|
|
398
|
+
// theme?: 'bubble' | 'snow';
|
|
399
|
+
// fontFamily?: string;
|
|
400
|
+
// maxValue?: number;
|
|
401
|
+
// }
|
|
402
|
+
// const RichText: React.FC<RichTextProps> = ({
|
|
403
|
+
// value,
|
|
404
|
+
// onChange,
|
|
405
|
+
// showEmojis = false,
|
|
406
|
+
// placeholder = 'Write something...',
|
|
407
|
+
// afterEmoji,
|
|
408
|
+
// funcss = '',
|
|
409
|
+
// modules,
|
|
410
|
+
// theme = 'bubble',
|
|
411
|
+
// fontFamily,
|
|
412
|
+
// maxValue,
|
|
413
|
+
// }) => {
|
|
414
|
+
// const savedRange = useRef<RangeStatic | null>(null);
|
|
415
|
+
// const onChangeRef = useRef(onChange);
|
|
416
|
+
// const maxValueRef = useRef(maxValue);
|
|
417
|
+
// const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
418
|
+
// const isInitialMount = useRef(true);
|
|
419
|
+
// const isTypingRef = useRef(false);
|
|
420
|
+
// const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
421
|
+
// const [isFocused, setIsFocused] = useState(false);
|
|
422
|
+
// const lastKnownValueRef = useRef(value);
|
|
423
|
+
// // Update refs when props change
|
|
424
|
+
// useEffect(() => {
|
|
425
|
+
// onChangeRef.current = onChange;
|
|
426
|
+
// maxValueRef.current = maxValue;
|
|
427
|
+
// }, [onChange, maxValue]);
|
|
428
|
+
// const defaultModules = {
|
|
429
|
+
// toolbar: [['bold', 'italic', 'underline'], [{ list: 'bullet' }]],
|
|
430
|
+
// };
|
|
431
|
+
// const { quill, quillRef } = useQuill({
|
|
432
|
+
// theme,
|
|
433
|
+
// placeholder,
|
|
434
|
+
// modules: modules || defaultModules,
|
|
435
|
+
// });
|
|
436
|
+
// // Debounced onChange handler
|
|
437
|
+
// const debouncedOnChange = useCallback((content: string) => {
|
|
438
|
+
// if (debounceTimeoutRef.current) {
|
|
439
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
440
|
+
// }
|
|
441
|
+
// debounceTimeoutRef.current = setTimeout(() => {
|
|
442
|
+
// onChangeRef.current(content);
|
|
443
|
+
// }, 300); // 300ms debounce delay
|
|
444
|
+
// }, []);
|
|
445
|
+
// // Handle text change with debouncing
|
|
446
|
+
// const handleTextChange = useCallback(() => {
|
|
447
|
+
// if (!quill) return;
|
|
448
|
+
// isTypingRef.current = true;
|
|
449
|
+
// // Reset typing flag after 500ms of inactivity
|
|
450
|
+
// if (typingTimeoutRef.current) {
|
|
451
|
+
// clearTimeout(typingTimeoutRef.current);
|
|
452
|
+
// }
|
|
453
|
+
// typingTimeoutRef.current = setTimeout(() => {
|
|
454
|
+
// isTypingRef.current = false;
|
|
455
|
+
// }, 500);
|
|
456
|
+
// const plainText = quill.getText().trim();
|
|
457
|
+
// // --- Enforce maxValue if needed ---
|
|
458
|
+
// if (maxValueRef.current && plainText.length > maxValueRef.current) {
|
|
459
|
+
// const truncated = plainText.slice(0, maxValueRef.current);
|
|
460
|
+
// quill.setText(truncated);
|
|
461
|
+
// quill.setSelection(truncated.length);
|
|
462
|
+
// return; // Don't trigger onChange for truncated text
|
|
463
|
+
// }
|
|
464
|
+
// // --- Clean the HTML output ---
|
|
465
|
+
// const cleanedHTML = quill.root.innerHTML
|
|
466
|
+
// ?.replace(/<p><br><\/p>/g, '') // remove empty paragraphs
|
|
467
|
+
// ?.replace(/\s+/g, ' ') // collapse multiple spaces
|
|
468
|
+
// ?.trim(); // remove leading/trailing spaces
|
|
469
|
+
// lastKnownValueRef.current = cleanedHTML || '';
|
|
470
|
+
// debouncedOnChange(lastKnownValueRef.current);
|
|
471
|
+
// }, [quill, debouncedOnChange]);
|
|
472
|
+
// // Handle selection change
|
|
473
|
+
// const handleSelectionChange = useCallback((range: RangeStatic | null) => {
|
|
474
|
+
// if (range) savedRange.current = range;
|
|
475
|
+
// }, []);
|
|
476
|
+
// // Handle focus
|
|
477
|
+
// const handleFocus = useCallback(() => {
|
|
478
|
+
// setIsFocused(true);
|
|
479
|
+
// }, []);
|
|
480
|
+
// // Handle blur
|
|
481
|
+
// const handleBlur = useCallback(() => {
|
|
482
|
+
// setIsFocused(false);
|
|
483
|
+
// isTypingRef.current = false;
|
|
484
|
+
// }, []);
|
|
485
|
+
// // Set up event listeners
|
|
486
|
+
// useEffect(() => {
|
|
487
|
+
// if (!quill) return;
|
|
488
|
+
// const editor = quill.root;
|
|
489
|
+
// quill.on('selection-change', handleSelectionChange);
|
|
490
|
+
// quill.on('text-change', handleTextChange);
|
|
491
|
+
// // Add focus/blur event listeners
|
|
492
|
+
// editor.addEventListener('focus', handleFocus);
|
|
493
|
+
// editor.addEventListener('blur', handleBlur);
|
|
494
|
+
// return () => {
|
|
495
|
+
// quill.off('selection-change', handleSelectionChange);
|
|
496
|
+
// quill.off('text-change', handleTextChange);
|
|
497
|
+
// // Remove focus/blur event listeners
|
|
498
|
+
// editor.removeEventListener('focus', handleFocus);
|
|
499
|
+
// editor.removeEventListener('blur', handleBlur);
|
|
500
|
+
// // Clean up timeouts
|
|
501
|
+
// if (debounceTimeoutRef.current) {
|
|
502
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
503
|
+
// }
|
|
504
|
+
// if (typingTimeoutRef.current) {
|
|
505
|
+
// clearTimeout(typingTimeoutRef.current);
|
|
506
|
+
// }
|
|
507
|
+
// };
|
|
508
|
+
// }, [quill, handleSelectionChange, handleTextChange, handleFocus, handleBlur]);
|
|
509
|
+
// // Initialize editor with initial value
|
|
510
|
+
// useEffect(() => {
|
|
511
|
+
// if (!quill) return;
|
|
512
|
+
// // Only set initial value on first mount
|
|
513
|
+
// if (isInitialMount.current && value) {
|
|
514
|
+
// // clean before setting editor value
|
|
515
|
+
// const cleanedValue = value
|
|
516
|
+
// ?.replace(/<p><br><\/p>/g, '')
|
|
517
|
+
// ?.replace(/\s+/g, ' ')
|
|
518
|
+
// ?.trim();
|
|
519
|
+
// quill.root.innerHTML = cleanedValue || '';
|
|
520
|
+
// lastKnownValueRef.current = cleanedValue || '';
|
|
521
|
+
// isInitialMount.current = false;
|
|
522
|
+
// }
|
|
523
|
+
// }, [quill, value]);
|
|
524
|
+
// // Update editor when value prop changes (for external updates)
|
|
525
|
+
// useEffect(() => {
|
|
526
|
+
// if (!quill || isInitialMount.current) return;
|
|
527
|
+
// // Skip if value is the same as current editor content
|
|
528
|
+
// if (value === lastKnownValueRef.current) return;
|
|
529
|
+
// // Don't update while user is typing or editor is focused
|
|
530
|
+
// if (isTypingRef.current || isFocused) {
|
|
531
|
+
// return;
|
|
532
|
+
// }
|
|
533
|
+
// // clean before setting editor value
|
|
534
|
+
// const cleanedValue = value
|
|
535
|
+
// ?.replace(/<p><br><\/p>/g, '')
|
|
536
|
+
// ?.replace(/\s+/g, ' ')
|
|
537
|
+
// ?.trim();
|
|
538
|
+
// if (quill.root.innerHTML !== cleanedValue) {
|
|
539
|
+
// quill.root.innerHTML = cleanedValue || '';
|
|
540
|
+
// lastKnownValueRef.current = cleanedValue || '';
|
|
541
|
+
// }
|
|
542
|
+
// }, [quill, value, isFocused]);
|
|
543
|
+
// const insertEmoji = useCallback((emoji: string) => {
|
|
544
|
+
// if (quill && savedRange.current) {
|
|
545
|
+
// const plainText = quill.getText().trim();
|
|
546
|
+
// if (!maxValueRef.current || plainText.length + emoji.length <= maxValueRef.current) {
|
|
547
|
+
// const selection = quill.getSelection();
|
|
548
|
+
// quill.insertText(savedRange.current.index, emoji);
|
|
549
|
+
// quill.setSelection(savedRange.current.index + emoji.length);
|
|
550
|
+
// }
|
|
551
|
+
// }
|
|
552
|
+
// }, [quill]);
|
|
553
|
+
// const renderEmojiSection = useCallback((title: string, emojis: string[]) => (
|
|
554
|
+
// <>
|
|
555
|
+
// <div className="mb-2 mt-2 text-sm">{title}</div>
|
|
556
|
+
// <RowFlex gap={0.3}>
|
|
557
|
+
// {emojis.map((emoji, i) => (
|
|
558
|
+
// <span
|
|
559
|
+
// key={i}
|
|
560
|
+
// className="h6 pointer"
|
|
561
|
+
// onClick={() => insertEmoji(emoji)}
|
|
562
|
+
// >
|
|
563
|
+
// {emoji}
|
|
564
|
+
// </span>
|
|
565
|
+
// ))}
|
|
566
|
+
// </RowFlex>
|
|
567
|
+
// </>
|
|
568
|
+
// ), [insertEmoji]);
|
|
569
|
+
// return (
|
|
570
|
+
// <div
|
|
571
|
+
// className={`fit round-edge ${funcss}`}
|
|
572
|
+
// style={{ position: 'relative', overflow: 'visible' }}
|
|
573
|
+
// >
|
|
574
|
+
// <div id="editor-container" className="bubble-editor-container p-0">
|
|
575
|
+
// <div
|
|
576
|
+
// ref={quillRef}
|
|
577
|
+
// className={theme === 'bubble' ? 'bubble-editor' : 'snow-editor'}
|
|
578
|
+
// style={{
|
|
579
|
+
// fontFamily: fontFamily || 'inherit',
|
|
580
|
+
// }}
|
|
581
|
+
// />
|
|
582
|
+
// </div>
|
|
583
|
+
// {(showEmojis || maxValue) && (
|
|
584
|
+
// <div
|
|
585
|
+
// className="p-1"
|
|
586
|
+
// style={{ height: 'fit-content', top: `calc(100%)`, width: '100%' }}
|
|
587
|
+
// >
|
|
588
|
+
// <Flex justify="space-between" gap={1} alignItems="center" width="100%">
|
|
589
|
+
// {(showEmojis || afterEmoji) ? (
|
|
590
|
+
// <div>
|
|
591
|
+
// <Flex width="100%" gap={0.5} alignItems="center">
|
|
592
|
+
// {showEmojis && (
|
|
593
|
+
// <Dropdown
|
|
594
|
+
// closableOnlyOutside
|
|
595
|
+
// button={
|
|
596
|
+
// <ToolTip>
|
|
597
|
+
// <Circle size={2} funcss="bg border">
|
|
598
|
+
// <MdOutlineEmojiEmotions />
|
|
599
|
+
// </Circle>
|
|
600
|
+
// <Tip
|
|
601
|
+
// tip="top"
|
|
602
|
+
// animation="ScaleUp"
|
|
603
|
+
// duration={0.5}
|
|
604
|
+
// content="Emojis"
|
|
605
|
+
// />
|
|
606
|
+
// </ToolTip>
|
|
607
|
+
// }
|
|
608
|
+
// items={[
|
|
609
|
+
// {
|
|
610
|
+
// label: (
|
|
611
|
+
// <div
|
|
612
|
+
// className="w-200 h-200"
|
|
613
|
+
// style={{ overflowY: 'auto' }}
|
|
614
|
+
// >
|
|
615
|
+
// {renderEmojiSection('❤️ Smileys & People', AllEmojis.Smiley)}
|
|
616
|
+
// {renderEmojiSection('👍 Gestures & Body Parts', AllEmojis.Gesture)}
|
|
617
|
+
// {renderEmojiSection('🔥 Symbols & Expressions', AllEmojis.Symbols)}
|
|
618
|
+
// {renderEmojiSection('🚀 Travel, Objects & Activities', AllEmojis.Travel)}
|
|
619
|
+
// {renderEmojiSection('👨👩👧👦 People & Professions', AllEmojis.People)}
|
|
620
|
+
// {renderEmojiSection('🐶 Animals & Nature', AllEmojis.Animals)}
|
|
621
|
+
// </div>
|
|
622
|
+
// ),
|
|
623
|
+
// },
|
|
624
|
+
// ]}
|
|
625
|
+
// />
|
|
626
|
+
// )}
|
|
627
|
+
// {afterEmoji}
|
|
628
|
+
// </Flex>
|
|
629
|
+
// </div>
|
|
630
|
+
// ) : (
|
|
631
|
+
// <div />
|
|
632
|
+
// )}
|
|
633
|
+
// {maxValue && quill ? (
|
|
634
|
+
// <div className="text-xs text-right">
|
|
635
|
+
// <span className="text-primary">
|
|
636
|
+
// {quill.getText().trim().length}
|
|
637
|
+
// </span>
|
|
638
|
+
// /{maxValue}
|
|
639
|
+
// </div>
|
|
640
|
+
// ) : (
|
|
641
|
+
// <div />
|
|
642
|
+
// )}
|
|
643
|
+
// </Flex>
|
|
644
|
+
// </div>
|
|
645
|
+
// )}
|
|
646
|
+
// </div>
|
|
647
|
+
// );
|
|
648
|
+
// };
|
|
649
|
+
// export default RichText;
|
|
1
650
|
'use client';
|
|
2
651
|
"use strict";
|
|
3
652
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -50,253 +699,65 @@ var Flex_1 = __importDefault(require("../flex/Flex"));
|
|
|
50
699
|
var RichText = function (_a) {
|
|
51
700
|
var value = _a.value, onChange = _a.onChange, _b = _a.showEmojis, showEmojis = _b === void 0 ? false : _b, _c = _a.placeholder, placeholder = _c === void 0 ? 'Write something...' : _c, afterEmoji = _a.afterEmoji, _d = _a.funcss, funcss = _d === void 0 ? '' : _d, modules = _a.modules, _e = _a.theme, theme = _e === void 0 ? 'bubble' : _e, fontFamily = _a.fontFamily, maxValue = _a.maxValue;
|
|
52
701
|
var savedRange = (0, react_1.useRef)(null);
|
|
53
|
-
var
|
|
54
|
-
var maxValueRef = (0, react_1.useRef)(maxValue);
|
|
55
|
-
var debounceTimeoutRef = (0, react_1.useRef)(null);
|
|
56
|
-
var isInitialMount = (0, react_1.useRef)(true);
|
|
57
|
-
var isTypingRef = (0, react_1.useRef)(false);
|
|
58
|
-
var typingTimeoutRef = (0, react_1.useRef)(null);
|
|
59
|
-
var isUserChangeRef = (0, react_1.useRef)(false);
|
|
60
|
-
var _f = (0, react_1.useState)(false), isFocused = _f[0], setIsFocused = _f[1];
|
|
61
|
-
var lastKnownValueRef = (0, react_1.useRef)(value);
|
|
62
|
-
var isEditorInitializedRef = (0, react_1.useRef)(false);
|
|
63
|
-
var isUpdatingFromPropsRef = (0, react_1.useRef)(false);
|
|
64
|
-
var forceUpdateRef = (0, react_1.useRef)(0);
|
|
65
|
-
// Store the value prop in a ref to compare with internal state
|
|
66
|
-
var valuePropRef = (0, react_1.useRef)(value);
|
|
67
|
-
// Update refs when props change
|
|
68
|
-
(0, react_1.useEffect)(function () {
|
|
69
|
-
onChangeRef.current = onChange;
|
|
70
|
-
maxValueRef.current = maxValue;
|
|
71
|
-
}, [onChange, maxValue]);
|
|
72
|
-
var defaultModules = (0, react_1.useMemo)(function () { return ({
|
|
702
|
+
var defaultModules = {
|
|
73
703
|
toolbar: [['bold', 'italic', 'underline'], [{ list: 'bullet' }]],
|
|
74
|
-
}
|
|
75
|
-
var
|
|
704
|
+
};
|
|
705
|
+
var _f = (0, react_quilljs_1.useQuill)({
|
|
76
706
|
theme: theme,
|
|
77
707
|
placeholder: placeholder,
|
|
78
708
|
modules: modules || defaultModules,
|
|
79
|
-
}), quill =
|
|
80
|
-
// Clean HTML helper function
|
|
81
|
-
var cleanHTML = (0, react_1.useCallback)(function (html) {
|
|
82
|
-
if (!html)
|
|
83
|
-
return '';
|
|
84
|
-
return html
|
|
85
|
-
.replace(/<p><br><\/p>/g, '')
|
|
86
|
-
.replace(/\s+/g, ' ')
|
|
87
|
-
.trim();
|
|
88
|
-
}, []);
|
|
89
|
-
// Debounced onChange handler
|
|
90
|
-
var debouncedOnChange = (0, react_1.useCallback)(function (content) {
|
|
91
|
-
if (debounceTimeoutRef.current) {
|
|
92
|
-
clearTimeout(debounceTimeoutRef.current);
|
|
93
|
-
}
|
|
94
|
-
debounceTimeoutRef.current = setTimeout(function () {
|
|
95
|
-
onChangeRef.current(content);
|
|
96
|
-
}, 300); // 300ms debounce delay
|
|
97
|
-
}, []);
|
|
98
|
-
// Handle text change with debouncing
|
|
99
|
-
var handleTextChange = (0, react_1.useCallback)(function (delta, oldDelta, source) {
|
|
100
|
-
if (!quill || source !== 'user')
|
|
101
|
-
return;
|
|
102
|
-
// Skip if we're updating from props
|
|
103
|
-
if (isUpdatingFromPropsRef.current) {
|
|
104
|
-
isUpdatingFromPropsRef.current = false;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
isUserChangeRef.current = true;
|
|
108
|
-
isTypingRef.current = true;
|
|
109
|
-
// Reset typing flag after 500ms of inactivity
|
|
110
|
-
if (typingTimeoutRef.current) {
|
|
111
|
-
clearTimeout(typingTimeoutRef.current);
|
|
112
|
-
}
|
|
113
|
-
typingTimeoutRef.current = setTimeout(function () {
|
|
114
|
-
isTypingRef.current = false;
|
|
115
|
-
}, 500);
|
|
116
|
-
var plainText = quill.getText().trim();
|
|
117
|
-
var currentHTML = quill.root.innerHTML;
|
|
118
|
-
// --- Enforce maxValue if needed ---
|
|
119
|
-
if (maxValueRef.current && plainText.length > maxValueRef.current) {
|
|
120
|
-
// Store current selection
|
|
121
|
-
var selection = quill.getSelection();
|
|
122
|
-
// Remove the extra content
|
|
123
|
-
quill.deleteText(maxValueRef.current, plainText.length - maxValueRef.current);
|
|
124
|
-
// Restore selection if it was at the end
|
|
125
|
-
if (selection && selection.index > maxValueRef.current) {
|
|
126
|
-
quill.setSelection(maxValueRef.current);
|
|
127
|
-
}
|
|
128
|
-
return; // Don't trigger onChange for truncated text
|
|
129
|
-
}
|
|
130
|
-
// --- Clean the HTML output ---
|
|
131
|
-
var cleanedHTML = cleanHTML(currentHTML);
|
|
132
|
-
lastKnownValueRef.current = cleanedHTML;
|
|
133
|
-
// Update value prop ref
|
|
134
|
-
valuePropRef.current = cleanedHTML;
|
|
135
|
-
debouncedOnChange(cleanedHTML);
|
|
136
|
-
}, [quill, debouncedOnChange, cleanHTML]);
|
|
137
|
-
// Handle selection change
|
|
138
|
-
var handleSelectionChange = (0, react_1.useCallback)(function (range) {
|
|
139
|
-
if (range)
|
|
140
|
-
savedRange.current = range;
|
|
141
|
-
}, []);
|
|
142
|
-
// Handle focus
|
|
143
|
-
var handleFocus = (0, react_1.useCallback)(function () {
|
|
144
|
-
setIsFocused(true);
|
|
145
|
-
isUserChangeRef.current = false;
|
|
146
|
-
}, []);
|
|
147
|
-
// Handle blur
|
|
148
|
-
var handleBlur = (0, react_1.useCallback)(function () {
|
|
149
|
-
setIsFocused(false);
|
|
150
|
-
isTypingRef.current = false;
|
|
151
|
-
isUserChangeRef.current = false;
|
|
152
|
-
}, []);
|
|
153
|
-
// Initialize editor with value
|
|
154
|
-
var initializeEditorWithValue = (0, react_1.useCallback)(function () {
|
|
155
|
-
if (!quill)
|
|
156
|
-
return;
|
|
157
|
-
var cleanedValue = cleanHTML(value);
|
|
158
|
-
if (cleanedValue && cleanedValue !== '<p><br></p>' && cleanedValue !== '<p></p>') {
|
|
159
|
-
quill.clipboard.dangerouslyPasteHTML(0, cleanedValue);
|
|
160
|
-
}
|
|
161
|
-
lastKnownValueRef.current = cleanedValue;
|
|
162
|
-
valuePropRef.current = cleanedValue;
|
|
163
|
-
}, [quill, value, cleanHTML]);
|
|
164
|
-
// Update editor content from props
|
|
165
|
-
var updateEditorFromProps = (0, react_1.useCallback)(function () {
|
|
166
|
-
if (!quill || !isEditorInitializedRef.current)
|
|
167
|
-
return;
|
|
168
|
-
// Clean the incoming value
|
|
169
|
-
var cleanedValue = cleanHTML(value);
|
|
170
|
-
// Get current editor content
|
|
171
|
-
var currentHTML = quill.root.innerHTML;
|
|
172
|
-
var currentCleaned = cleanHTML(currentHTML);
|
|
173
|
-
// Only update if values are different and not empty paragraphs
|
|
174
|
-
if (cleanedValue !== currentCleaned && cleanedValue !== '<p><br></p>' && cleanedValue !== '<p></p>') {
|
|
175
|
-
// Mark that we're updating from props
|
|
176
|
-
isUpdatingFromPropsRef.current = true;
|
|
177
|
-
// Store selection
|
|
178
|
-
var selection = quill.getSelection();
|
|
179
|
-
// Calculate position to set cursor
|
|
180
|
-
var cursorPosition_1 = Math.min((selection === null || selection === void 0 ? void 0 : selection.index) || 0, cleanedValue.replace(/<[^>]*>/g, '').length);
|
|
181
|
-
// Replace content
|
|
182
|
-
quill.setText('');
|
|
183
|
-
if (cleanedValue) {
|
|
184
|
-
quill.clipboard.dangerouslyPasteHTML(0, cleanedValue);
|
|
185
|
-
}
|
|
186
|
-
// Update refs
|
|
187
|
-
lastKnownValueRef.current = cleanedValue;
|
|
188
|
-
valuePropRef.current = cleanedValue;
|
|
189
|
-
// Restore cursor position
|
|
190
|
-
if (quill.hasFocus()) {
|
|
191
|
-
setTimeout(function () {
|
|
192
|
-
quill.setSelection(cursorPosition_1);
|
|
193
|
-
}, 0);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}, [quill, value, cleanHTML]);
|
|
197
|
-
// Set up event listeners
|
|
709
|
+
}), quill = _f.quill, quillRef = _f.quillRef;
|
|
198
710
|
(0, react_1.useEffect)(function () {
|
|
199
711
|
if (!quill)
|
|
200
712
|
return;
|
|
201
|
-
var
|
|
713
|
+
var handleSelectionChange = function (range) {
|
|
714
|
+
if (range)
|
|
715
|
+
savedRange.current = range;
|
|
716
|
+
};
|
|
717
|
+
var handleTextChange = function () {
|
|
718
|
+
var _a, _b, _c;
|
|
719
|
+
if (!quill)
|
|
720
|
+
return;
|
|
721
|
+
var plainText = quill.getText().trim();
|
|
722
|
+
// --- Enforce maxValue if needed ---
|
|
723
|
+
if (maxValue && plainText.length > maxValue) {
|
|
724
|
+
var truncated = plainText.slice(0, maxValue);
|
|
725
|
+
quill.setText(truncated);
|
|
726
|
+
quill.setSelection(truncated.length);
|
|
727
|
+
}
|
|
728
|
+
// --- Clean the HTML output ---
|
|
729
|
+
var cleanedHTML = (_c = (_b = (_a = quill.root.innerHTML) === null || _a === void 0 ? void 0 : _a.replace(/<p><br><\/p>/g, '') // remove empty paragraphs
|
|
730
|
+
) === null || _b === void 0 ? void 0 : _b.replace(/\s+/g, ' ') // collapse multiple spaces
|
|
731
|
+
) === null || _c === void 0 ? void 0 : _c.trim(); // remove leading/trailing spaces
|
|
732
|
+
onChange(cleanedHTML || '');
|
|
733
|
+
};
|
|
202
734
|
quill.on('selection-change', handleSelectionChange);
|
|
203
735
|
quill.on('text-change', handleTextChange);
|
|
204
|
-
// Add focus/blur event listeners
|
|
205
|
-
editor.addEventListener('focus', handleFocus);
|
|
206
|
-
editor.addEventListener('blur', handleBlur);
|
|
207
|
-
// Initialize editor with initial value on first mount
|
|
208
|
-
if (isInitialMount.current) {
|
|
209
|
-
initializeEditorWithValue();
|
|
210
|
-
isInitialMount.current = false;
|
|
211
|
-
}
|
|
212
|
-
isEditorInitializedRef.current = true;
|
|
213
736
|
return function () {
|
|
214
737
|
quill.off('selection-change', handleSelectionChange);
|
|
215
738
|
quill.off('text-change', handleTextChange);
|
|
216
|
-
// Remove focus/blur event listeners
|
|
217
|
-
editor.removeEventListener('focus', handleFocus);
|
|
218
|
-
editor.removeEventListener('blur', handleBlur);
|
|
219
|
-
// Clean up timeouts
|
|
220
|
-
if (debounceTimeoutRef.current) {
|
|
221
|
-
clearTimeout(debounceTimeoutRef.current);
|
|
222
|
-
}
|
|
223
|
-
if (typingTimeoutRef.current) {
|
|
224
|
-
clearTimeout(typingTimeoutRef.current);
|
|
225
|
-
}
|
|
226
739
|
};
|
|
227
|
-
}, [quill,
|
|
228
|
-
// Update editor when value prop changes (for external updates)
|
|
229
|
-
(0, react_1.useEffect)(function () {
|
|
230
|
-
if (!quill || !isEditorInitializedRef.current)
|
|
231
|
-
return;
|
|
232
|
-
// Skip if value hasn't changed
|
|
233
|
-
if (value === valuePropRef.current)
|
|
234
|
-
return;
|
|
235
|
-
// Don't update while user is typing
|
|
236
|
-
if (isTypingRef.current) {
|
|
237
|
-
// If user is typing but value changed significantly (like from template selection),
|
|
238
|
-
// we should still update
|
|
239
|
-
var currentPlain = quill.getText();
|
|
240
|
-
var newPlain = value.replace(/<[^>]*>/g, '');
|
|
241
|
-
// If the plain text is significantly different, update anyway
|
|
242
|
-
if (Math.abs(currentPlain.length - newPlain.length) > 10 ||
|
|
243
|
-
!currentPlain.includes(newPlain.substring(0, 20)) &&
|
|
244
|
-
!newPlain.includes(currentPlain.substring(0, 20))) {
|
|
245
|
-
updateEditorFromProps();
|
|
246
|
-
}
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
// Don't update if editor is focused and user recently made changes
|
|
250
|
-
if (isFocused && isUserChangeRef.current) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
updateEditorFromProps();
|
|
254
|
-
}, [quill, value, isFocused, updateEditorFromProps]);
|
|
255
|
-
// Force update when forceUpdateRef changes (for external reset)
|
|
740
|
+
}, [quill, onChange, maxValue]);
|
|
256
741
|
(0, react_1.useEffect)(function () {
|
|
257
|
-
|
|
258
|
-
|
|
742
|
+
var _a, _b;
|
|
743
|
+
if (quill && value !== quill.root.innerHTML) {
|
|
744
|
+
// clean before setting editor value
|
|
745
|
+
var cleanedValue = (_b = (_a = value === null || value === void 0 ? void 0 : value.replace(/<p><br><\/p>/g, '')) === null || _a === void 0 ? void 0 : _a.replace(/\s+/g, ' ')) === null || _b === void 0 ? void 0 : _b.trim();
|
|
746
|
+
quill.root.innerHTML = cleanedValue || '';
|
|
259
747
|
}
|
|
260
|
-
}, [
|
|
261
|
-
var insertEmoji =
|
|
262
|
-
if (quill) {
|
|
263
|
-
// Use current selection if available, otherwise use saved range or end of text
|
|
264
|
-
var selection = quill.getSelection();
|
|
748
|
+
}, [quill, value]);
|
|
749
|
+
var insertEmoji = function (emoji) {
|
|
750
|
+
if (quill && savedRange.current) {
|
|
265
751
|
var plainText = quill.getText().trim();
|
|
266
|
-
if (!
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
insertIndex = selection.index;
|
|
270
|
-
}
|
|
271
|
-
else if (savedRange.current) {
|
|
272
|
-
insertIndex = savedRange.current.index;
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
insertIndex = quill.getLength();
|
|
276
|
-
}
|
|
277
|
-
// Mark as user change
|
|
278
|
-
isUserChangeRef.current = true;
|
|
279
|
-
quill.insertText(insertIndex, emoji);
|
|
280
|
-
quill.setSelection(insertIndex + emoji.length);
|
|
281
|
-
// Update saved range
|
|
282
|
-
savedRange.current = {
|
|
283
|
-
index: insertIndex + emoji.length,
|
|
284
|
-
length: 0
|
|
285
|
-
};
|
|
286
|
-
// Focus the editor
|
|
287
|
-
quill.focus();
|
|
288
|
-
// Update refs
|
|
289
|
-
var currentHTML = quill.root.innerHTML;
|
|
290
|
-
var cleanedHTML = cleanHTML(currentHTML);
|
|
291
|
-
lastKnownValueRef.current = cleanedHTML;
|
|
292
|
-
valuePropRef.current = cleanedHTML;
|
|
293
|
-
debouncedOnChange(cleanedHTML);
|
|
752
|
+
if (!maxValue || plainText.length + emoji.length <= maxValue) {
|
|
753
|
+
quill.insertText(savedRange.current.index, emoji);
|
|
754
|
+
quill.setSelection(savedRange.current.index + emoji.length);
|
|
294
755
|
}
|
|
295
756
|
}
|
|
296
|
-
}
|
|
297
|
-
var renderEmojiSection =
|
|
757
|
+
};
|
|
758
|
+
var renderEmojiSection = function (title, emojis) { return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
298
759
|
react_1.default.createElement("div", { className: "mb-2 mt-2 text-sm" }, title),
|
|
299
|
-
react_1.default.createElement(RowFlex_1.default, { gap: 0.3 }, emojis.map(function (emoji, i) { return (react_1.default.createElement("span", { key: i, className: "h6 pointer", onClick: function () { return insertEmoji(emoji); } }, emoji)); })))); }
|
|
760
|
+
react_1.default.createElement(RowFlex_1.default, { gap: 0.3 }, emojis.map(function (emoji, i) { return (react_1.default.createElement("span", { key: i, className: "h6 pointer", onClick: function () { return insertEmoji(emoji); } }, emoji)); })))); };
|
|
300
761
|
return (react_1.default.createElement("div", { className: "fit round-edge ".concat(funcss), style: { position: 'relative', overflow: 'visible' } },
|
|
301
762
|
react_1.default.createElement("div", { id: "editor-container", className: "bubble-editor-container p-0" },
|
|
302
763
|
react_1.default.createElement("div", { ref: quillRef, className: theme === 'bubble' ? 'bubble-editor' : 'snow-editor', style: {
|
package/ui/sidebar/SideBar.js
CHANGED
|
@@ -268,7 +268,7 @@ function SideBar(_a) {
|
|
|
268
268
|
links.length > 0 && (react_1.default.createElement("nav", { className: "sidebar-links" }, isAccordion ? (react_1.default.createElement(Accordion_1.default, { itemClass: accordionItemCss, items: accordionItems, allowMultiple: false, contentClass: "", titleClass: 'text-sm', activeClass: "" })) : (Object.entries(groupedLinks).map(function (_a) {
|
|
269
269
|
var section = _a[0], sectionLinks = _a[1];
|
|
270
270
|
return (react_1.default.createElement("div", { key: section, className: "sidebar-section ".concat(dividers ? 'bt' : '', " pt-2 pb-2") },
|
|
271
|
-
react_1.default.createElement(Text_1.default, { size: "
|
|
271
|
+
react_1.default.createElement(Text_1.default, { size: "xs", uppercase: true, opacity: 4 }, section),
|
|
272
272
|
sectionLinks.map(function (link, index) {
|
|
273
273
|
var isActive = link.onClick
|
|
274
274
|
? selectedOption === "".concat(section, "-").concat(index)
|