@oix1987/yjd 1.0.1 → 1.0.2
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/README.md +9 -1
- package/dist/rich-editor.esm.js +2 -0
- package/dist/rich-editor.esm.js.map +1 -0
- package/dist/rich-editor.min.js +2 -0
- package/dist/rich-editor.min.js.map +1 -0
- package/package.json +12 -7
- package/index.js +0 -221
- package/lib/core/editor.js +0 -1175
- package/lib/core/format.js +0 -542
- package/lib/core/module.js +0 -81
- package/lib/core/registry.js +0 -152
- package/lib/formats/background.js +0 -212
- package/lib/formats/bold.js +0 -67
- package/lib/formats/capitalization.js +0 -563
- package/lib/formats/color.js +0 -165
- package/lib/formats/emoji.js +0 -282
- package/lib/formats/font-family.js +0 -547
- package/lib/formats/heading.js +0 -502
- package/lib/formats/image.js +0 -344
- package/lib/formats/import.js +0 -385
- package/lib/formats/indent.js +0 -297
- package/lib/formats/italic.js +0 -27
- package/lib/formats/line-height.js +0 -558
- package/lib/formats/link.js +0 -251
- package/lib/formats/list.js +0 -635
- package/lib/formats/strike.js +0 -31
- package/lib/formats/subscript.js +0 -36
- package/lib/formats/superscript.js +0 -35
- package/lib/formats/table.js +0 -288
- package/lib/formats/tag.js +0 -304
- package/lib/formats/text-align.js +0 -421
- package/lib/formats/text-size.js +0 -497
- package/lib/formats/underline.js +0 -30
- package/lib/formats/video.js +0 -372
- package/lib/modules/block-toolbar.js +0 -628
- package/lib/modules/code-view.js +0 -434
- package/lib/modules/history.js +0 -410
- package/lib/modules/resize-handles.js +0 -677
- package/lib/modules/table-toolbar.js +0 -618
- package/lib/modules/toolbar.js +0 -424
- package/lib/styles-loader.js +0 -144
- package/lib/styles.css +0 -2123
- package/lib/ui/color-picker.js +0 -296
- package/lib/ui/customselect.js +0 -319
- package/lib/ui/emoji-picker.js +0 -196
- package/lib/ui/icons.js +0 -413
- package/lib/ui/image-popup.js +0 -444
- package/lib/ui/import-popup.js +0 -288
- package/lib/ui/link-popup.js +0 -191
- package/lib/ui/list-picker.js +0 -307
- package/lib/ui/select-button.js +0 -61
- package/lib/ui/table-popup.js +0 -171
- package/lib/ui/tag-popup.js +0 -249
- package/lib/ui/text-align-picker.js +0 -281
- package/lib/ui/video-popup.js +0 -422
- package/lib/utils/history-helper.js +0 -50
- package/lib/utils/popup-helper.js +0 -219
- package/lib/utils/popup-positioning.js +0 -231
package/lib/ui/image-popup.js
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image Popup Component - Popup for inserting images
|
|
3
|
-
*/
|
|
4
|
-
import { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';
|
|
5
|
-
|
|
6
|
-
class ImagePopup {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.options = {
|
|
9
|
-
onImageInsert: null,
|
|
10
|
-
editor: null,
|
|
11
|
-
...options
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
this.popup = null;
|
|
15
|
-
this.isVisible = false;
|
|
16
|
-
this.clickOutsideHandler = null;
|
|
17
|
-
this.selectedImageSrc = null;
|
|
18
|
-
this.savedSelection = null; // Save editor selection
|
|
19
|
-
this.resizeHandler = null;
|
|
20
|
-
|
|
21
|
-
this.createImagePopup();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create image popup
|
|
26
|
-
*/
|
|
27
|
-
createImagePopup() {
|
|
28
|
-
this.popup = document.createElement('div');
|
|
29
|
-
this.popup.className = 'image-popup';
|
|
30
|
-
|
|
31
|
-
const content = document.createElement('div');
|
|
32
|
-
content.className = 'image-popup-content';
|
|
33
|
-
|
|
34
|
-
// Title
|
|
35
|
-
const title = document.createElement('h3');
|
|
36
|
-
title.textContent = 'Upload image';
|
|
37
|
-
title.className = 'yjd-input-title';
|
|
38
|
-
content.appendChild(title);
|
|
39
|
-
|
|
40
|
-
// Container
|
|
41
|
-
const uploadContainer = document.createElement('div');
|
|
42
|
-
uploadContainer.className = 'image-input-container';
|
|
43
|
-
|
|
44
|
-
const textLabel = document.createElement('p');
|
|
45
|
-
textLabel.textContent = 'Your image url';
|
|
46
|
-
textLabel.className = 'yjd-input-label';
|
|
47
|
-
|
|
48
|
-
const inputgroup1 = document.createElement('div');
|
|
49
|
-
inputgroup1.className = 'yjd-input-upload-group';
|
|
50
|
-
this.inputGroup = inputgroup1; // Store reference
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// input url
|
|
54
|
-
this.urlInput = document.createElement('input');
|
|
55
|
-
this.urlInput.type = 'url';
|
|
56
|
-
this.urlInput.className = 'yjd-input';
|
|
57
|
-
this.urlInput.placeholder = 'Please enter your image URL';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Hidden file input
|
|
61
|
-
this.fileInput = document.createElement('input');
|
|
62
|
-
this.fileInput.type = 'file';
|
|
63
|
-
this.fileInput.accept = 'image/*';
|
|
64
|
-
this.fileInput.className = 'image-input-hidden'; // ẩn bằng CSS
|
|
65
|
-
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
|
|
66
|
-
|
|
67
|
-
// Custom button
|
|
68
|
-
const customButton = document.createElement('button');
|
|
69
|
-
customButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17" fill="none">
|
|
70
|
-
<g clip-path="url(#clip0_243_650)">
|
|
71
|
-
<path d="M9.45721 4.06101V11.4287C9.45721 11.966 9.02311 12.4001 8.48578 12.4001C7.94846 12.4001 7.51436 11.966 7.51436 11.4287V4.06101L5.28614 6.28923C4.90668 6.66869 4.29043 6.66869 3.91096 6.28923C3.5315 5.90976 3.5315 5.29351 3.91096 4.91405L7.79668 1.02833C8.17614 0.64887 8.79239 0.64887 9.17186 1.02833L13.0576 4.91405C13.437 5.29351 13.437 5.90976 13.0576 6.28923C12.6781 6.66869 12.0619 6.66869 11.6824 6.28923L9.45721 4.06101ZM2.65721 11.4287H6.54293C6.54293 12.5003 7.41418 13.3715 8.48578 13.3715C9.55739 13.3715 10.4286 12.5003 10.4286 11.4287H14.3144C15.386 11.4287 16.2572 12.2999 16.2572 13.3715V14.343C16.2572 15.4146 15.386 16.2858 14.3144 16.2858H2.65721C1.58561 16.2858 0.714355 15.4146 0.714355 14.343V13.3715C0.714355 12.2999 1.58561 11.4287 2.65721 11.4287ZM13.8286 14.5858C14.0219 14.5858 14.2072 14.5091 14.3438 14.3724C14.4805 14.2358 14.5572 14.0505 14.5572 13.8573C14.5572 13.664 14.4805 13.4787 14.3438 13.3421C14.2072 13.2055 14.0219 13.1287 13.8286 13.1287C13.6354 13.1287 13.4501 13.2055 13.3135 13.3421C13.1768 13.4787 13.1001 13.664 13.1001 13.8573C13.1001 14.0505 13.1768 14.2358 13.3135 14.3724C13.4501 14.5091 13.6354 14.5858 13.8286 14.5858Z" fill="#252424"/>
|
|
72
|
-
</g>
|
|
73
|
-
<defs>
|
|
74
|
-
<clipPath id="clip0_243_650">
|
|
75
|
-
<rect width="15.5429" height="15.5429" fill="white" transform="translate(0.714355 0.742859)"/>
|
|
76
|
-
</clipPath>
|
|
77
|
-
</defs>
|
|
78
|
-
</svg>`;
|
|
79
|
-
customButton.className = 'yjd-custom-upload-button';
|
|
80
|
-
this.customButton = customButton;
|
|
81
|
-
customButton.addEventListener('click', () => this.fileInput.click());
|
|
82
|
-
|
|
83
|
-
// Create preview container
|
|
84
|
-
this.createPreviewContainer();
|
|
85
|
-
|
|
86
|
-
// Append elements
|
|
87
|
-
inputgroup1.appendChild(this.urlInput);
|
|
88
|
-
inputgroup1.appendChild(this.fileInput);
|
|
89
|
-
inputgroup1.appendChild(customButton);
|
|
90
|
-
uploadContainer.appendChild(textLabel);
|
|
91
|
-
uploadContainer.appendChild(inputgroup1);
|
|
92
|
-
uploadContainer.appendChild(this.previewContainer);
|
|
93
|
-
content.appendChild(uploadContainer);
|
|
94
|
-
this.urlInput.addEventListener('input', () => {
|
|
95
|
-
this.updateInsertButton();
|
|
96
|
-
// Show preview if URL is valid
|
|
97
|
-
const url = this.urlInput.value.trim();
|
|
98
|
-
if (url && this.isValidImageUrl(url)) {
|
|
99
|
-
this.showPreview(url);
|
|
100
|
-
} else {
|
|
101
|
-
this.removePreview();
|
|
102
|
-
}
|
|
103
|
-
if(this.urlInput.value.trim()){
|
|
104
|
-
this.customButton.style.display = 'none';
|
|
105
|
-
}else{
|
|
106
|
-
this.customButton.style.display = 'flex';
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
// Buttons
|
|
110
|
-
const buttonContainer = document.createElement('div');
|
|
111
|
-
buttonContainer.className = 'yjd-button-container';
|
|
112
|
-
|
|
113
|
-
const cancelButton = document.createElement('button');
|
|
114
|
-
cancelButton.type = 'button';
|
|
115
|
-
cancelButton.className = 'image-button yjd-button-cancel';
|
|
116
|
-
cancelButton.textContent = 'Cancel';
|
|
117
|
-
cancelButton.addEventListener('click', () => {
|
|
118
|
-
this.hide();
|
|
119
|
-
// Maintain editor focus after popup close
|
|
120
|
-
if (this.options.editor) {
|
|
121
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
this.insertButton = document.createElement('button');
|
|
126
|
-
this.insertButton.type = 'button';
|
|
127
|
-
this.insertButton.className = 'image-button yjd-button-confirm button-disable';
|
|
128
|
-
this.insertButton.textContent = 'Add image';
|
|
129
|
-
this.insertButton.disabled = true;
|
|
130
|
-
this.insertButton.addEventListener('click', () => {
|
|
131
|
-
this.insertImage();
|
|
132
|
-
// Maintain editor focus after insert
|
|
133
|
-
if (this.options.editor) {
|
|
134
|
-
setTimeout(() => this.options.editor.focus(), 0);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
buttonContainer.appendChild(cancelButton);
|
|
139
|
-
buttonContainer.appendChild(this.insertButton);
|
|
140
|
-
content.appendChild(buttonContainer);
|
|
141
|
-
|
|
142
|
-
this.popup.appendChild(content);
|
|
143
|
-
appendPopup(this.popup);
|
|
144
|
-
|
|
145
|
-
// Prevent focus loss when clicking on popup
|
|
146
|
-
if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {
|
|
147
|
-
this.options.editor.preventFocusLoss(this.popup);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async handleFileSelect(e) {
|
|
152
|
-
const file = e.target.files[0];
|
|
153
|
-
if (!file) return;
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const { default: Image } = await import('../formats/image.js');
|
|
157
|
-
this.selectedImageSrc = await Image.handleFileUpload(file);
|
|
158
|
-
this.urlInput.value = '';
|
|
159
|
-
this.showPreview(this.selectedImageSrc);
|
|
160
|
-
this.updateInsertButton();
|
|
161
|
-
} catch (error) {
|
|
162
|
-
alert(error.message);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
updateInsertButton() {
|
|
167
|
-
const hasImage = this.selectedImageSrc || this.urlInput.value.trim();
|
|
168
|
-
this.insertButton.disabled = !hasImage;
|
|
169
|
-
this.insertButton.classList.toggle('button-disable', !hasImage);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Show image preview
|
|
174
|
-
*/
|
|
175
|
-
showPreview(imageSrc) {
|
|
176
|
-
if (!imageSrc) return;
|
|
177
|
-
|
|
178
|
-
this.imagePreview.src = imageSrc;
|
|
179
|
-
this.previewContainer.style.display = 'block';
|
|
180
|
-
this.selectedImageSrc = imageSrc;
|
|
181
|
-
|
|
182
|
-
// Hide input group
|
|
183
|
-
this.toggleInputGroup(false);
|
|
184
|
-
|
|
185
|
-
// Recalculate position after preview is shown to ensure buttons remain visible
|
|
186
|
-
this.recalculatePosition();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Remove image preview and show input again
|
|
191
|
-
*/
|
|
192
|
-
removePreview() {
|
|
193
|
-
this.selectedImageSrc = null;
|
|
194
|
-
this.previewContainer.style.display = 'none';
|
|
195
|
-
this.imagePreview.src = '';
|
|
196
|
-
|
|
197
|
-
// Show input group and reset file input
|
|
198
|
-
this.toggleInputGroup(true);
|
|
199
|
-
if (this.fileInput) {
|
|
200
|
-
this.fileInput.value = '';
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
this.updateInsertButton();
|
|
204
|
-
|
|
205
|
-
// Recalculate position after preview is removed
|
|
206
|
-
this.recalculatePosition();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Toggle input group visibility
|
|
211
|
-
*/
|
|
212
|
-
toggleInputGroup(show) {
|
|
213
|
-
if (!this.inputGroup) return;
|
|
214
|
-
|
|
215
|
-
if (show) {
|
|
216
|
-
this.inputGroup.style.display = 'flex';
|
|
217
|
-
this.inputGroup.style.visibility = 'visible';
|
|
218
|
-
if (this.customButton) {
|
|
219
|
-
this.customButton.style.pointerEvents = 'auto';
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
this.inputGroup.style.display = 'none';
|
|
223
|
-
this.inputGroup.style.visibility = 'hidden';
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Create preview container with image and remove button
|
|
229
|
-
*/
|
|
230
|
-
createPreviewContainer() {
|
|
231
|
-
this.previewContainer = document.createElement('div');
|
|
232
|
-
this.previewContainer.className = 'image-preview-container';
|
|
233
|
-
this.previewContainer.style.cssText = 'display: none; position: relative;';
|
|
234
|
-
|
|
235
|
-
// Image preview
|
|
236
|
-
this.imagePreview = document.createElement('img');
|
|
237
|
-
this.imagePreview.className = 'image-preview';
|
|
238
|
-
this.imagePreview.style.cssText = 'max-width: 100%; max-height: 200px; border-radius: 8px; object-fit: contain;';
|
|
239
|
-
|
|
240
|
-
// Remove button
|
|
241
|
-
this.removeButton = document.createElement('button');
|
|
242
|
-
this.removeButton.className = 'image-remove-button';
|
|
243
|
-
this.removeButton.innerHTML = '×';
|
|
244
|
-
this.removeButton.style.cssText = `
|
|
245
|
-
position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.7);
|
|
246
|
-
color: white; border: none; border-radius: 50%; width: 24px; height: 24px;
|
|
247
|
-
cursor: pointer; font-size: 16px; font-weight: bold;
|
|
248
|
-
`;
|
|
249
|
-
this.removeButton.addEventListener('click', () => this.removePreview());
|
|
250
|
-
|
|
251
|
-
this.previewContainer.appendChild(this.imagePreview);
|
|
252
|
-
this.previewContainer.appendChild(this.removeButton);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Check if URL is a valid image URL
|
|
257
|
-
*/
|
|
258
|
-
isValidImageUrl(url) {
|
|
259
|
-
try {
|
|
260
|
-
const urlObj = new URL(url);
|
|
261
|
-
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'];
|
|
262
|
-
const imageHosts = ['imgur.com', 'images.unsplash.com', 'picsum.photos', 'via.placeholder.com'];
|
|
263
|
-
|
|
264
|
-
const pathname = urlObj.pathname.toLowerCase();
|
|
265
|
-
const hasImageExtension = imageExtensions.some(ext => pathname.endsWith(ext));
|
|
266
|
-
const isFromImageHost = imageHosts.some(host => urlObj.hostname.includes(host));
|
|
267
|
-
|
|
268
|
-
return hasImageExtension || isFromImageHost;
|
|
269
|
-
} catch {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async insertImage() {
|
|
275
|
-
let src = this.selectedImageSrc || this.urlInput.value.trim();
|
|
276
|
-
const alt = '';
|
|
277
|
-
|
|
278
|
-
if (!src) return;
|
|
279
|
-
|
|
280
|
-
// Always validate URL (both file upload and URL input)
|
|
281
|
-
try {
|
|
282
|
-
const { default: Image } = await import('../formats/image.js');
|
|
283
|
-
const isValid = await Image.validateImageUrl(src);
|
|
284
|
-
if (!isValid) {
|
|
285
|
-
alert('Invalid image URL. Please check the URL and try again.');
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
} catch (error) {
|
|
289
|
-
alert('Error validating image URL.');
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Restore editor selection before inserting
|
|
294
|
-
this.restoreSelection();
|
|
295
|
-
|
|
296
|
-
if (this.options.onImageInsert) {
|
|
297
|
-
this.options.onImageInsert(src, alt);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
this.hide();
|
|
301
|
-
this.reset();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
reset() {
|
|
305
|
-
this.fileInput.value = '';
|
|
306
|
-
this.urlInput.value = '';
|
|
307
|
-
this.selectedImageSrc = null;
|
|
308
|
-
|
|
309
|
-
// Hide preview and show input
|
|
310
|
-
this.previewContainer.style.display = 'none';
|
|
311
|
-
this.imagePreview.src = '';
|
|
312
|
-
this.toggleInputGroup(true);
|
|
313
|
-
|
|
314
|
-
this.updateInsertButton();
|
|
315
|
-
this.customButton.style.display = 'block';
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Save current editor selection
|
|
320
|
-
*/
|
|
321
|
-
saveSelection() {
|
|
322
|
-
const selection = window.getSelection();
|
|
323
|
-
if (selection && selection.rangeCount > 0) {
|
|
324
|
-
this.savedSelection = selection.getRangeAt(0).cloneRange();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Restore editor selection
|
|
330
|
-
*/
|
|
331
|
-
restoreSelection() {
|
|
332
|
-
if (this.savedSelection) {
|
|
333
|
-
const selection = window.getSelection();
|
|
334
|
-
selection.removeAllRanges();
|
|
335
|
-
selection.addRange(this.savedSelection);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
setupClickOutside() {
|
|
340
|
-
if (this.clickOutsideHandler) {
|
|
341
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this.clickOutsideHandler = (e) => {
|
|
345
|
-
if (!this.popup.contains(e.target)) {
|
|
346
|
-
this.hide();
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
setTimeout(() => {
|
|
351
|
-
document.addEventListener('click', this.clickOutsideHandler);
|
|
352
|
-
}, 100);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
setupResizeHandler() {
|
|
356
|
-
if (this.resizeHandler) {
|
|
357
|
-
window.removeEventListener('resize', this.resizeHandler);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
this.resizeHandler = () => {
|
|
361
|
-
if (this.isVisible) {
|
|
362
|
-
this.recalculatePosition();
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
removeResizeHandler() {
|
|
370
|
-
if (this.resizeHandler) {
|
|
371
|
-
window.removeEventListener('resize', this.resizeHandler);
|
|
372
|
-
this.resizeHandler = null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
removeClickOutside() {
|
|
377
|
-
if (this.clickOutsideHandler) {
|
|
378
|
-
document.removeEventListener('click', this.clickOutsideHandler);
|
|
379
|
-
this.clickOutsideHandler = null;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
show(anchor) {
|
|
384
|
-
if (!anchor) return;
|
|
385
|
-
|
|
386
|
-
// Save current editor selection before showing popup
|
|
387
|
-
this.saveSelection();
|
|
388
|
-
|
|
389
|
-
// Reset state when showing popup
|
|
390
|
-
this.reset();
|
|
391
|
-
|
|
392
|
-
// Store anchor for recalculation
|
|
393
|
-
this.currentAnchor = anchor;
|
|
394
|
-
|
|
395
|
-
// Calculate and set popup position
|
|
396
|
-
const position = calculatePopupPosition(anchor, this.popup, {
|
|
397
|
-
offsetY: 5,
|
|
398
|
-
offsetX: 0
|
|
399
|
-
});
|
|
400
|
-
setPopupPosition(this.popup, position);
|
|
401
|
-
|
|
402
|
-
this.popup.classList.add('visible');
|
|
403
|
-
this.isVisible = true;
|
|
404
|
-
|
|
405
|
-
this.setupClickOutside();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Recalculate popup position to ensure it stays within viewport
|
|
410
|
-
*/
|
|
411
|
-
recalculatePosition() {
|
|
412
|
-
if (!this.currentAnchor || !this.isVisible) return;
|
|
413
|
-
|
|
414
|
-
// Small delay to ensure DOM updates are complete
|
|
415
|
-
setTimeout(() => {
|
|
416
|
-
const position = calculatePopupPosition(this.currentAnchor, this.popup, {
|
|
417
|
-
offsetY: 5,
|
|
418
|
-
offsetX: 0
|
|
419
|
-
});
|
|
420
|
-
setPopupPosition(this.popup, position);
|
|
421
|
-
}, 10);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
hide() {
|
|
425
|
-
this.popup.classList.remove('visible');
|
|
426
|
-
this.isVisible = false;
|
|
427
|
-
this.removeClickOutside();
|
|
428
|
-
// Clear saved selection to avoid memory leaks
|
|
429
|
-
this.savedSelection = null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
destroy() {
|
|
433
|
-
this.removeClickOutside();
|
|
434
|
-
|
|
435
|
-
if (this.popup && this.popup.parentNode) {
|
|
436
|
-
this.popup.parentNode.removeChild(this.popup);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
this.popup = null;
|
|
440
|
-
this.isVisible = false;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export default ImagePopup;
|