react-lite-rich-text-editor 1.1.7 → 1.1.9

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/dist/index.cjs.js CHANGED
@@ -411,7 +411,7 @@ function styleInject(css, ref) {
411
411
  }
412
412
  }
413
413
 
414
- var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-heading-select{width:112px}.rte-toolbar-button-text{font-size:12px;font-weight:700;min-width:32px;padding:0 8px;width:auto}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content-wrapper{position:relative}.rte-placeholder{color:#9ca3af;font-size:16px;line-height:1.6;pointer-events:none;position:absolute;top:12px;user-select:none}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content blockquote{background:#f8fafc;border-left:4px solid #bfdbfe;border-radius:0 8px 8px 0;color:#475569;margin:12px 0;padding:8px 14px}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);display:block;height:0;line-height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative;width:100%}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;box-sizing:border-box;font-size:14px;outline:none;padding:8px 12px;transition:all .15s ease;width:100%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{display:block;line-height:0;margin:16px 0;max-width:100%;position:relative;width:fit-content}.image-container.image-align-center,.video-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left,.video-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right,.video-container.image-align-right{margin-left:auto;margin-right:0}.image-container.rte-media-selected,.video-container.rte-media-selected{border-radius:12px;outline:2px solid #3b82f6;outline-offset:2px}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:100%}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-container[data-width-percent] .image-media-frame,.image-container[data-width-percent] .image-media-frame img{width:100%}.image-delete-button{align-items:center;background:#ef4444;border:3px solid #fff;border-radius:9999px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#fff;cursor:pointer;display:none;font-size:18px;font-weight:700;height:26px;justify-content:center;line-height:1;padding:0;pointer-events:none;position:absolute;right:0;top:0;transform:translate(50%,-50%);transition:all .2s cubic-bezier(.4,0,.2,1);width:26px;z-index:50}.rte-content.rte-is-editable.rte-is-focused .image-delete-button,.rte-content.rte-is-editable.rte-is-focused .video-delete-button{display:flex;pointer-events:auto}.rte-content.rte-is-editable .video-container.rte-media-selected iframe{pointer-events:none}.image-delete-button:hover{background:#b91c1c;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);transform:translate(50%,-50%) scale(1.1)}.media-resize-handle{background:#3b82f6;border:2px solid #fff;border-radius:3px;bottom:2px;box-shadow:0 1px 3px rgba(15,23,42,.2);cursor:nwse-resize;height:12px;pointer-events:auto;position:absolute;right:2px;width:12px;z-index:60}.media-resize-handle:hover{background:#2563eb}.video-container .media-resize-handle{bottom:6px;right:6px}.video-container .video-delete-button{z-index:70}.rte-table-delete-btn,.rte-table-delete-hover{align-items:center;display:flex;justify-content:center}.rte-table-delete-btn{background:#fff;border:1px solid #ef4444;border-radius:6px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#ef4444;cursor:pointer;height:28px;padding:0;transition:all .2s ease;width:28px}.rte-table-delete-btn:hover{background:#ef4444;color:#fff;transform:scale(1.1)}.rte-table-delete-btn:active{transform:scale(.95)}.rte-media-toolbar{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);display:flex;gap:2px;padding:4px;pointer-events:auto}.rte-media-toolbar button{align-items:center;background:transparent;border:none;border-radius:4px;color:#4b5563;cursor:pointer;display:flex;font-size:11px;font-weight:600;height:28px;justify-content:center;min-width:28px;padding:0 6px;transition:all .15s ease}.rte-media-toolbar button.active,.rte-media-toolbar button:hover{background-color:#eff6ff;color:#2563eb}.rte-media-toolbar button.danger{color:#ef4444}.rte-media-toolbar button.danger:hover{background-color:#fef2f2;color:#dc2626}.rte-media-toolbar-divider{background:#e5e7eb;height:18px;margin:0 2px;width:1px}.image-container:after{clear:both;content:\"\";display:table}.rte-footer{background-color:#fcfcfd;border-top:1px solid #f3f4f6;display:flex;justify-content:flex-end;padding:6px 16px;user-select:none}.rte-footer-content{align-items:center;color:#9ca3af;display:flex;font-size:11px;gap:10px;letter-spacing:.025em}.rte-footer-separator{color:#e5e7eb;font-size:14px;line-height:1}.rte-footer-item b{color:#6b7280;font-weight:600}";
414
+ var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-heading-select{width:112px}.rte-toolbar-button-text{font-size:12px;font-weight:700;min-width:32px;padding:0 8px;width:auto}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-bg-color-picker-label .rte-bg-color-swatch{align-items:center;border:1px solid #d1d5db;border-radius:4px;display:inline-flex;font-size:11px;font-weight:700;height:18px;justify-content:center;line-height:1;pointer-events:none;width:18px}.rte-media-width-custom{align-items:center;display:flex;gap:2px}.rte-media-width-input{border:1px solid #e5e7eb;border-radius:4px;color:#374151;font-size:11px;height:28px;outline:none;padding:0 6px;width:56px}.rte-media-width-input:focus{border-color:#3b82f6}.rte-media-width-unit{background:#fff;border:1px solid #e5e7eb;border-radius:4px;color:#374151;cursor:pointer;font-size:11px;height:28px;outline:none;padding:0 4px;width:44px}.rte-media-width-apply{color:#2563eb!important;font-size:13px!important;font-weight:700;height:28px;min-width:28px!important;padding:0 6px!important}.rte-media-width-apply:hover{background-color:#eff6ff!important}.rte-area-highlight-mode .rte-content{cursor:crosshair}.rte-area-highlight-block{box-decoration-break:clone;-webkit-box-decoration-break:clone;box-sizing:border-box;display:block}.rte-area-highlight-hint{background:rgba(37,99,235,.92);border-radius:999px;color:#fff;font-size:11px;font-weight:600;left:50%;padding:4px 10px;pointer-events:none;position:absolute;top:8px;transform:translateX(-50%);white-space:nowrap;z-index:5}.rte-marquee-preview{border:2px dashed;border-radius:2px;box-sizing:border-box;pointer-events:none;position:absolute;z-index:6}.rte-area-highlight-toggle{min-width:32px;padding:0 6px!important;width:auto!important}.rte-area-highlight-icon{border:2px dashed;border-radius:2px;box-sizing:border-box;display:block;height:14px;width:14px}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content-wrapper{position:relative}.rte-placeholder{color:#9ca3af;font-size:16px;line-height:1.6;pointer-events:none;position:absolute;top:12px;user-select:none}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content blockquote{background:#f8fafc;border-left:4px solid #bfdbfe;border-radius:0 8px 8px 0;color:#475569;margin:12px 0;padding:8px 14px}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);display:block;height:0;line-height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative;width:100%}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;box-sizing:border-box;font-size:14px;outline:none;padding:8px 12px;transition:all .15s ease;width:100%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{box-sizing:border-box;display:block;line-height:0;margin:16px 0;max-width:100%;position:relative}.image-container.image-align-center,.video-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left,.video-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right,.video-container.image-align-right{margin-left:auto;margin-right:0}.image-container.rte-media-selected,.video-container.rte-media-selected{border-radius:12px;outline:2px solid #3b82f6;outline-offset:2px}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:100%}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-container[data-width-percent] .image-media-frame,.image-container[data-width-percent] .image-media-frame img{width:100%}.image-container[data-width-px] .image-media-frame,.image-container[data-width-px] .image-media-frame img{height:auto;width:100%}.image-delete-button{align-items:center;background:#ef4444;border:3px solid #fff;border-radius:9999px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#fff;cursor:pointer;display:none;font-size:18px;font-weight:700;height:26px;justify-content:center;line-height:1;padding:0;pointer-events:none;position:absolute;right:0;top:0;transform:translate(50%,-50%);transition:all .2s cubic-bezier(.4,0,.2,1);width:26px;z-index:50}.rte-content.rte-is-editable.rte-is-focused .image-delete-button,.rte-content.rte-is-editable.rte-is-focused .video-delete-button{display:flex;pointer-events:auto}.rte-content.rte-is-editable .video-container.rte-media-selected iframe{pointer-events:none}.image-delete-button:hover{background:#b91c1c;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);transform:translate(50%,-50%) scale(1.1)}.media-resize-handle{background:#3b82f6;border:2px solid #fff;border-radius:3px;bottom:2px;box-shadow:0 1px 3px rgba(15,23,42,.2);cursor:nwse-resize;height:12px;pointer-events:auto;position:absolute;right:2px;width:12px;z-index:60}.media-resize-handle:hover{background:#2563eb}.video-container .media-resize-handle{bottom:6px;right:6px}.video-container .video-delete-button{z-index:70}.rte-table-delete-btn,.rte-table-delete-hover{align-items:center;display:flex;justify-content:center}.rte-table-delete-btn{background:#fff;border:1px solid #ef4444;border-radius:6px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#ef4444;cursor:pointer;height:28px;padding:0;transition:all .2s ease;width:28px}.rte-table-delete-btn:hover{background:#ef4444;color:#fff;transform:scale(1.1)}.rte-table-delete-btn:active{transform:scale(.95)}.rte-media-toolbar{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);display:flex;flex-wrap:wrap;gap:2px;max-width:calc(100% - 16px);padding:4px;pointer-events:auto}.rte-media-toolbar button{align-items:center;background:transparent;border:none;border-radius:4px;color:#4b5563;cursor:pointer;display:flex;font-size:11px;font-weight:600;height:28px;justify-content:center;min-width:28px;padding:0 6px;transition:all .15s ease}.rte-media-toolbar button.active,.rte-media-toolbar button:hover{background-color:#eff6ff;color:#2563eb}.rte-media-toolbar button.danger{color:#ef4444}.rte-media-toolbar button.danger:hover{background-color:#fef2f2;color:#dc2626}.rte-media-toolbar-divider{background:#e5e7eb;height:18px;margin:0 2px;width:1px}.image-container:after{clear:both;content:\"\";display:table}.rte-footer{background-color:#fcfcfd;border-top:1px solid #f3f4f6;display:flex;justify-content:flex-end;padding:6px 16px;user-select:none}.rte-footer-content{align-items:center;color:#9ca3af;display:flex;font-size:11px;gap:10px;letter-spacing:.025em}.rte-footer-separator{color:#e5e7eb;font-size:14px;line-height:1}.rte-footer-item b{color:#6b7280;font-weight:600}";
415
415
  styleInject(css_248z);
416
416
 
417
417
  // Helper functions for HTML escaping
@@ -423,6 +423,108 @@ const escapeAttr = str => escapeHtml(str).replace(/"/g, """);
423
423
 
424
424
  // URL detection regex
425
425
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
426
+ const rectsIntersect = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
427
+ const caretRangeFromClientPoint = (x, y) => {
428
+ var _document$caretPositi, _document;
429
+ if (document.caretRangeFromPoint) {
430
+ return document.caretRangeFromPoint(x, y);
431
+ }
432
+ const position = (_document$caretPositi = (_document = document).caretPositionFromPoint) === null || _document$caretPositi === void 0 ? void 0 : _document$caretPositi.call(_document, x, y);
433
+ if (!position) return null;
434
+ const range = document.createRange();
435
+ range.setStart(position.offsetNode, position.offset);
436
+ range.collapse(true);
437
+ return range;
438
+ };
439
+ const clipRectToBounds = (rect, bounds) => {
440
+ const left = Math.max(rect.left, bounds.left);
441
+ const top = Math.max(rect.top, bounds.top);
442
+ const right = Math.min(rect.right, bounds.right);
443
+ const bottom = Math.min(rect.bottom, bounds.bottom);
444
+ return {
445
+ left,
446
+ top,
447
+ right,
448
+ bottom,
449
+ width: Math.max(0, right - left),
450
+ height: Math.max(0, bottom - top)
451
+ };
452
+ };
453
+ const BLOCK_SELECTOR = "h1, h2, h3, h4, h5, h6, p, blockquote, li, td, th, div:not(.rte-area-highlight-block):not(.image-container):not(.video-container):not(.image-media-frame)";
454
+ const getIntersectingBlocks = (editor, clientRect) => {
455
+ const candidates = [...editor.querySelectorAll(BLOCK_SELECTOR)].filter(el => {
456
+ if (el === editor || !editor.contains(el)) return false;
457
+ if (el.closest(".rte-area-highlight-block, .image-container, .video-container")) {
458
+ return false;
459
+ }
460
+ return rectsIntersect(el.getBoundingClientRect(), clientRect);
461
+ });
462
+ return candidates.filter(el => !candidates.some(other => other !== el && other.contains(el)));
463
+ };
464
+ const insertAreaHighlightRegion = (editor, clientRect, color) => {
465
+ const editorRect = editor.getBoundingClientRect();
466
+ const clipped = clipRectToBounds(clientRect, editorRect);
467
+ const widthPx = Math.max(40, Math.round(clipped.width));
468
+ const heightPx = Math.max(24, Math.round(clipped.height));
469
+ const region = document.createElement("div");
470
+ region.className = "rte-area-highlight-block";
471
+ region.style.backgroundColor = color;
472
+ region.style.minHeight = `${heightPx}px`;
473
+ region.style.width = `${widthPx}px`;
474
+ region.style.maxWidth = "100%";
475
+ region.style.boxSizing = "border-box";
476
+ region.style.padding = "12px";
477
+ region.style.borderRadius = "4px";
478
+ region.style.margin = "8px 0";
479
+ region.style.display = "block";
480
+ const blocks = getIntersectingBlocks(editor, clipped);
481
+ if (blocks.length > 0) {
482
+ const first = blocks[0];
483
+ if (first.parentNode) {
484
+ first.parentNode.insertBefore(region, first);
485
+ } else {
486
+ editor.appendChild(region);
487
+ }
488
+ blocks.forEach(block => {
489
+ block.style.backgroundColor = "";
490
+ block.style.borderRadius = "";
491
+ region.appendChild(block);
492
+ });
493
+ return true;
494
+ }
495
+ const insertRange = caretRangeFromClientPoint(clipped.left + clipped.width / 2, clipped.top + Math.min(8, clipped.height / 2)) || caretRangeFromClientPoint(clipped.left + 4, clipped.top + 4);
496
+ region.innerHTML = "&nbsp;";
497
+ if (insertRange && editor.contains(insertRange.startContainer)) {
498
+ insertRange.collapse(true);
499
+ let node = insertRange.startContainer;
500
+ if (node.nodeType === 3) node = node.parentNode;
501
+ while (node && node !== editor && node.parentNode !== editor) {
502
+ node = node.parentNode;
503
+ }
504
+ if (node && node !== editor && editor.contains(node)) {
505
+ node.parentNode.insertBefore(region, node);
506
+ } else {
507
+ editor.appendChild(region);
508
+ }
509
+ } else {
510
+ editor.appendChild(region);
511
+ }
512
+ return true;
513
+ };
514
+ const getClientRectFromDrag = (startX, startY, currentX, currentY) => {
515
+ const left = Math.min(startX, currentX);
516
+ const top = Math.min(startY, currentY);
517
+ const right = Math.max(startX, currentX);
518
+ const bottom = Math.max(startY, currentY);
519
+ return {
520
+ left,
521
+ top,
522
+ right,
523
+ bottom,
524
+ width: right - left,
525
+ height: bottom - top
526
+ };
527
+ };
426
528
  function RichTextEditor({
427
529
  onChange,
428
530
  showEditButton,
@@ -441,8 +543,11 @@ function RichTextEditor({
441
543
  onImageUpload
442
544
  }) {
443
545
  const editorRef = React.useRef(null);
546
+ const contentWrapperRef = React.useRef(null);
444
547
  const fileInputRef = React.useRef(null);
445
548
  const scrollTopRef = React.useRef(0);
549
+ const areaDragRef = React.useRef(null);
550
+ const bgColorRef = React.useRef("#ffff00");
446
551
  const [html, setHtml] = React.useState("");
447
552
  const [linkModalOpen, setLinkModalOpen] = React.useState(false);
448
553
  const [linkUrl, setLinkUrl] = React.useState("");
@@ -483,11 +588,15 @@ function RichTextEditor({
483
588
  const [tableCols, setTableCols] = React.useState(3);
484
589
  const [selectionVersion, setSelectionVersion] = React.useState(0);
485
590
  const [selectedMedia, setSelectedMedia] = React.useState(null);
591
+ const [mediaWidthInput, setMediaWidthInput] = React.useState("100");
592
+ const [mediaWidthUnit, setMediaWidthUnit] = React.useState("%");
486
593
  const [metrics, setMetrics] = React.useState({
487
594
  words: 0,
488
595
  chars: 0
489
596
  });
490
597
  const [isEmpty, setIsEmpty] = React.useState(!value);
598
+ const [areaHighlightMode, setAreaHighlightMode] = React.useState(false);
599
+ const [marqueePreview, setMarqueePreview] = React.useState(null);
491
600
  const updateMetrics = React.useCallback(() => {
492
601
  if (!editorRef.current) return;
493
602
  // Calculate metrics immediately but outside of render path
@@ -524,6 +633,10 @@ function RichTextEditor({
524
633
  selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
525
634
  }
526
635
  };
636
+ const handleKeyUp = () => {
637
+ saveSelection();
638
+ setSelectionVersion(v => v + 1);
639
+ };
527
640
  const handleZoomIn = () => {
528
641
  setZoomLevel(prevZoom => prevZoom + 0.1);
529
642
  };
@@ -773,9 +886,31 @@ function RichTextEditor({
773
886
  }
774
887
  return 100;
775
888
  };
889
+ const getMediaWidthPx = container => {
890
+ if (!container) return 0;
891
+ if (container.dataset.widthPx) {
892
+ return Number(container.dataset.widthPx);
893
+ }
894
+ const width = container.style.width || "";
895
+ if (width.endsWith("px")) {
896
+ return parseInt(width, 10) || 0;
897
+ }
898
+ return Math.round(container.getBoundingClientRect().width);
899
+ };
900
+ const syncMediaWidthControls = container => {
901
+ if (!container) return;
902
+ if (container.dataset.widthPx) {
903
+ setMediaWidthInput(String(getMediaWidthPx(container)));
904
+ setMediaWidthUnit("px");
905
+ return;
906
+ }
907
+ setMediaWidthInput(String(getMediaWidthPercent(container)));
908
+ setMediaWidthUnit("%");
909
+ };
776
910
  const applyMediaWidthPercent = (container, percent) => {
777
911
  if (!container) return;
778
- const clamped = Math.max(25, Math.min(100, Math.round(percent)));
912
+ const clamped = Math.max(10, Math.min(100, Math.round(percent)));
913
+ delete container.dataset.widthPx;
779
914
  container.dataset.widthPercent = String(clamped);
780
915
  container.style.width = `${clamped}%`;
781
916
  container.style.maxWidth = "100%";
@@ -796,8 +931,36 @@ function RichTextEditor({
796
931
  img.style.objectFit = "";
797
932
  }
798
933
  };
934
+ const applyMediaWidthPx = (container, px) => {
935
+ if (!container) return;
936
+ const clamped = Math.max(20, Math.min(2000, Math.round(px)));
937
+ delete container.dataset.widthPercent;
938
+ container.dataset.widthPx = String(clamped);
939
+ container.style.width = `${clamped}px`;
940
+ container.style.maxWidth = "100%";
941
+ container.style.marginLeft = "";
942
+ container.style.marginTop = "";
943
+ if (container.classList.contains("video-container")) {
944
+ container.style.height = "0";
945
+ container.style.paddingBottom = "56.25%";
946
+ return;
947
+ }
948
+ const frame = getImageMediaTarget(container);
949
+ if (!frame) return;
950
+ frame.style.width = "100%";
951
+ frame.style.height = "";
952
+ const img = frame.querySelector("img");
953
+ if (img) {
954
+ img.style.height = "auto";
955
+ img.style.objectFit = "";
956
+ }
957
+ };
799
958
  const normalizeMediaWidth = container => {
800
959
  if (!container) return;
960
+ if (container.dataset.widthPx) {
961
+ applyMediaWidthPx(container, Number(container.dataset.widthPx));
962
+ return;
963
+ }
801
964
  if (container.classList.contains("image-small")) {
802
965
  container.classList.remove("image-small");
803
966
  applyMediaWidthPercent(container, 50);
@@ -813,11 +976,7 @@ function RichTextEditor({
813
976
  return;
814
977
  }
815
978
  if (width.endsWith("px")) {
816
- const editorWidth = getEditorInnerWidth();
817
- const px = parseFloat(width);
818
- if (editorWidth > 0 && px > 0) {
819
- applyMediaWidthPercent(container, Math.round(px / editorWidth * 100));
820
- }
979
+ applyMediaWidthPx(container, parseFloat(width));
821
980
  return;
822
981
  }
823
982
  if (container.classList.contains("video-container")) {
@@ -860,12 +1019,18 @@ function RichTextEditor({
860
1019
  const startWidth = container.getBoundingClientRect().width;
861
1020
  const onMouseMove = moveEvent => {
862
1021
  const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
863
- const percent = Math.round(nextWidth / editorWidth * 100);
864
- applyMediaWidthPercent(container, percent);
1022
+ if (container.dataset.widthPx) {
1023
+ applyMediaWidthPx(container, nextWidth);
1024
+ } else {
1025
+ const percent = Math.round(nextWidth / editorWidth * 100);
1026
+ applyMediaWidthPercent(container, percent);
1027
+ }
865
1028
  };
866
1029
  const onMouseUp = () => {
867
1030
  document.removeEventListener("mousemove", onMouseMove);
868
1031
  document.removeEventListener("mouseup", onMouseUp);
1032
+ syncMediaWidthControls(container);
1033
+ setSelectionVersion(v => v + 1);
869
1034
  triggerChange();
870
1035
  };
871
1036
  document.addEventListener("mousemove", onMouseMove);
@@ -992,11 +1157,118 @@ function RichTextEditor({
992
1157
  focus();
993
1158
  };
994
1159
  const [fontColor, setFontColor] = React.useState("#000000");
1160
+ const [bgColor, setBgColor] = React.useState("#ffff00");
1161
+ React.useEffect(() => {
1162
+ bgColorRef.current = bgColor;
1163
+ }, [bgColor]);
995
1164
  const getActiveTextColor = () => getColorAtCursor() || fontColor;
996
- const handleColorChange = color => {
1165
+ const restoreSavedSelection = () => {
1166
+ if (!selectionRangeRef.current) return;
1167
+ const sel = window.getSelection();
1168
+ if (!sel) return;
1169
+ sel.removeAllRanges();
1170
+ sel.addRange(selectionRangeRef.current);
1171
+ };
1172
+ const applyTextColor = color => {
997
1173
  setFontColor(color);
998
- exec("foreColor", color);
1174
+ focus();
1175
+ restoreSavedSelection();
1176
+ document.execCommand("styleWithCSS", false, true);
1177
+ document.execCommand("foreColor", false, color);
1178
+ document.execCommand("styleWithCSS", false, false);
1179
+ triggerChange();
1180
+ };
1181
+ const applyBackgroundColor = color => {
1182
+ setBgColor(color);
1183
+ focus();
1184
+ restoreSavedSelection();
1185
+ document.execCommand("styleWithCSS", false, true);
1186
+ if (!document.execCommand("hiliteColor", false, color)) {
1187
+ document.execCommand("backColor", false, color);
1188
+ }
1189
+ document.execCommand("styleWithCSS", false, false);
1190
+ triggerChange();
999
1191
  };
1192
+ const applyMarqueeHighlight = React.useCallback((clientRect, color) => {
1193
+ const editor = editorRef.current;
1194
+ if (!editor || clientRect.width < 10 || clientRect.height < 10) {
1195
+ return;
1196
+ }
1197
+ editor.focus();
1198
+ const editorRect = editor.getBoundingClientRect();
1199
+ const clippedRect = clipRectToBounds(clientRect, editorRect);
1200
+ if (clippedRect.width < 10 || clippedRect.height < 10) {
1201
+ return;
1202
+ }
1203
+ insertAreaHighlightRegion(editor, clippedRect, color);
1204
+ editor.querySelectorAll("td, th").forEach(cell => {
1205
+ if (!editor.contains(cell)) return;
1206
+ if (cell.closest(".rte-area-highlight-block")) return;
1207
+ if (rectsIntersect(cell.getBoundingClientRect(), clippedRect)) {
1208
+ cell.style.backgroundColor = color;
1209
+ }
1210
+ });
1211
+ editor.querySelectorAll(".image-container, .video-container").forEach(media => {
1212
+ if (!editor.contains(media)) return;
1213
+ if (rectsIntersect(media.getBoundingClientRect(), clippedRect)) {
1214
+ media.style.backgroundColor = color;
1215
+ media.style.padding = "4px";
1216
+ media.style.borderRadius = "4px";
1217
+ }
1218
+ });
1219
+ updateMetrics();
1220
+ triggerChange();
1221
+ focus();
1222
+ }, [triggerChange, updateMetrics]);
1223
+ const handleAreaSelectMouseDown = React.useCallback(e => {
1224
+ if (!areaHighlightMode || !editable || disabled) return;
1225
+ if (e.button !== 0) return;
1226
+ if (e.target.closest(".rte-toolbar, .rte-media-toolbar, .rte-modal-overlay")) return;
1227
+ e.preventDefault();
1228
+ e.stopPropagation();
1229
+ const wrapper = contentWrapperRef.current;
1230
+ if (!wrapper) return;
1231
+ const wrapperRect = wrapper.getBoundingClientRect();
1232
+ areaDragRef.current = {
1233
+ startX: e.clientX,
1234
+ startY: e.clientY,
1235
+ currentX: e.clientX,
1236
+ currentY: e.clientY,
1237
+ wrapperRect
1238
+ };
1239
+ const updatePreview = (clientX, clientY) => {
1240
+ const drag = areaDragRef.current;
1241
+ if (!drag) return;
1242
+ drag.currentX = clientX;
1243
+ drag.currentY = clientY;
1244
+ const rect = getClientRectFromDrag(drag.startX, drag.startY, drag.currentX, drag.currentY);
1245
+ setMarqueePreview({
1246
+ left: rect.left - drag.wrapperRect.left,
1247
+ top: rect.top - drag.wrapperRect.top,
1248
+ width: rect.width,
1249
+ height: rect.height
1250
+ });
1251
+ };
1252
+ const onMove = ev => {
1253
+ ev.preventDefault();
1254
+ updatePreview(ev.clientX, ev.clientY);
1255
+ };
1256
+ const onUp = ev => {
1257
+ document.removeEventListener("mousemove", onMove);
1258
+ document.removeEventListener("mouseup", onUp);
1259
+ const drag = areaDragRef.current;
1260
+ areaDragRef.current = null;
1261
+ setMarqueePreview(null);
1262
+ if (!drag) return;
1263
+ const clientRect = getClientRectFromDrag(drag.startX, drag.startY, ev.clientX, ev.clientY);
1264
+ requestAnimationFrame(() => {
1265
+ applyMarqueeHighlight(clientRect, bgColorRef.current);
1266
+ });
1267
+ };
1268
+ document.addEventListener("mousemove", onMove);
1269
+ document.addEventListener("mouseup", onUp);
1270
+ updatePreview(e.clientX, e.clientY);
1271
+ }, [areaHighlightMode, editable, disabled, applyMarqueeHighlight]);
1000
1272
  const addLink = () => {
1001
1273
  const sel = window.getSelection();
1002
1274
  if (!sel || sel.rangeCount === 0) return;
@@ -1250,9 +1522,25 @@ function RichTextEditor({
1250
1522
  if (frame && !frame.querySelector(".image-delete-button")) {
1251
1523
  frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
1252
1524
  existingWrapper.remove();
1525
+ clearMediaSelection();
1253
1526
  triggerChange && triggerChange();
1254
1527
  }));
1255
1528
  }
1529
+ if (!existingWrapper.dataset.mediaEnhanced) {
1530
+ existingWrapper.dataset.mediaEnhanced = "true";
1531
+ existingWrapper.addEventListener("click", event => {
1532
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1533
+ event.preventDefault();
1534
+ event.stopPropagation();
1535
+ selectMediaContainer(existingWrapper);
1536
+ });
1537
+ img.addEventListener("dblclick", event => {
1538
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1539
+ event.preventDefault();
1540
+ event.stopPropagation();
1541
+ openImageModal(img.src);
1542
+ });
1543
+ }
1256
1544
  attachMediaResizeHandle(existingWrapper);
1257
1545
  normalizeMediaWidth(existingWrapper);
1258
1546
  updateMediaControlVisibility(existingWrapper);
@@ -1330,14 +1618,33 @@ function RichTextEditor({
1330
1618
  event.stopPropagation();
1331
1619
  selectMediaContainer(container);
1332
1620
  });
1621
+ container.style.cursor = editable ? "pointer" : "default";
1622
+ container.dataset.mediaEnhanced = "true";
1623
+ container.addEventListener("click", event => {
1624
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1625
+ event.preventDefault();
1626
+ event.stopPropagation();
1627
+ selectMediaContainer(container);
1628
+ });
1629
+ const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1630
+ container.remove();
1631
+ clearMediaSelection();
1632
+ triggerChange();
1633
+ });
1333
1634
  frame.appendChild(img);
1635
+ frame.appendChild(deleteBtn);
1334
1636
  container.appendChild(frame);
1335
- applyMediaWidthPercent(container, 25);
1637
+ attachMediaResizeHandle(container);
1638
+ applyMediaWidthPercent(container, 50);
1336
1639
 
1337
1640
  // Insert at cursor position
1338
1641
  insertNodeAtCursor(container);
1339
1642
  requestAnimationFrame(() => {
1643
+ var _editorRef$current5;
1340
1644
  processExistingMedia(editorRef.current);
1645
+ selectMediaContainer(container);
1646
+ syncMediaWidthControls(container);
1647
+ (_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
1341
1648
  triggerChange();
1342
1649
  });
1343
1650
  }
@@ -1505,6 +1812,28 @@ function RichTextEditor({
1505
1812
  selection.addRange(newRange);
1506
1813
  };
1507
1814
  const handleKeyDown = React.useCallback(e => {
1815
+ var _editorRef$current6;
1816
+ if (e.key === "Escape") {
1817
+ if (areaHighlightMode) {
1818
+ e.preventDefault();
1819
+ areaDragRef.current = null;
1820
+ setMarqueePreview(null);
1821
+ setAreaHighlightMode(false);
1822
+ return;
1823
+ }
1824
+ if (selectedMedia) {
1825
+ e.preventDefault();
1826
+ clearMediaSelection();
1827
+ return;
1828
+ }
1829
+ }
1830
+ if ((e.key === "Delete" || e.key === "Backspace") && selectedMedia !== null && selectedMedia !== void 0 && selectedMedia.classList.contains("rte-media-selected") && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(selectedMedia)) {
1831
+ e.preventDefault();
1832
+ selectedMedia.remove();
1833
+ clearMediaSelection();
1834
+ triggerChange();
1835
+ return;
1836
+ }
1508
1837
  if (applyMarkdownShortcut(e)) return;
1509
1838
 
1510
1839
  // Handle Enter key
@@ -1578,7 +1907,7 @@ function RichTextEditor({
1578
1907
  return;
1579
1908
  }
1580
1909
  if (e.key === "Backspace") {
1581
- var _node$closest, _node, _editorRef$current5;
1910
+ var _node$closest, _node, _editorRef$current7;
1582
1911
  const selection = window.getSelection();
1583
1912
  if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1584
1913
  const range = selection.getRangeAt(0);
@@ -1588,7 +1917,7 @@ function RichTextEditor({
1588
1917
  node = node.parentNode;
1589
1918
  }
1590
1919
  const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
1591
- if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
1920
+ if (!listItem || !((_editorRef$current7 = editorRef.current) !== null && _editorRef$current7 !== void 0 && _editorRef$current7.contains(listItem))) return;
1592
1921
  const list = listItem.parentNode;
1593
1922
  if (isListItemEffectivelyEmpty(listItem)) {
1594
1923
  e.preventDefault();
@@ -1637,7 +1966,7 @@ function RichTextEditor({
1637
1966
  e.preventDefault();
1638
1967
  exec("underline");
1639
1968
  }
1640
- }, [exec, triggerChange, fontColor]);
1969
+ }, [exec, triggerChange, fontColor, areaHighlightMode, selectedMedia]);
1641
1970
  const confirmLink = () => {
1642
1971
  // Add protocol if missing
1643
1972
  let url = linkUrl.trim();
@@ -1679,6 +2008,11 @@ function RichTextEditor({
1679
2008
  setCurrentFontSize("16");
1680
2009
  setCurrentLineHeight("");
1681
2010
  setFontColor("#000000");
2011
+ setBgColor("#ffff00");
2012
+ document.execCommand("styleWithCSS", false, true);
2013
+ document.execCommand("hiliteColor", false, "transparent");
2014
+ document.execCommand("backColor", false, "transparent");
2015
+ document.execCommand("styleWithCSS", false, false);
1682
2016
  triggerChange();
1683
2017
  focus();
1684
2018
  };
@@ -1905,7 +2239,11 @@ function RichTextEditor({
1905
2239
  }
1906
2240
  }, [disabled]);
1907
2241
  const handleEditorClick = React.useCallback(e => {
1908
- var _editorRef$current6;
2242
+ var _editorRef$current8;
2243
+ if (areaHighlightMode) {
2244
+ e.preventDefault();
2245
+ return;
2246
+ }
1909
2247
  setSelectionVersion(v => v + 1);
1910
2248
  const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1911
2249
  if (deleteBtn && editable && editorFocused) {
@@ -1920,7 +2258,10 @@ function RichTextEditor({
1920
2258
  return;
1921
2259
  }
1922
2260
  const clickedMedia = e.target.closest('.image-container, .video-container');
1923
- if (clickedMedia && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(clickedMedia) && editable) {
2261
+ if (clickedMedia && (_editorRef$current8 = editorRef.current) !== null && _editorRef$current8 !== void 0 && _editorRef$current8.contains(clickedMedia) && editable) {
2262
+ e.preventDefault();
2263
+ e.stopPropagation();
2264
+ selectMediaContainer(clickedMedia);
1924
2265
  return;
1925
2266
  }
1926
2267
 
@@ -1951,16 +2292,78 @@ function RichTextEditor({
1951
2292
  }
1952
2293
  }, 0);
1953
2294
  }
1954
- }, [editable, disabled, editorFocused, triggerChange]);
2295
+ }, [areaHighlightMode, editable, disabled, editorFocused, triggerChange]);
2296
+ React.useEffect(() => {
2297
+ if (!selectedMedia) return;
2298
+ syncMediaWidthControls(selectedMedia);
2299
+ }, [selectedMedia, selectionVersion]);
2300
+ React.useEffect(() => {
2301
+ const editor = editorRef.current;
2302
+ if (!editor || !selectedMedia) return;
2303
+ const bumpToolbarPosition = () => setSelectionVersion(v => v + 1);
2304
+ editor.addEventListener("scroll", bumpToolbarPosition, {
2305
+ passive: true
2306
+ });
2307
+ window.addEventListener("resize", bumpToolbarPosition, {
2308
+ passive: true
2309
+ });
2310
+ return () => {
2311
+ editor.removeEventListener("scroll", bumpToolbarPosition);
2312
+ window.removeEventListener("resize", bumpToolbarPosition);
2313
+ };
2314
+ }, [selectedMedia]);
1955
2315
  const renderMediaToolbar = () => {
1956
2316
  if (!selectedMedia || !editorRef.current || !editable) return null;
1957
- const editorRect = editorRef.current.getBoundingClientRect();
2317
+ const wrapperEl = contentWrapperRef.current;
2318
+ const anchorRect = (wrapperEl === null || wrapperEl === void 0 ? void 0 : wrapperEl.getBoundingClientRect()) ?? editorRef.current.getBoundingClientRect();
1958
2319
  const mediaRect = selectedMedia.getBoundingClientRect();
1959
- const top = mediaRect.top - editorRect.top + editorRef.current.scrollTop;
1960
- const left = mediaRect.left - editorRect.left + editorRef.current.scrollLeft;
1961
- const width = mediaRect.width;
2320
+ const relTop = mediaRect.top - anchorRect.top;
2321
+ const relLeft = mediaRect.left - anchorRect.left;
2322
+ const mediaWidth = mediaRect.width;
2323
+ const mediaHeight = mediaRect.height;
1962
2324
  const currentPercent = getMediaWidthPercent(selectedMedia);
1963
2325
  const widthPresets = [25, 50, 75, 100];
2326
+ const toolbarWidth = 340;
2327
+ const toolbarHeight = 40;
2328
+ const gap = 8;
2329
+ const preferredTop = relTop - toolbarHeight - gap;
2330
+ const toolbarTop = preferredTop >= 4 ? preferredTop : relTop + mediaHeight + gap;
2331
+ const applyCustomMediaWidth = () => {
2332
+ const num = parseFloat(mediaWidthInput);
2333
+ if (isNaN(num) || num <= 0) {
2334
+ syncMediaWidthControls(selectedMedia);
2335
+ return;
2336
+ }
2337
+ if (mediaWidthUnit === "px") {
2338
+ applyMediaWidthPx(selectedMedia, num);
2339
+ } else {
2340
+ applyMediaWidthPercent(selectedMedia, num);
2341
+ }
2342
+ syncMediaWidthControls(selectedMedia);
2343
+ setSelectionVersion(v => v + 1);
2344
+ triggerChange();
2345
+ };
2346
+ const handleUnitChange = newUnit => {
2347
+ if (newUnit === mediaWidthUnit) return;
2348
+ const editorInnerWidth = getEditorInnerWidth();
2349
+ const currentVal = parseFloat(mediaWidthInput);
2350
+ if (isNaN(currentVal) || currentVal <= 0) {
2351
+ setMediaWidthUnit(newUnit);
2352
+ return;
2353
+ }
2354
+ let converted = currentVal;
2355
+ if (newUnit === "px" && mediaWidthUnit === "%") {
2356
+ converted = Math.round(editorInnerWidth * currentVal / 100);
2357
+ applyMediaWidthPx(selectedMedia, converted);
2358
+ } else if (newUnit === "%" && mediaWidthUnit === "px") {
2359
+ converted = Math.min(100, Math.max(10, Math.round(currentVal / editorInnerWidth * 100)));
2360
+ applyMediaWidthPercent(selectedMedia, converted);
2361
+ }
2362
+ setMediaWidthInput(String(converted));
2363
+ setMediaWidthUnit(newUnit);
2364
+ setSelectionVersion(v => v + 1);
2365
+ triggerChange();
2366
+ };
1964
2367
  const handleAlignment = align => {
1965
2368
  selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
1966
2369
  selectedMedia.classList.add(`image-align-${align}`);
@@ -1970,6 +2373,8 @@ function RichTextEditor({
1970
2373
  };
1971
2374
  const setWidth = percent => {
1972
2375
  applyMediaWidthPercent(selectedMedia, percent);
2376
+ syncMediaWidthControls(selectedMedia);
2377
+ setSelectionVersion(v => v + 1);
1973
2378
  triggerChange();
1974
2379
  };
1975
2380
  const removeMedia = () => {
@@ -1978,17 +2383,26 @@ function RichTextEditor({
1978
2383
  triggerChange();
1979
2384
  };
1980
2385
  const isActivePercent = percent => {
2386
+ if (selectedMedia.dataset.widthPx) return false;
1981
2387
  if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
1982
2388
  return false;
1983
2389
  }
1984
2390
  return Math.abs(currentPercent - percent) <= 3;
1985
2391
  };
2392
+ const isFormControl = target => target instanceof Element && !!target.closest("input, select, textarea");
1986
2393
  return /*#__PURE__*/React.createElement("div", {
1987
2394
  className: "rte-media-toolbar",
2395
+ onMouseDown: e => {
2396
+ e.stopPropagation();
2397
+ if (!isFormControl(e.target)) {
2398
+ e.preventDefault();
2399
+ }
2400
+ },
2401
+ onClick: e => e.stopPropagation(),
1988
2402
  style: {
1989
2403
  position: "absolute",
1990
- top: Math.max(0, top - 44),
1991
- left: Math.max(8, left + width / 2 - 156),
2404
+ top: Math.max(4, toolbarTop),
2405
+ left: Math.max(8, Math.min(relLeft + mediaWidth / 2 - toolbarWidth / 2, anchorRect.width - toolbarWidth - 8)),
1992
2406
  zIndex: 1000
1993
2407
  }
1994
2408
  }, /*#__PURE__*/React.createElement("button", {
@@ -2013,6 +2427,44 @@ function RichTextEditor({
2013
2427
  title: `${percent}% width`
2014
2428
  }, percent, "%")), /*#__PURE__*/React.createElement("span", {
2015
2429
  className: "rte-media-toolbar-divider"
2430
+ }), /*#__PURE__*/React.createElement("div", {
2431
+ className: "rte-media-width-custom"
2432
+ }, /*#__PURE__*/React.createElement("input", {
2433
+ type: "number",
2434
+ className: "rte-media-width-input",
2435
+ value: mediaWidthInput,
2436
+ min: mediaWidthUnit === "%" ? 10 : 20,
2437
+ max: mediaWidthUnit === "%" ? 100 : 2000,
2438
+ title: "Custom width",
2439
+ onChange: e => setMediaWidthInput(e.target.value),
2440
+ onBlur: e => {
2441
+ var _e$relatedTarget, _e$relatedTarget$clos;
2442
+ if ((_e$relatedTarget = e.relatedTarget) !== null && _e$relatedTarget !== void 0 && (_e$relatedTarget$clos = _e$relatedTarget.closest) !== null && _e$relatedTarget$clos !== void 0 && _e$relatedTarget$clos.call(_e$relatedTarget, ".rte-media-width-custom")) return;
2443
+ applyCustomMediaWidth();
2444
+ },
2445
+ onMouseDown: e => e.stopPropagation(),
2446
+ onKeyDown: e => {
2447
+ if (e.key === "Enter") {
2448
+ e.preventDefault();
2449
+ applyCustomMediaWidth();
2450
+ }
2451
+ }
2452
+ }), /*#__PURE__*/React.createElement("select", {
2453
+ className: "rte-media-width-unit",
2454
+ value: mediaWidthUnit,
2455
+ onChange: e => handleUnitChange(e.target.value),
2456
+ onMouseDown: e => e.stopPropagation()
2457
+ }, /*#__PURE__*/React.createElement("option", {
2458
+ value: "%"
2459
+ }, "%"), /*#__PURE__*/React.createElement("option", {
2460
+ value: "px"
2461
+ }, "px")), /*#__PURE__*/React.createElement("button", {
2462
+ type: "button",
2463
+ className: "rte-media-width-apply",
2464
+ title: "Apply size",
2465
+ onClick: applyCustomMediaWidth
2466
+ }, "\u2713")), /*#__PURE__*/React.createElement("span", {
2467
+ className: "rte-media-toolbar-divider"
2016
2468
  }), /*#__PURE__*/React.createElement("button", {
2017
2469
  type: "button",
2018
2470
  onClick: removeMedia,
@@ -2058,12 +2510,9 @@ function RichTextEditor({
2058
2510
  }, /*#__PURE__*/React.createElement("button", {
2059
2511
  type: "button",
2060
2512
  title: "Bold",
2061
- onClick: e => {
2513
+ onMouseDown: e => {
2062
2514
  e.preventDefault();
2063
- e.stopPropagation();
2064
- document.execCommand("bold");
2065
- handleInput();
2066
- focus();
2515
+ exec("bold");
2067
2516
  },
2068
2517
  className: `rte-toolbar-button ${isBold ? "active" : ""}`
2069
2518
  }, /*#__PURE__*/React.createElement(FaBold, {
@@ -2071,12 +2520,9 @@ function RichTextEditor({
2071
2520
  })), /*#__PURE__*/React.createElement("button", {
2072
2521
  type: "button",
2073
2522
  title: "Italic",
2074
- onClick: e => {
2523
+ onMouseDown: e => {
2075
2524
  e.preventDefault();
2076
- e.stopPropagation();
2077
- document.execCommand("italic");
2078
- handleInput();
2079
- focus();
2525
+ exec("italic");
2080
2526
  },
2081
2527
  className: `rte-toolbar-button ${isItalic ? "active" : ""}`
2082
2528
  }, /*#__PURE__*/React.createElement(FaItalic, {
@@ -2084,12 +2530,9 @@ function RichTextEditor({
2084
2530
  })), /*#__PURE__*/React.createElement("button", {
2085
2531
  type: "button",
2086
2532
  title: "Underline",
2087
- onClick: e => {
2533
+ onMouseDown: e => {
2088
2534
  e.preventDefault();
2089
- e.stopPropagation();
2090
- document.execCommand("underline");
2091
- handleInput();
2092
- focus();
2535
+ exec("underline");
2093
2536
  },
2094
2537
  className: `rte-toolbar-button ${isUnderline ? "active" : ""}`
2095
2538
  }, /*#__PURE__*/React.createElement(FaUnderline, {
@@ -2156,7 +2599,7 @@ function RichTextEditor({
2156
2599
  key: s,
2157
2600
  value: s
2158
2601
  }, s, "px"))), /*#__PURE__*/React.createElement("label", {
2159
- title: "Font Color",
2602
+ title: "Text Color",
2160
2603
  className: "rte-color-picker-label"
2161
2604
  }, /*#__PURE__*/React.createElement(FaFont, {
2162
2605
  size: 14,
@@ -2169,12 +2612,53 @@ function RichTextEditor({
2169
2612
  onMouseDown: e => {
2170
2613
  e.preventDefault();
2171
2614
  e.stopPropagation();
2615
+ saveSelection();
2172
2616
  },
2173
2617
  onChange: e => {
2174
2618
  e.stopPropagation();
2175
- handleColorChange(e.target.value);
2619
+ applyTextColor(e.target.value);
2176
2620
  },
2177
2621
  className: "rte-color-input"
2622
+ })), /*#__PURE__*/React.createElement("label", {
2623
+ title: areaHighlightMode ? "Highlight color for area selection" : "Text background color",
2624
+ className: "rte-color-picker-label rte-bg-color-picker-label"
2625
+ }, /*#__PURE__*/React.createElement("span", {
2626
+ className: "rte-bg-color-swatch",
2627
+ style: {
2628
+ backgroundColor: bgColor,
2629
+ color: fontColor
2630
+ }
2631
+ }, "A"), /*#__PURE__*/React.createElement("input", {
2632
+ type: "color",
2633
+ value: bgColor,
2634
+ onMouseDown: e => {
2635
+ e.preventDefault();
2636
+ e.stopPropagation();
2637
+ if (!areaHighlightMode) saveSelection();
2638
+ },
2639
+ onChange: e => {
2640
+ e.stopPropagation();
2641
+ const color = e.target.value;
2642
+ setBgColor(color);
2643
+ if (!areaHighlightMode) {
2644
+ applyBackgroundColor(color);
2645
+ }
2646
+ },
2647
+ className: "rte-color-input"
2648
+ })), /*#__PURE__*/React.createElement("button", {
2649
+ type: "button",
2650
+ title: "Area highlight \u2014 drag to select a box (like screenshot)",
2651
+ className: `rte-toolbar-button rte-area-highlight-toggle${areaHighlightMode ? " active" : ""}`,
2652
+ onMouseDown: e => e.preventDefault(),
2653
+ onClick: () => {
2654
+ setAreaHighlightMode(prev => !prev);
2655
+ setMarqueePreview(null);
2656
+ areaDragRef.current = null;
2657
+ focus();
2658
+ }
2659
+ }, /*#__PURE__*/React.createElement("span", {
2660
+ className: "rte-area-highlight-icon",
2661
+ "aria-hidden": "true"
2178
2662
  })), /*#__PURE__*/React.createElement("div", {
2179
2663
  style: {
2180
2664
  width: '1px',
@@ -2471,11 +2955,26 @@ function RichTextEditor({
2471
2955
  }
2472
2956
  return null;
2473
2957
  })()), /*#__PURE__*/React.createElement("div", {
2474
- className: "rte-content-wrapper",
2958
+ ref: contentWrapperRef,
2959
+ className: `rte-content-wrapper${areaHighlightMode ? " rte-area-highlight-mode" : ""}`,
2475
2960
  style: {
2476
2961
  position: 'relative'
2962
+ },
2963
+ onMouseDown: handleAreaSelectMouseDown
2964
+ }, areaHighlightMode && /*#__PURE__*/React.createElement("div", {
2965
+ className: "rte-area-highlight-hint",
2966
+ "aria-hidden": "true"
2967
+ }, "Drag to select an area, then release to apply highlight"), marqueePreview && /*#__PURE__*/React.createElement("div", {
2968
+ className: "rte-marquee-preview",
2969
+ style: {
2970
+ left: marqueePreview.left,
2971
+ top: marqueePreview.top,
2972
+ width: marqueePreview.width,
2973
+ height: marqueePreview.height,
2974
+ backgroundColor: `${bgColor}55`,
2975
+ borderColor: bgColor
2477
2976
  }
2478
- }, /*#__PURE__*/React.createElement("div", {
2977
+ }), /*#__PURE__*/React.createElement("div", {
2479
2978
  ref: editorRef,
2480
2979
  contentEditable: editable && disabled !== true,
2481
2980
  suppressContentEditableWarning: true,
@@ -2488,6 +2987,11 @@ function RichTextEditor({
2488
2987
  onDragStart: e => e.preventDefault(),
2489
2988
  onDragOver: e => e.preventDefault(),
2490
2989
  onKeyDown: handleKeyDown,
2990
+ onKeyUp: handleKeyUp,
2991
+ onMouseUp: () => {
2992
+ saveSelection();
2993
+ setSelectionVersion(v => v + 1);
2994
+ },
2491
2995
  onClick: handleEditorClick,
2492
2996
  onFocus: handleEditorFocus,
2493
2997
  onBlur: handleEditorBlur,
@@ -2503,7 +3007,7 @@ function RichTextEditor({
2503
3007
  left: paddingLeft || '12px'
2504
3008
  },
2505
3009
  "aria-hidden": "true"
2506
- }, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
3010
+ }, placeholder), renderMediaToolbar()), /*#__PURE__*/React.createElement("div", {
2507
3011
  className: "rte-footer"
2508
3012
  }, /*#__PURE__*/React.createElement("div", {
2509
3013
  className: "rte-footer-content"