react-lite-rich-text-editor 1.1.8 → 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.esm.js CHANGED
@@ -409,7 +409,7 @@ function styleInject(css, ref) {
409
409
  }
410
410
  }
411
411
 
412
- 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}";
412
+ 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}";
413
413
  styleInject(css_248z);
414
414
 
415
415
  // Helper functions for HTML escaping
@@ -421,6 +421,108 @@ const escapeAttr = str => escapeHtml(str).replace(/"/g, """);
421
421
 
422
422
  // URL detection regex
423
423
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
424
+ const rectsIntersect = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
425
+ const caretRangeFromClientPoint = (x, y) => {
426
+ var _document$caretPositi, _document;
427
+ if (document.caretRangeFromPoint) {
428
+ return document.caretRangeFromPoint(x, y);
429
+ }
430
+ const position = (_document$caretPositi = (_document = document).caretPositionFromPoint) === null || _document$caretPositi === void 0 ? void 0 : _document$caretPositi.call(_document, x, y);
431
+ if (!position) return null;
432
+ const range = document.createRange();
433
+ range.setStart(position.offsetNode, position.offset);
434
+ range.collapse(true);
435
+ return range;
436
+ };
437
+ const clipRectToBounds = (rect, bounds) => {
438
+ const left = Math.max(rect.left, bounds.left);
439
+ const top = Math.max(rect.top, bounds.top);
440
+ const right = Math.min(rect.right, bounds.right);
441
+ const bottom = Math.min(rect.bottom, bounds.bottom);
442
+ return {
443
+ left,
444
+ top,
445
+ right,
446
+ bottom,
447
+ width: Math.max(0, right - left),
448
+ height: Math.max(0, bottom - top)
449
+ };
450
+ };
451
+ 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)";
452
+ const getIntersectingBlocks = (editor, clientRect) => {
453
+ const candidates = [...editor.querySelectorAll(BLOCK_SELECTOR)].filter(el => {
454
+ if (el === editor || !editor.contains(el)) return false;
455
+ if (el.closest(".rte-area-highlight-block, .image-container, .video-container")) {
456
+ return false;
457
+ }
458
+ return rectsIntersect(el.getBoundingClientRect(), clientRect);
459
+ });
460
+ return candidates.filter(el => !candidates.some(other => other !== el && other.contains(el)));
461
+ };
462
+ const insertAreaHighlightRegion = (editor, clientRect, color) => {
463
+ const editorRect = editor.getBoundingClientRect();
464
+ const clipped = clipRectToBounds(clientRect, editorRect);
465
+ const widthPx = Math.max(40, Math.round(clipped.width));
466
+ const heightPx = Math.max(24, Math.round(clipped.height));
467
+ const region = document.createElement("div");
468
+ region.className = "rte-area-highlight-block";
469
+ region.style.backgroundColor = color;
470
+ region.style.minHeight = `${heightPx}px`;
471
+ region.style.width = `${widthPx}px`;
472
+ region.style.maxWidth = "100%";
473
+ region.style.boxSizing = "border-box";
474
+ region.style.padding = "12px";
475
+ region.style.borderRadius = "4px";
476
+ region.style.margin = "8px 0";
477
+ region.style.display = "block";
478
+ const blocks = getIntersectingBlocks(editor, clipped);
479
+ if (blocks.length > 0) {
480
+ const first = blocks[0];
481
+ if (first.parentNode) {
482
+ first.parentNode.insertBefore(region, first);
483
+ } else {
484
+ editor.appendChild(region);
485
+ }
486
+ blocks.forEach(block => {
487
+ block.style.backgroundColor = "";
488
+ block.style.borderRadius = "";
489
+ region.appendChild(block);
490
+ });
491
+ return true;
492
+ }
493
+ const insertRange = caretRangeFromClientPoint(clipped.left + clipped.width / 2, clipped.top + Math.min(8, clipped.height / 2)) || caretRangeFromClientPoint(clipped.left + 4, clipped.top + 4);
494
+ region.innerHTML = "&nbsp;";
495
+ if (insertRange && editor.contains(insertRange.startContainer)) {
496
+ insertRange.collapse(true);
497
+ let node = insertRange.startContainer;
498
+ if (node.nodeType === 3) node = node.parentNode;
499
+ while (node && node !== editor && node.parentNode !== editor) {
500
+ node = node.parentNode;
501
+ }
502
+ if (node && node !== editor && editor.contains(node)) {
503
+ node.parentNode.insertBefore(region, node);
504
+ } else {
505
+ editor.appendChild(region);
506
+ }
507
+ } else {
508
+ editor.appendChild(region);
509
+ }
510
+ return true;
511
+ };
512
+ const getClientRectFromDrag = (startX, startY, currentX, currentY) => {
513
+ const left = Math.min(startX, currentX);
514
+ const top = Math.min(startY, currentY);
515
+ const right = Math.max(startX, currentX);
516
+ const bottom = Math.max(startY, currentY);
517
+ return {
518
+ left,
519
+ top,
520
+ right,
521
+ bottom,
522
+ width: right - left,
523
+ height: bottom - top
524
+ };
525
+ };
424
526
  function RichTextEditor({
425
527
  onChange,
426
528
  showEditButton,
@@ -439,8 +541,11 @@ function RichTextEditor({
439
541
  onImageUpload
440
542
  }) {
441
543
  const editorRef = useRef(null);
544
+ const contentWrapperRef = useRef(null);
442
545
  const fileInputRef = useRef(null);
443
546
  const scrollTopRef = useRef(0);
547
+ const areaDragRef = useRef(null);
548
+ const bgColorRef = useRef("#ffff00");
444
549
  const [html, setHtml] = useState("");
445
550
  const [linkModalOpen, setLinkModalOpen] = useState(false);
446
551
  const [linkUrl, setLinkUrl] = useState("");
@@ -481,11 +586,15 @@ function RichTextEditor({
481
586
  const [tableCols, setTableCols] = useState(3);
482
587
  const [selectionVersion, setSelectionVersion] = useState(0);
483
588
  const [selectedMedia, setSelectedMedia] = useState(null);
589
+ const [mediaWidthInput, setMediaWidthInput] = useState("100");
590
+ const [mediaWidthUnit, setMediaWidthUnit] = useState("%");
484
591
  const [metrics, setMetrics] = useState({
485
592
  words: 0,
486
593
  chars: 0
487
594
  });
488
595
  const [isEmpty, setIsEmpty] = useState(!value);
596
+ const [areaHighlightMode, setAreaHighlightMode] = useState(false);
597
+ const [marqueePreview, setMarqueePreview] = useState(null);
489
598
  const updateMetrics = useCallback(() => {
490
599
  if (!editorRef.current) return;
491
600
  // Calculate metrics immediately but outside of render path
@@ -522,6 +631,10 @@ function RichTextEditor({
522
631
  selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
523
632
  }
524
633
  };
634
+ const handleKeyUp = () => {
635
+ saveSelection();
636
+ setSelectionVersion(v => v + 1);
637
+ };
525
638
  const handleZoomIn = () => {
526
639
  setZoomLevel(prevZoom => prevZoom + 0.1);
527
640
  };
@@ -771,9 +884,31 @@ function RichTextEditor({
771
884
  }
772
885
  return 100;
773
886
  };
887
+ const getMediaWidthPx = container => {
888
+ if (!container) return 0;
889
+ if (container.dataset.widthPx) {
890
+ return Number(container.dataset.widthPx);
891
+ }
892
+ const width = container.style.width || "";
893
+ if (width.endsWith("px")) {
894
+ return parseInt(width, 10) || 0;
895
+ }
896
+ return Math.round(container.getBoundingClientRect().width);
897
+ };
898
+ const syncMediaWidthControls = container => {
899
+ if (!container) return;
900
+ if (container.dataset.widthPx) {
901
+ setMediaWidthInput(String(getMediaWidthPx(container)));
902
+ setMediaWidthUnit("px");
903
+ return;
904
+ }
905
+ setMediaWidthInput(String(getMediaWidthPercent(container)));
906
+ setMediaWidthUnit("%");
907
+ };
774
908
  const applyMediaWidthPercent = (container, percent) => {
775
909
  if (!container) return;
776
- const clamped = Math.max(25, Math.min(100, Math.round(percent)));
910
+ const clamped = Math.max(10, Math.min(100, Math.round(percent)));
911
+ delete container.dataset.widthPx;
777
912
  container.dataset.widthPercent = String(clamped);
778
913
  container.style.width = `${clamped}%`;
779
914
  container.style.maxWidth = "100%";
@@ -794,8 +929,36 @@ function RichTextEditor({
794
929
  img.style.objectFit = "";
795
930
  }
796
931
  };
932
+ const applyMediaWidthPx = (container, px) => {
933
+ if (!container) return;
934
+ const clamped = Math.max(20, Math.min(2000, Math.round(px)));
935
+ delete container.dataset.widthPercent;
936
+ container.dataset.widthPx = String(clamped);
937
+ container.style.width = `${clamped}px`;
938
+ container.style.maxWidth = "100%";
939
+ container.style.marginLeft = "";
940
+ container.style.marginTop = "";
941
+ if (container.classList.contains("video-container")) {
942
+ container.style.height = "0";
943
+ container.style.paddingBottom = "56.25%";
944
+ return;
945
+ }
946
+ const frame = getImageMediaTarget(container);
947
+ if (!frame) return;
948
+ frame.style.width = "100%";
949
+ frame.style.height = "";
950
+ const img = frame.querySelector("img");
951
+ if (img) {
952
+ img.style.height = "auto";
953
+ img.style.objectFit = "";
954
+ }
955
+ };
797
956
  const normalizeMediaWidth = container => {
798
957
  if (!container) return;
958
+ if (container.dataset.widthPx) {
959
+ applyMediaWidthPx(container, Number(container.dataset.widthPx));
960
+ return;
961
+ }
799
962
  if (container.classList.contains("image-small")) {
800
963
  container.classList.remove("image-small");
801
964
  applyMediaWidthPercent(container, 50);
@@ -811,11 +974,7 @@ function RichTextEditor({
811
974
  return;
812
975
  }
813
976
  if (width.endsWith("px")) {
814
- const editorWidth = getEditorInnerWidth();
815
- const px = parseFloat(width);
816
- if (editorWidth > 0 && px > 0) {
817
- applyMediaWidthPercent(container, Math.round(px / editorWidth * 100));
818
- }
977
+ applyMediaWidthPx(container, parseFloat(width));
819
978
  return;
820
979
  }
821
980
  if (container.classList.contains("video-container")) {
@@ -858,12 +1017,18 @@ function RichTextEditor({
858
1017
  const startWidth = container.getBoundingClientRect().width;
859
1018
  const onMouseMove = moveEvent => {
860
1019
  const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
861
- const percent = Math.round(nextWidth / editorWidth * 100);
862
- applyMediaWidthPercent(container, percent);
1020
+ if (container.dataset.widthPx) {
1021
+ applyMediaWidthPx(container, nextWidth);
1022
+ } else {
1023
+ const percent = Math.round(nextWidth / editorWidth * 100);
1024
+ applyMediaWidthPercent(container, percent);
1025
+ }
863
1026
  };
864
1027
  const onMouseUp = () => {
865
1028
  document.removeEventListener("mousemove", onMouseMove);
866
1029
  document.removeEventListener("mouseup", onMouseUp);
1030
+ syncMediaWidthControls(container);
1031
+ setSelectionVersion(v => v + 1);
867
1032
  triggerChange();
868
1033
  };
869
1034
  document.addEventListener("mousemove", onMouseMove);
@@ -990,11 +1155,118 @@ function RichTextEditor({
990
1155
  focus();
991
1156
  };
992
1157
  const [fontColor, setFontColor] = useState("#000000");
1158
+ const [bgColor, setBgColor] = useState("#ffff00");
1159
+ useEffect(() => {
1160
+ bgColorRef.current = bgColor;
1161
+ }, [bgColor]);
993
1162
  const getActiveTextColor = () => getColorAtCursor() || fontColor;
994
- const handleColorChange = color => {
1163
+ const restoreSavedSelection = () => {
1164
+ if (!selectionRangeRef.current) return;
1165
+ const sel = window.getSelection();
1166
+ if (!sel) return;
1167
+ sel.removeAllRanges();
1168
+ sel.addRange(selectionRangeRef.current);
1169
+ };
1170
+ const applyTextColor = color => {
995
1171
  setFontColor(color);
996
- exec("foreColor", color);
1172
+ focus();
1173
+ restoreSavedSelection();
1174
+ document.execCommand("styleWithCSS", false, true);
1175
+ document.execCommand("foreColor", false, color);
1176
+ document.execCommand("styleWithCSS", false, false);
1177
+ triggerChange();
1178
+ };
1179
+ const applyBackgroundColor = color => {
1180
+ setBgColor(color);
1181
+ focus();
1182
+ restoreSavedSelection();
1183
+ document.execCommand("styleWithCSS", false, true);
1184
+ if (!document.execCommand("hiliteColor", false, color)) {
1185
+ document.execCommand("backColor", false, color);
1186
+ }
1187
+ document.execCommand("styleWithCSS", false, false);
1188
+ triggerChange();
997
1189
  };
1190
+ const applyMarqueeHighlight = useCallback((clientRect, color) => {
1191
+ const editor = editorRef.current;
1192
+ if (!editor || clientRect.width < 10 || clientRect.height < 10) {
1193
+ return;
1194
+ }
1195
+ editor.focus();
1196
+ const editorRect = editor.getBoundingClientRect();
1197
+ const clippedRect = clipRectToBounds(clientRect, editorRect);
1198
+ if (clippedRect.width < 10 || clippedRect.height < 10) {
1199
+ return;
1200
+ }
1201
+ insertAreaHighlightRegion(editor, clippedRect, color);
1202
+ editor.querySelectorAll("td, th").forEach(cell => {
1203
+ if (!editor.contains(cell)) return;
1204
+ if (cell.closest(".rte-area-highlight-block")) return;
1205
+ if (rectsIntersect(cell.getBoundingClientRect(), clippedRect)) {
1206
+ cell.style.backgroundColor = color;
1207
+ }
1208
+ });
1209
+ editor.querySelectorAll(".image-container, .video-container").forEach(media => {
1210
+ if (!editor.contains(media)) return;
1211
+ if (rectsIntersect(media.getBoundingClientRect(), clippedRect)) {
1212
+ media.style.backgroundColor = color;
1213
+ media.style.padding = "4px";
1214
+ media.style.borderRadius = "4px";
1215
+ }
1216
+ });
1217
+ updateMetrics();
1218
+ triggerChange();
1219
+ focus();
1220
+ }, [triggerChange, updateMetrics]);
1221
+ const handleAreaSelectMouseDown = useCallback(e => {
1222
+ if (!areaHighlightMode || !editable || disabled) return;
1223
+ if (e.button !== 0) return;
1224
+ if (e.target.closest(".rte-toolbar, .rte-media-toolbar, .rte-modal-overlay")) return;
1225
+ e.preventDefault();
1226
+ e.stopPropagation();
1227
+ const wrapper = contentWrapperRef.current;
1228
+ if (!wrapper) return;
1229
+ const wrapperRect = wrapper.getBoundingClientRect();
1230
+ areaDragRef.current = {
1231
+ startX: e.clientX,
1232
+ startY: e.clientY,
1233
+ currentX: e.clientX,
1234
+ currentY: e.clientY,
1235
+ wrapperRect
1236
+ };
1237
+ const updatePreview = (clientX, clientY) => {
1238
+ const drag = areaDragRef.current;
1239
+ if (!drag) return;
1240
+ drag.currentX = clientX;
1241
+ drag.currentY = clientY;
1242
+ const rect = getClientRectFromDrag(drag.startX, drag.startY, drag.currentX, drag.currentY);
1243
+ setMarqueePreview({
1244
+ left: rect.left - drag.wrapperRect.left,
1245
+ top: rect.top - drag.wrapperRect.top,
1246
+ width: rect.width,
1247
+ height: rect.height
1248
+ });
1249
+ };
1250
+ const onMove = ev => {
1251
+ ev.preventDefault();
1252
+ updatePreview(ev.clientX, ev.clientY);
1253
+ };
1254
+ const onUp = ev => {
1255
+ document.removeEventListener("mousemove", onMove);
1256
+ document.removeEventListener("mouseup", onUp);
1257
+ const drag = areaDragRef.current;
1258
+ areaDragRef.current = null;
1259
+ setMarqueePreview(null);
1260
+ if (!drag) return;
1261
+ const clientRect = getClientRectFromDrag(drag.startX, drag.startY, ev.clientX, ev.clientY);
1262
+ requestAnimationFrame(() => {
1263
+ applyMarqueeHighlight(clientRect, bgColorRef.current);
1264
+ });
1265
+ };
1266
+ document.addEventListener("mousemove", onMove);
1267
+ document.addEventListener("mouseup", onUp);
1268
+ updatePreview(e.clientX, e.clientY);
1269
+ }, [areaHighlightMode, editable, disabled, applyMarqueeHighlight]);
998
1270
  const addLink = () => {
999
1271
  const sel = window.getSelection();
1000
1272
  if (!sel || sel.rangeCount === 0) return;
@@ -1248,9 +1520,25 @@ function RichTextEditor({
1248
1520
  if (frame && !frame.querySelector(".image-delete-button")) {
1249
1521
  frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
1250
1522
  existingWrapper.remove();
1523
+ clearMediaSelection();
1251
1524
  triggerChange && triggerChange();
1252
1525
  }));
1253
1526
  }
1527
+ if (!existingWrapper.dataset.mediaEnhanced) {
1528
+ existingWrapper.dataset.mediaEnhanced = "true";
1529
+ existingWrapper.addEventListener("click", event => {
1530
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1531
+ event.preventDefault();
1532
+ event.stopPropagation();
1533
+ selectMediaContainer(existingWrapper);
1534
+ });
1535
+ img.addEventListener("dblclick", event => {
1536
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1537
+ event.preventDefault();
1538
+ event.stopPropagation();
1539
+ openImageModal(img.src);
1540
+ });
1541
+ }
1254
1542
  attachMediaResizeHandle(existingWrapper);
1255
1543
  normalizeMediaWidth(existingWrapper);
1256
1544
  updateMediaControlVisibility(existingWrapper);
@@ -1328,14 +1616,33 @@ function RichTextEditor({
1328
1616
  event.stopPropagation();
1329
1617
  selectMediaContainer(container);
1330
1618
  });
1619
+ container.style.cursor = editable ? "pointer" : "default";
1620
+ container.dataset.mediaEnhanced = "true";
1621
+ container.addEventListener("click", event => {
1622
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1623
+ event.preventDefault();
1624
+ event.stopPropagation();
1625
+ selectMediaContainer(container);
1626
+ });
1627
+ const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1628
+ container.remove();
1629
+ clearMediaSelection();
1630
+ triggerChange();
1631
+ });
1331
1632
  frame.appendChild(img);
1633
+ frame.appendChild(deleteBtn);
1332
1634
  container.appendChild(frame);
1333
- applyMediaWidthPercent(container, 25);
1635
+ attachMediaResizeHandle(container);
1636
+ applyMediaWidthPercent(container, 50);
1334
1637
 
1335
1638
  // Insert at cursor position
1336
1639
  insertNodeAtCursor(container);
1337
1640
  requestAnimationFrame(() => {
1641
+ var _editorRef$current5;
1338
1642
  processExistingMedia(editorRef.current);
1643
+ selectMediaContainer(container);
1644
+ syncMediaWidthControls(container);
1645
+ (_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
1339
1646
  triggerChange();
1340
1647
  });
1341
1648
  }
@@ -1503,6 +1810,28 @@ function RichTextEditor({
1503
1810
  selection.addRange(newRange);
1504
1811
  };
1505
1812
  const handleKeyDown = useCallback(e => {
1813
+ var _editorRef$current6;
1814
+ if (e.key === "Escape") {
1815
+ if (areaHighlightMode) {
1816
+ e.preventDefault();
1817
+ areaDragRef.current = null;
1818
+ setMarqueePreview(null);
1819
+ setAreaHighlightMode(false);
1820
+ return;
1821
+ }
1822
+ if (selectedMedia) {
1823
+ e.preventDefault();
1824
+ clearMediaSelection();
1825
+ return;
1826
+ }
1827
+ }
1828
+ 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)) {
1829
+ e.preventDefault();
1830
+ selectedMedia.remove();
1831
+ clearMediaSelection();
1832
+ triggerChange();
1833
+ return;
1834
+ }
1506
1835
  if (applyMarkdownShortcut(e)) return;
1507
1836
 
1508
1837
  // Handle Enter key
@@ -1576,7 +1905,7 @@ function RichTextEditor({
1576
1905
  return;
1577
1906
  }
1578
1907
  if (e.key === "Backspace") {
1579
- var _node$closest, _node, _editorRef$current5;
1908
+ var _node$closest, _node, _editorRef$current7;
1580
1909
  const selection = window.getSelection();
1581
1910
  if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1582
1911
  const range = selection.getRangeAt(0);
@@ -1586,7 +1915,7 @@ function RichTextEditor({
1586
1915
  node = node.parentNode;
1587
1916
  }
1588
1917
  const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
1589
- if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
1918
+ if (!listItem || !((_editorRef$current7 = editorRef.current) !== null && _editorRef$current7 !== void 0 && _editorRef$current7.contains(listItem))) return;
1590
1919
  const list = listItem.parentNode;
1591
1920
  if (isListItemEffectivelyEmpty(listItem)) {
1592
1921
  e.preventDefault();
@@ -1635,7 +1964,7 @@ function RichTextEditor({
1635
1964
  e.preventDefault();
1636
1965
  exec("underline");
1637
1966
  }
1638
- }, [exec, triggerChange, fontColor]);
1967
+ }, [exec, triggerChange, fontColor, areaHighlightMode, selectedMedia]);
1639
1968
  const confirmLink = () => {
1640
1969
  // Add protocol if missing
1641
1970
  let url = linkUrl.trim();
@@ -1677,6 +2006,11 @@ function RichTextEditor({
1677
2006
  setCurrentFontSize("16");
1678
2007
  setCurrentLineHeight("");
1679
2008
  setFontColor("#000000");
2009
+ setBgColor("#ffff00");
2010
+ document.execCommand("styleWithCSS", false, true);
2011
+ document.execCommand("hiliteColor", false, "transparent");
2012
+ document.execCommand("backColor", false, "transparent");
2013
+ document.execCommand("styleWithCSS", false, false);
1680
2014
  triggerChange();
1681
2015
  focus();
1682
2016
  };
@@ -1903,7 +2237,11 @@ function RichTextEditor({
1903
2237
  }
1904
2238
  }, [disabled]);
1905
2239
  const handleEditorClick = useCallback(e => {
1906
- var _editorRef$current6;
2240
+ var _editorRef$current8;
2241
+ if (areaHighlightMode) {
2242
+ e.preventDefault();
2243
+ return;
2244
+ }
1907
2245
  setSelectionVersion(v => v + 1);
1908
2246
  const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1909
2247
  if (deleteBtn && editable && editorFocused) {
@@ -1918,7 +2256,10 @@ function RichTextEditor({
1918
2256
  return;
1919
2257
  }
1920
2258
  const clickedMedia = e.target.closest('.image-container, .video-container');
1921
- if (clickedMedia && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(clickedMedia) && editable) {
2259
+ if (clickedMedia && (_editorRef$current8 = editorRef.current) !== null && _editorRef$current8 !== void 0 && _editorRef$current8.contains(clickedMedia) && editable) {
2260
+ e.preventDefault();
2261
+ e.stopPropagation();
2262
+ selectMediaContainer(clickedMedia);
1922
2263
  return;
1923
2264
  }
1924
2265
 
@@ -1949,16 +2290,78 @@ function RichTextEditor({
1949
2290
  }
1950
2291
  }, 0);
1951
2292
  }
1952
- }, [editable, disabled, editorFocused, triggerChange]);
2293
+ }, [areaHighlightMode, editable, disabled, editorFocused, triggerChange]);
2294
+ useEffect(() => {
2295
+ if (!selectedMedia) return;
2296
+ syncMediaWidthControls(selectedMedia);
2297
+ }, [selectedMedia, selectionVersion]);
2298
+ useEffect(() => {
2299
+ const editor = editorRef.current;
2300
+ if (!editor || !selectedMedia) return;
2301
+ const bumpToolbarPosition = () => setSelectionVersion(v => v + 1);
2302
+ editor.addEventListener("scroll", bumpToolbarPosition, {
2303
+ passive: true
2304
+ });
2305
+ window.addEventListener("resize", bumpToolbarPosition, {
2306
+ passive: true
2307
+ });
2308
+ return () => {
2309
+ editor.removeEventListener("scroll", bumpToolbarPosition);
2310
+ window.removeEventListener("resize", bumpToolbarPosition);
2311
+ };
2312
+ }, [selectedMedia]);
1953
2313
  const renderMediaToolbar = () => {
1954
2314
  if (!selectedMedia || !editorRef.current || !editable) return null;
1955
- const editorRect = editorRef.current.getBoundingClientRect();
2315
+ const wrapperEl = contentWrapperRef.current;
2316
+ const anchorRect = (wrapperEl === null || wrapperEl === void 0 ? void 0 : wrapperEl.getBoundingClientRect()) ?? editorRef.current.getBoundingClientRect();
1956
2317
  const mediaRect = selectedMedia.getBoundingClientRect();
1957
- const top = mediaRect.top - editorRect.top + editorRef.current.scrollTop;
1958
- const left = mediaRect.left - editorRect.left + editorRef.current.scrollLeft;
1959
- const width = mediaRect.width;
2318
+ const relTop = mediaRect.top - anchorRect.top;
2319
+ const relLeft = mediaRect.left - anchorRect.left;
2320
+ const mediaWidth = mediaRect.width;
2321
+ const mediaHeight = mediaRect.height;
1960
2322
  const currentPercent = getMediaWidthPercent(selectedMedia);
1961
2323
  const widthPresets = [25, 50, 75, 100];
2324
+ const toolbarWidth = 340;
2325
+ const toolbarHeight = 40;
2326
+ const gap = 8;
2327
+ const preferredTop = relTop - toolbarHeight - gap;
2328
+ const toolbarTop = preferredTop >= 4 ? preferredTop : relTop + mediaHeight + gap;
2329
+ const applyCustomMediaWidth = () => {
2330
+ const num = parseFloat(mediaWidthInput);
2331
+ if (isNaN(num) || num <= 0) {
2332
+ syncMediaWidthControls(selectedMedia);
2333
+ return;
2334
+ }
2335
+ if (mediaWidthUnit === "px") {
2336
+ applyMediaWidthPx(selectedMedia, num);
2337
+ } else {
2338
+ applyMediaWidthPercent(selectedMedia, num);
2339
+ }
2340
+ syncMediaWidthControls(selectedMedia);
2341
+ setSelectionVersion(v => v + 1);
2342
+ triggerChange();
2343
+ };
2344
+ const handleUnitChange = newUnit => {
2345
+ if (newUnit === mediaWidthUnit) return;
2346
+ const editorInnerWidth = getEditorInnerWidth();
2347
+ const currentVal = parseFloat(mediaWidthInput);
2348
+ if (isNaN(currentVal) || currentVal <= 0) {
2349
+ setMediaWidthUnit(newUnit);
2350
+ return;
2351
+ }
2352
+ let converted = currentVal;
2353
+ if (newUnit === "px" && mediaWidthUnit === "%") {
2354
+ converted = Math.round(editorInnerWidth * currentVal / 100);
2355
+ applyMediaWidthPx(selectedMedia, converted);
2356
+ } else if (newUnit === "%" && mediaWidthUnit === "px") {
2357
+ converted = Math.min(100, Math.max(10, Math.round(currentVal / editorInnerWidth * 100)));
2358
+ applyMediaWidthPercent(selectedMedia, converted);
2359
+ }
2360
+ setMediaWidthInput(String(converted));
2361
+ setMediaWidthUnit(newUnit);
2362
+ setSelectionVersion(v => v + 1);
2363
+ triggerChange();
2364
+ };
1962
2365
  const handleAlignment = align => {
1963
2366
  selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
1964
2367
  selectedMedia.classList.add(`image-align-${align}`);
@@ -1968,6 +2371,8 @@ function RichTextEditor({
1968
2371
  };
1969
2372
  const setWidth = percent => {
1970
2373
  applyMediaWidthPercent(selectedMedia, percent);
2374
+ syncMediaWidthControls(selectedMedia);
2375
+ setSelectionVersion(v => v + 1);
1971
2376
  triggerChange();
1972
2377
  };
1973
2378
  const removeMedia = () => {
@@ -1976,17 +2381,26 @@ function RichTextEditor({
1976
2381
  triggerChange();
1977
2382
  };
1978
2383
  const isActivePercent = percent => {
2384
+ if (selectedMedia.dataset.widthPx) return false;
1979
2385
  if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
1980
2386
  return false;
1981
2387
  }
1982
2388
  return Math.abs(currentPercent - percent) <= 3;
1983
2389
  };
2390
+ const isFormControl = target => target instanceof Element && !!target.closest("input, select, textarea");
1984
2391
  return /*#__PURE__*/React.createElement("div", {
1985
2392
  className: "rte-media-toolbar",
2393
+ onMouseDown: e => {
2394
+ e.stopPropagation();
2395
+ if (!isFormControl(e.target)) {
2396
+ e.preventDefault();
2397
+ }
2398
+ },
2399
+ onClick: e => e.stopPropagation(),
1986
2400
  style: {
1987
2401
  position: "absolute",
1988
- top: Math.max(0, top - 44),
1989
- left: Math.max(8, left + width / 2 - 156),
2402
+ top: Math.max(4, toolbarTop),
2403
+ left: Math.max(8, Math.min(relLeft + mediaWidth / 2 - toolbarWidth / 2, anchorRect.width - toolbarWidth - 8)),
1990
2404
  zIndex: 1000
1991
2405
  }
1992
2406
  }, /*#__PURE__*/React.createElement("button", {
@@ -2011,6 +2425,44 @@ function RichTextEditor({
2011
2425
  title: `${percent}% width`
2012
2426
  }, percent, "%")), /*#__PURE__*/React.createElement("span", {
2013
2427
  className: "rte-media-toolbar-divider"
2428
+ }), /*#__PURE__*/React.createElement("div", {
2429
+ className: "rte-media-width-custom"
2430
+ }, /*#__PURE__*/React.createElement("input", {
2431
+ type: "number",
2432
+ className: "rte-media-width-input",
2433
+ value: mediaWidthInput,
2434
+ min: mediaWidthUnit === "%" ? 10 : 20,
2435
+ max: mediaWidthUnit === "%" ? 100 : 2000,
2436
+ title: "Custom width",
2437
+ onChange: e => setMediaWidthInput(e.target.value),
2438
+ onBlur: e => {
2439
+ var _e$relatedTarget, _e$relatedTarget$clos;
2440
+ 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;
2441
+ applyCustomMediaWidth();
2442
+ },
2443
+ onMouseDown: e => e.stopPropagation(),
2444
+ onKeyDown: e => {
2445
+ if (e.key === "Enter") {
2446
+ e.preventDefault();
2447
+ applyCustomMediaWidth();
2448
+ }
2449
+ }
2450
+ }), /*#__PURE__*/React.createElement("select", {
2451
+ className: "rte-media-width-unit",
2452
+ value: mediaWidthUnit,
2453
+ onChange: e => handleUnitChange(e.target.value),
2454
+ onMouseDown: e => e.stopPropagation()
2455
+ }, /*#__PURE__*/React.createElement("option", {
2456
+ value: "%"
2457
+ }, "%"), /*#__PURE__*/React.createElement("option", {
2458
+ value: "px"
2459
+ }, "px")), /*#__PURE__*/React.createElement("button", {
2460
+ type: "button",
2461
+ className: "rte-media-width-apply",
2462
+ title: "Apply size",
2463
+ onClick: applyCustomMediaWidth
2464
+ }, "\u2713")), /*#__PURE__*/React.createElement("span", {
2465
+ className: "rte-media-toolbar-divider"
2014
2466
  }), /*#__PURE__*/React.createElement("button", {
2015
2467
  type: "button",
2016
2468
  onClick: removeMedia,
@@ -2145,7 +2597,7 @@ function RichTextEditor({
2145
2597
  key: s,
2146
2598
  value: s
2147
2599
  }, s, "px"))), /*#__PURE__*/React.createElement("label", {
2148
- title: "Font Color",
2600
+ title: "Text Color",
2149
2601
  className: "rte-color-picker-label"
2150
2602
  }, /*#__PURE__*/React.createElement(FaFont, {
2151
2603
  size: 14,
@@ -2158,12 +2610,53 @@ function RichTextEditor({
2158
2610
  onMouseDown: e => {
2159
2611
  e.preventDefault();
2160
2612
  e.stopPropagation();
2613
+ saveSelection();
2614
+ },
2615
+ onChange: e => {
2616
+ e.stopPropagation();
2617
+ applyTextColor(e.target.value);
2618
+ },
2619
+ className: "rte-color-input"
2620
+ })), /*#__PURE__*/React.createElement("label", {
2621
+ title: areaHighlightMode ? "Highlight color for area selection" : "Text background color",
2622
+ className: "rte-color-picker-label rte-bg-color-picker-label"
2623
+ }, /*#__PURE__*/React.createElement("span", {
2624
+ className: "rte-bg-color-swatch",
2625
+ style: {
2626
+ backgroundColor: bgColor,
2627
+ color: fontColor
2628
+ }
2629
+ }, "A"), /*#__PURE__*/React.createElement("input", {
2630
+ type: "color",
2631
+ value: bgColor,
2632
+ onMouseDown: e => {
2633
+ e.preventDefault();
2634
+ e.stopPropagation();
2635
+ if (!areaHighlightMode) saveSelection();
2161
2636
  },
2162
2637
  onChange: e => {
2163
2638
  e.stopPropagation();
2164
- handleColorChange(e.target.value);
2639
+ const color = e.target.value;
2640
+ setBgColor(color);
2641
+ if (!areaHighlightMode) {
2642
+ applyBackgroundColor(color);
2643
+ }
2165
2644
  },
2166
2645
  className: "rte-color-input"
2646
+ })), /*#__PURE__*/React.createElement("button", {
2647
+ type: "button",
2648
+ title: "Area highlight \u2014 drag to select a box (like screenshot)",
2649
+ className: `rte-toolbar-button rte-area-highlight-toggle${areaHighlightMode ? " active" : ""}`,
2650
+ onMouseDown: e => e.preventDefault(),
2651
+ onClick: () => {
2652
+ setAreaHighlightMode(prev => !prev);
2653
+ setMarqueePreview(null);
2654
+ areaDragRef.current = null;
2655
+ focus();
2656
+ }
2657
+ }, /*#__PURE__*/React.createElement("span", {
2658
+ className: "rte-area-highlight-icon",
2659
+ "aria-hidden": "true"
2167
2660
  })), /*#__PURE__*/React.createElement("div", {
2168
2661
  style: {
2169
2662
  width: '1px',
@@ -2460,11 +2953,26 @@ function RichTextEditor({
2460
2953
  }
2461
2954
  return null;
2462
2955
  })()), /*#__PURE__*/React.createElement("div", {
2463
- className: "rte-content-wrapper",
2956
+ ref: contentWrapperRef,
2957
+ className: `rte-content-wrapper${areaHighlightMode ? " rte-area-highlight-mode" : ""}`,
2464
2958
  style: {
2465
2959
  position: 'relative'
2960
+ },
2961
+ onMouseDown: handleAreaSelectMouseDown
2962
+ }, areaHighlightMode && /*#__PURE__*/React.createElement("div", {
2963
+ className: "rte-area-highlight-hint",
2964
+ "aria-hidden": "true"
2965
+ }, "Drag to select an area, then release to apply highlight"), marqueePreview && /*#__PURE__*/React.createElement("div", {
2966
+ className: "rte-marquee-preview",
2967
+ style: {
2968
+ left: marqueePreview.left,
2969
+ top: marqueePreview.top,
2970
+ width: marqueePreview.width,
2971
+ height: marqueePreview.height,
2972
+ backgroundColor: `${bgColor}55`,
2973
+ borderColor: bgColor
2466
2974
  }
2467
- }, /*#__PURE__*/React.createElement("div", {
2975
+ }), /*#__PURE__*/React.createElement("div", {
2468
2976
  ref: editorRef,
2469
2977
  contentEditable: editable && disabled !== true,
2470
2978
  suppressContentEditableWarning: true,
@@ -2477,6 +2985,11 @@ function RichTextEditor({
2477
2985
  onDragStart: e => e.preventDefault(),
2478
2986
  onDragOver: e => e.preventDefault(),
2479
2987
  onKeyDown: handleKeyDown,
2988
+ onKeyUp: handleKeyUp,
2989
+ onMouseUp: () => {
2990
+ saveSelection();
2991
+ setSelectionVersion(v => v + 1);
2992
+ },
2480
2993
  onClick: handleEditorClick,
2481
2994
  onFocus: handleEditorFocus,
2482
2995
  onBlur: handleEditorBlur,
@@ -2492,7 +3005,7 @@ function RichTextEditor({
2492
3005
  left: paddingLeft || '12px'
2493
3006
  },
2494
3007
  "aria-hidden": "true"
2495
- }, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
3008
+ }, placeholder), renderMediaToolbar()), /*#__PURE__*/React.createElement("div", {
2496
3009
  className: "rte-footer"
2497
3010
  }, /*#__PURE__*/React.createElement("div", {
2498
3011
  className: "rte-footer-content"